build: hook up traditional dbus activation
by Andreas Henriksson
On systems using systemd dbus will use the SystemdServices= directive,
but on non-systemd systems the so called "traditional dbus activation"
method involves reading the Exec= directive. Setting that to /bin/false
means this service is not relevant on non-systemd systems, but
apparently there's an interest from users of such systems to use
iwd (and possibly ead). Thus hook up the expansion of the paths in the
Exec= directive of the dbus service files to allow the traditional dbus
activation to start respective daemon.
Signed-off-by: Andreas Henriksson <andreas(a)fatal.se>
---
Makefile.am | 11 ++++++-----
src/net.connman.iwd.service | 5 -----
src/net.connman.iwd.service.in | 5 +++++
wired/net.connman.ead.service | 5 -----
wired/net.connman.ead.service.in | 5 +++++
5 files changed, 16 insertions(+), 15 deletions(-)
delete mode 100644 src/net.connman.iwd.service
create mode 100644 src/net.connman.iwd.service.in
delete mode 100644 wired/net.connman.ead.service
create mode 100644 wired/net.connman.ead.service.in
diff --git a/Makefile.am b/Makefile.am
index 57c694d..006d1d1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -250,7 +250,7 @@ dist_dbus_data_DATA += src/iwd-dbus.conf
endif
if SYSTEMD_SERVICE
-src_iwd_DEPENDENCIES += src/iwd.service
+src_iwd_DEPENDENCIES += src/iwd.service src/net.connman.iwd.service
systemd_unit_DATA += src/iwd.service
dbus_bus_DATA += src/net.connman.iwd.service
@@ -328,7 +328,7 @@ dist_dbus_data_DATA += wired/ead-dbus.conf
endif
if SYSTEMD_SERVICE
-wired_ead_DEPENDENCIES += wired/ead.service
+wired_ead_DEPENDENCIES += wired/ead.service wired/net.connman.ead.service
systemd_unit_DATA += wired/ead.service
dbus_bus_DATA += wired/net.connman.ead.service
@@ -524,8 +524,8 @@ unit_test_p2p_LDADD = $(ell_ldadd)
TESTS = $(unit_tests)
-EXTRA_DIST = src/genbuiltin src/iwd.service.in src/net.connman.iwd.service \
- wired/ead.service.in wired/net.connman.ead.service \
+EXTRA_DIST = src/genbuiltin src/iwd.service.in src/net.connman.iwd.service.in \
+ wired/ead.service.in wired/net.connman.ead.service.in \
src/80-iwd.link src/pkcs8.conf unit/gencerts.cnf \
$(manual_pages) $(patsubst %.1,%.rst, \
$(patsubst %.5,%.rst, \
@@ -539,7 +539,8 @@ if MAINTAINER_MODE
AM_CFLAGS += -DHAVE_PKCS8_SUPPORT
endif
-CLEANFILES = src/iwd.service wired/ead.service
+CLEANFILES = src/iwd.service wired/ead.service \
+ src/net.connman.iwd.service wired/net.connman.ead.service
DISTCHECK_CONFIGURE_FLAGS = --disable-dbus-policy --disable-systemd-service \
--enable-sim-hardcoded \
diff --git a/src/net.connman.iwd.service b/src/net.connman.iwd.service
deleted file mode 100644
index d8ece4c..0000000
--- a/src/net.connman.iwd.service
+++ /dev/null
@@ -1,5 +0,0 @@
-[D-BUS Service]
-Name=net.connman.iwd
-Exec=/bin/false
-User=root
-SystemdService=iwd.service
diff --git a/src/net.connman.iwd.service.in b/src/net.connman.iwd.service.in
new file mode 100644
index 0000000..a7cb7ed
--- /dev/null
+++ b/src/net.connman.iwd.service.in
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=net.connman.iwd
+Exec=@libexecdir@/iwd
+User=root
+SystemdService=iwd.service
diff --git a/wired/net.connman.ead.service b/wired/net.connman.ead.service
deleted file mode 100644
index 24af96b..0000000
--- a/wired/net.connman.ead.service
+++ /dev/null
@@ -1,5 +0,0 @@
-[D-BUS Service]
-Name=net.connman.ead
-Exec=/bin/false
-User=root
-SystemdService=ead.service
diff --git a/wired/net.connman.ead.service.in b/wired/net.connman.ead.service.in
new file mode 100644
index 0000000..63e5011
--- /dev/null
+++ b/wired/net.connman.ead.service.in
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=net.connman.ead
+Exec=@libexecdir@/ead
+User=root
+SystemdService=ead.service
1 year, 11 months
[PATCH 1/3] wfd-source: Display some stream properties
by Andrew Zaborowski
Define a bunch of stream parameters each with a getter and an optional
setter. In the right pane of the window show widgets for these
properties, some as just labels and some as editable controls depending
on the type of the property. Parse the EDID data.
---
test/wfd-source | 363 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 328 insertions(+), 35 deletions(-)
diff --git a/test/wfd-source b/test/wfd-source
index 09b47a27..bc5ce834 100755
--- a/test/wfd-source
+++ b/test/wfd-source
@@ -16,6 +16,7 @@ import collections.abc
import random
import dataclasses
import traceback
+import codecs
import gi
gi.require_version('GLib', '2.0')
@@ -27,7 +28,9 @@ class WFDRTSPServer:
class RTSPException(Exception):
pass
- def __init__(self, port, state_handler, error_handler):
+ Prop = collections.namedtuple('Prop', ['name', 'desc', 'getter', 'setter', 'type', 'vals'])
+
+ def __init__(self, port, state_handler, error_handler, init_values, prop_handler):
# Should start the TCP server only on the P2P connection's local IP but we won't
# know the IP or interface name until after the connection is established. At that
# time the sink may try to make the TCP connection at any time so our listen
@@ -44,7 +47,8 @@ class WFDRTSPServer:
self.state_handler = state_handler
self.error_handler = error_handler
- self.sm_init()
+ self.prop_handler = prop_handler
+ self.sm_init(init_values)
def handle_data_out(self, conn, *args):
try:
@@ -200,7 +204,11 @@ class WFDRTSPServer:
def ready(self):
return self._state in ['streaming', 'paused']
- def sm_init(self):
+ @property
+ def props(self):
+ return self._props
+
+ def sm_init(self, init_values):
self._state = 'waiting-rtsp'
self.local_params = {
'wfd_video_formats': '00 00 01 08 00000000 00000000 00000040 00 0000 0000 00 none none'
@@ -226,6 +234,33 @@ class WFDRTSPServer:
self.rtsp_keepalive_timeout = None
self.expected_remote_ip = None
self.remote_ip = None
+ self.init_width = init_values['width']
+ self.init_height = init_values['height']
+ self.rtcp_enabled = init_values['rtcp_enabled']
+
+ self._props = []
+
+ @staticmethod
+ def get_init_props():
+ props = []
+ values = {
+ 'width': 800,
+ 'height': 600,
+ 'rtcp_enabled': True
+ }
+
+ def set_val(key, val):
+ values[key] = val
+ props.append(WFDRTSPServer.Prop('Output width', 'Scale the video stream to this X resolution for sending',
+ lambda: values['width'], lambda x: set_val('width', x), int, (640, 1920)))
+ props.append(WFDRTSPServer.Prop('Output height', 'Scale the video stream to this Y resolution for sending',
+ lambda: values['height'], lambda x: set_val('height', x), int, (480, 1080)))
+ props.append(WFDRTSPServer.Prop('Enable RTCP', 'Use RTCP if the Sink requests it during setup',
+ lambda: values['rtcp_enabled'], lambda x: set_val('rtcp_enabled', x), bool, None))
+ # TODO: Enable Audio
+ # TODO: Audio source
+
+ return props, values
def close(self):
# Avoid passing self to io watches so that the refcount can ever reach 0 and
@@ -431,6 +466,94 @@ class WFDRTSPServer:
self.error('Optional RTCP port not valid in SETUP Transport header: ' + str(rtcp_port))
self.remote_rtcp_port = rtcp_port
+ self._props.append(WFDRTSPServer.Prop('RTP transport', '', lambda: 'TCP' if self.use_tcp else 'UDP', None, str, None))
+ self._props.append(WFDRTSPServer.Prop('Remote RTP port', '', lambda: self.remote_rtp_port, None, int, None))
+ self._props.append(WFDRTSPServer.Prop('Remote RTCP port', '', lambda: self.remote_rtcp_port, None, int, None))
+
+ def parse_display_edid(self):
+ try:
+ len_str, hex_str = self.remote_params['wfd_display_edid'].split(' ', 1)
+ if len(len_str.strip()) != 4:
+ raise Exception('edid-block-count length is not 4 hex digits')
+ blocks = int(len_str, 16)
+ edid = codecs.decode(hex_str.strip(), 'hex')
+ if blocks < 1 or blocks > 256 or blocks * 128 != len(edid):
+ raise Exception('edid-block-count value wrong')
+ except:
+ edid = None
+
+ self._props.append(WFDRTSPServer.Prop('EDID info', 'Remote display\'s EDID data', lambda: edid, None, bytes, None))
+
+ def create_running_props(self):
+ src = self.rtp_pipeline.get_by_name('src')
+ fps = self.rtp_pipeline.get_by_name('fps')
+ enc = self.rtp_pipeline.get_by_name('videnc')
+ res = self.rtp_pipeline.get_by_name('res')
+ sink = self.rtp_pipeline.get_by_name('sink')
+ self.pipeline_props = []
+
+ srcpadcaps = src.srcpads[0].get_allowed_caps()
+ width = srcpadcaps[0]['width']
+ height = srcpadcaps[0]['height']
+ props = []
+ props.append(WFDRTSPServer.Prop('Local width', 'Local screen X resolution', lambda: width, None, int, None))
+ props.append(WFDRTSPServer.Prop('Local height', 'Local screen Y resolution', lambda: height, None, int, None))
+
+ def set_use_damage(val):
+ src.props.use_damage = val
+ props.append(WFDRTSPServer.Prop('Use XDamage', 'Try to use XDamage to reduce bandwidth usage',
+ lambda: src.props.use_damage, set_use_damage, bool, None))
+
+ src.props.endx = width
+ src.props.endy = height
+ def set_startx(val):
+ src.set_property('startx', min(val, src.props.endx - 1))
+ def set_starty(val):
+ src.set_property('starty', min(val, src.props.endy - 1))
+ def set_endx(val):
+ src.set_property('endx', max(val, src.props.startx + 1))
+ def set_endy(val):
+ src.set_property('endy', max(val, src.props.starty + 1))
+ props.append(WFDRTSPServer.Prop('Window min X', 'Skip this many pixels on the left side of the local screen',
+ lambda: src.props.startx, set_startx, int, (0, width - 1)))
+ props.append(WFDRTSPServer.Prop('Window min Y', 'Skip this many pixels on the top of the local screen',
+ lambda: src.props.starty, set_starty, int, (0, height - 1)))
+ props.append(WFDRTSPServer.Prop('Window max X', 'Send screen contents only up to this X coordinate',
+ lambda: src.props.endx, set_endx, int, (1, width)))
+ props.append(WFDRTSPServer.Prop('Window max Y', 'Send screen contents only up to this Y coordinate',
+ lambda: src.props.endy, set_endy, int, (1, height)))
+
+ def set_framerate(val):
+ fps.props.caps[0]['framerate'] = Gst.Fraction(val)
+ def set_width(val):
+ res.props.caps[0]['width'] = val
+ def set_height(val):
+ res.props.caps[0]['height'] = val
+ props.append(WFDRTSPServer.Prop('Framerate', 'Try to output this many frames per second',
+ lambda: int(fps.props.caps[0]['framerate'].num), set_framerate, int, (1, 30)))
+ props.append(WFDRTSPServer.Prop('Output width', 'Scale the video stream to this X resolution for sending',
+ lambda: res.props.caps[0]['width'], set_width, int, (640, 1920)))
+ props.append(WFDRTSPServer.Prop('Output height', 'Scale the video stream to this Y resolution for sending',
+ lambda: res.props.caps[0]['height'], set_height, int, (480, 1080)))
+
+ preset_values = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast', 'placebo']
+ preset_map = {'veryslow': 9, 'slower': 8, 'slow': 7, 'medium': 6, 'fast': 5, 'faster': 4, 'veryfast': 3, 'superfast': 2, 'ultrafast': 1, 'placebo': 10}
+
+ def set_speed_preset(val):
+ enc.props.speed_preset = preset_map[val]
+ props.append(WFDRTSPServer.Prop('H.264 speed preset', 'Speed/quality setting of the H.264 encoder to optimise bandwidth/latency',
+ lambda: enc.props.speed_preset.value_nick, set_speed_preset, str, preset_values))
+
+ def set_max_lateness(val):
+ if val <= 0:
+ sink.props.max_lateness = -1
+ else:
+ sink.props.max_lateness = val * 1000000 # milliseconds to nanoseconds
+ props.append(WFDRTSPServer.Prop('Max lateness', 'Maximum number of milliseconds that a buffer can be late before it is dropped, or 0 for unlimited',
+ lambda: 0 if sink.props.max_lateness == -1 else sink.props.max_lateness / 1000000, set_max_lateness, int, (-1, 3000)))
+
+ return props
+
def on_gst_message(self, bus, message):
t = message.type
if t == Gst.MessageType.EOS:
@@ -438,6 +561,8 @@ class WFDRTSPServer:
elif t == Gst.MessageType.STATE_CHANGED:
old, new, pending = message.parse_state_changed()
self.debug('Gstreamer state change for ' + message.src.name + ' from ' + str(old) + ' to ' + str(new) + ', pending=' + str(pending))
+ if message.src == self.rtp_pipeline:
+ self.prop_handler()
elif t == Gst.MessageType.INFO:
err, debug = message.parse_info()
self.debug('Gstreamer info for ' + message.src.name + ': ' + str(err) + '\nDebug: ' + str(debug))
@@ -511,7 +636,8 @@ class WFDRTSPServer:
# Send M2 response
self.response(public=self.local_methods)
# Send M3
- self.request('GET_PARAMETER', 'rtsp://localhost/wfd1.0', params=['wfd_audio_codecs', 'wfd_video_formats', 'wfd_client_rtp_ports', 'wfd_display_edid', 'wfd_uibc_capability'])
+ params = ['wfd_audio_codecs', 'wfd_video_formats', 'wfd_client_rtp_ports', 'wfd_display_edid', 'wfd_uibc_capability']
+ self.request('GET_PARAMETER', 'rtsp://localhost/wfd1.0', params=params)
self.enter_state('M3')
elif self._state == 'M3':
# Validate M3 response
@@ -520,6 +646,8 @@ class WFDRTSPServer:
self.error('Required parameters missing from GET_PARAMETER response')
self.parse_video_formats(self.remote_params['wfd_video_formats'])
self.parse_client_rtp_ports(self.remote_params['wfd_client_rtp_ports'])
+ self.parse_display_edid()
+ self.prop_handler()
# Send M4
params = {
'wfd_video_formats': self.local_params['wfd_video_formats'],
@@ -547,7 +675,7 @@ class WFDRTSPServer:
self.session_stream_url = target
self.session_id = str(random.randint(a=1, b=999999))
self.local_rtp_port = random.randint(a=20000, b=30000)
- if self.remote_rtcp_port is not None:
+ if self.remote_rtcp_port is not None and self.rtcp_enabled:
self.local_rtcp_port = self.local_rtp_port + 1
profile ='RTP/AVP/TCP;unicast' if self.use_tcp else 'RTP/AVP/UDP;unicast'
client_port = str(self.remote_rtp_port) + (('-' + str(self.remote_rtcp_port)) if self.remote_rtcp_port is not None else '')
@@ -555,22 +683,26 @@ class WFDRTSPServer:
transport = profile + ';client_port' + client_port + ';server_port=' + server_port
# Section B.1
pipeline = ('ximagesrc name=src use-damage=false do-timestamp=true ! capsfilter name=fps caps=video/x-raw,framerate=10/1' +
- ' ! videoscale method=0 ! capsfilter name=res caps=video/x-raw,width=800,height=600' +
+ ' ! videoscale method=0 ! capsfilter name=res caps=video/x-raw,width=' + str(self.init_width) + ',height=' + str(self.init_height) +
' ! videoconvert ! video/x-raw,format=I420 ! x264enc tune=zerolatency speed-preset=ultrafast name=videnc' +
' ! queue' + # TODO: add leaky=downstream
' ! mpegtsmux name=mux' +
' ! rtpmp2tpay pt=33 mtu=1472 ! .send_rtp_sink rtpsession name=session .send_rtp_src' +
- ' ! udpsink host=' + self.remote_ip + ' port=' + str(self.remote_rtp_port) + ' bind-port=' + str(self.local_rtp_port)) # TODO: bind-address
-
+ ' ! udpsink name=sink host=' + self.remote_ip + ' port=' + str(self.remote_rtp_port) + ' bind-port=' + str(self.local_rtp_port)) # TODO: bind-address
if self.local_rtcp_port is not None:
pipeline += ' session.send_rtcp_src ! udpsink name=rtcp_sink host=' + self.remote_ip + \
' port=' + str(self.remote_rtcp_port) + ' bind-port=' + str(self.local_rtcp_port) # TODO: bind-address
+ self._props.append(WFDRTSPServer.Prop('RTCP enabled', 'Whether we\'re currently sending RTCP data',
+ lambda: self.local_rtcp_port is not None, None, bool, None))
self.rtp_pipeline = Gst.parse_launch(pipeline)
bus = self.rtp_pipeline.get_bus()
bus.enable_sync_message_emission()
bus.add_signal_watch()
- bus.connect('sync-message', self.on_gst_message)
+ bus.connect('message', self.on_gst_message)
+
+ self._props += self.create_running_props()
+ self.prop_handler()
# Send M6 response
self.response(session=self.session_id + ';timeout=' + str(self.session_timeout), transport=transport)
@@ -644,6 +776,8 @@ class WFDSource(Gtk.Window):
widget: Gtk.Widget
rtsp: WFDRTSPServer
+ indent = '\xbb '
+
def __init__(self):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, title='WFD Source')
self.set_decorated(True)
@@ -653,12 +787,16 @@ class WFDSource(Gtk.Window):
self.device_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
leftscroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER)
leftscroll.add(self.device_box)
- self.infolabel1 = Gtk.Label()
- self.infolabel1.set_ellipsize(Pango.EllipsizeMode.START)
- infopane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- infopane.pack_start(self.infolabel1, False, False, padding=10)
- rightscroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER, vscrollbar_policy=Gtk.PolicyType.NEVER)
- rightscroll.add(infopane)
+ self.infopane = Gtk.FlowBox(orientation=Gtk.Orientation.VERTICAL)
+ self.infopane.set_selection_mode(Gtk.SelectionMode.NONE)
+ self.infopane.set_max_children_per_line(20)
+ self.infopane.set_min_children_per_line(3)
+ self.infopane.set_column_spacing(20)
+ self.infopane.set_row_spacing(5)
+ self.infopane.set_valign(Gtk.Align.START)
+ self.infopane.set_halign(Gtk.Align.START)
+ rightscroll = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
+ rightscroll.add(self.infopane)
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
paned.pack1(leftscroll, True, True)
paned.pack2(rightscroll, False, False)
@@ -669,6 +807,8 @@ class WFDSource(Gtk.Window):
self.show_all()
self.connect('notify::is-active', self.on_notify_is_active)
+ self.rtsp_props = None
+ self.rtsp_init_values = {}
self.rtsp_port = 7236
self.devices = None
self.objects = {}
@@ -1001,11 +1141,13 @@ class WFDSource(Gtk.Window):
peer_list.insert(peer.widget, index)
peer.widget.show_all()
elif (PEER_IF not in props or WFD_IF not in props or WSC_IF not in props or not props[WFD_IF]['Sink']) and peer.widget:
- del device.sorted_peers[peer.widget.get_index()]
- peer_list.remove(peer.widget)
+ tmp = peer.widget
+ peer.widget = None
+ del device.sorted_peers[tmp.get_index()]
+ peer_list.remove(tmp)
if peer == device.selected_peer:
device.selected_peer = None
- self.update_info(dev_path, None)
+ self.update_info_pane(dev_path, None)
if peer == device.connecting_peer:
device.dbus_call.cancel()
device.connecting_peer = None
@@ -1020,7 +1162,6 @@ class WFDSource(Gtk.Window):
peer.peer_proxy = None
peer.wfd_proxy = None
peer.wsc_proxy = None
- peer.widget = None
if peer.rtsp:
peer.rtsp.close()
peer.rtsp = None
@@ -1055,7 +1196,7 @@ class WFDSource(Gtk.Window):
button.hide()
if peer == device.selected_peer:
- self.update_info(dev_path, path)
+ self.update_info_pane(dev_path, path)
def update_selected_peer(self, dev_path):
device = self.devices[dev_path]
@@ -1063,12 +1204,74 @@ class WFDSource(Gtk.Window):
sel_path = self.get_peer_path(device, device.selected_peer)
self.update_peer_props(dev_path, sel_path)
- def update_info(self, dev_path, path):
- device = self.devices[dev_path]
+ def add_info(self, name, desc, valuewidget):
+ namelabel = Gtk.Label(label=name + ':', xalign=0)
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ box.pack_start(namelabel, expand=False, fill=False, padding=3)
+ if valuewidget:
+ box.pack_end(valuewidget, expand=False, fill=False, padding=3)
+ if desc:
+ box.set_tooltip_text(desc)
+ self.infopane.add(box)
+
+ def add_info_str(self, name, value):
+ vlabel = Gtk.Label(xalign=0)
+ vlabel.set_markup('<span weight="bold">' + value + '</span>')
+ self.add_info(name, None, vlabel)
+
+ def add_info_prop(self, prop):
+ val = prop.getter()
+ if prop.setter is None:
+ if val is None:
+ return
+ if prop.type == bool:
+ vals = prop.vals if prop.vals is not None else ['no', 'yes']
+ text = vals[val]
+ elif prop.name == 'EDID info':
+ text = WFDSource.edid_to_text(val)
+ if isinstance(text, collections.abc.Sequence):
+ self.add_info(prop.name, prop.desc, None)
+ for name, val in text:
+ if val:
+ v = Gtk.Label(xalign=0)
+ v.set_markup('<span weight="bold">' + str(val) + '</span>')
+ else:
+ v = None
+ self.add_info(self.indent + name, prop.desc, v)
+ return
+ else:
+ text = str(val)
+ v = Gtk.Label(xalign=0)
+ v.set_markup('<span weight="bold">' + text + '</span>')
+ elif val is None:
+ return
+ elif prop.type == bool:
+ v = Gtk.Switch()
+ v.set_active(val)
+ v.connect('state-set', lambda switch, state: prop.setter(state))
+ elif prop.type == int:
+ v = Gtk.SpinButton.new_with_range(min=prop.vals[0], max=prop.vals[1], step=prop.vals[2] if len(prop.vals) > 2 else 1)
+ v.set_value(val)
+ v.connect('value-changed', lambda sb: prop.setter(int(sb.get_value())))
+ elif prop.type == str:
+ if prop.vals:
+ v = Gtk.ComboBoxText()
+ for option in prop.vals:
+ v.append(option, option)
+ v.set_active_id(val)
+ v.connect('changed', lambda entry: prop.setter(entry.get_active_text()))
+ else:
+ v = Gtk.Entry(text=val)
+ v.connect('changed', lambda entry: prop.setter(entry.get_text()))
+ self.add_info(prop.name, prop.desc, v)
+
+ def update_info_pane(self, dev_path, path):
+ self.infopane.foreach(lambda x, y: self.infopane.remove(x), None)
+
if path is None:
- self.infolabel1.set_text('')
return
+ device = self.devices[dev_path]
peer = device.peers[path]
if peer == device.connecting_peer:
@@ -1085,14 +1288,13 @@ class WFDSource(Gtk.Window):
state = 'connected'
else:
state = 'not connected'
+ self.add_info_str('Connection state', state)
subcat = 'unknown'
if 'DeviceSubcategory' in self.objects[path][PEER_IF]:
subcat = self.objects[path][PEER_IF]['DeviceSubcategory']
-
- text = ('Connection state: ' + state + '\n' +
- 'Device category: ' + self.objects[path][PEER_IF]['DeviceCategory'] + '\n'
- 'Device subcategory: ' + subcat + '\n')
+ self.add_info_str('Peer category', self.objects[path][PEER_IF]['DeviceCategory'])
+ self.add_info_str('Peer subcategory', subcat)
if WFD_IF in self.objects[path]:
if self.objects[path][WFD_IF]['Source']:
@@ -1102,17 +1304,27 @@ class WFDSource(Gtk.Window):
t = 'source'
else:
t = 'sink'
- text += 'WFD device type: ' + t + '\n'
+ self.add_info_str('Peer WFD type', t)
if self.objects[path][WFD_IF]['Sink']:
- text += 'Audio: ' + ('yes' if self.objects[path][WFD_IF]['HasAudio'] else 'no') + '\n'
+ self.add_info_str('Peer audio support', 'yes' if self.objects[path][WFD_IF]['HasAudio'] else 'no')
+
+ self.add_info_str('Peer UIBC support', 'yes' if self.objects[path][WFD_IF]['HasUIBC'] else 'no')
+
+ self.add_info_str('Peer content protection', 'yes' if self.objects[path][WFD_IF]['HasContentProtection'] else 'no')
+
+ if self.rtsp_props is None:
+ self.rtsp_props, self.rtsp_init_values = WFDRTSPServer.get_init_props()
- text += 'UIBC: ' + ('yes' if self.objects[path][WFD_IF]['HasUIBC'] else 'no') + '\n'
+ if peer.rtsp is not None:
+ props = peer.rtsp.props
+ else:
+ props = self.rtsp_props
- text += 'Content protection: ' + ('yes' if self.objects[path][WFD_IF]['HasContentProtection'] else 'no') + '\n'
+ for prop in props:
+ self.add_info_prop(prop)
- self.infolabel1.set_text(text)
- # TODO: more info in labels 2 and so on
+ self.infopane.show_all()
# Direct method calls on dbus.Interface's don't return dbus.lowlevel.PendingCall objects so
# we have to use bus.call_async to make cancellable async calls
@@ -1172,12 +1384,17 @@ class WFDSource(Gtk.Window):
dialog.connect('response', on_ok)
+ def on_rtsp_props_changed():
+ # Should also check if the infopane is currently showing a selected peer on another device...
+ if peer == device.selected_peer:
+ self.update_info_pane(dev_path, path)
+
# Cannot use peer.wsc_proxy.PushButton()
device.dbus_call = self.async_call(peer.wsc_proxy, 'PushButton', reply_handler=on_reply, error_handler=on_error, timeout=120)
device.connecting_peer = peer
# Create the RTSP server now so it's ready as soon as the P2P connection succeeds even if
# we haven't received the DBus reply yet
- peer.rtsp = WFDRTSPServer(self.rtsp_port, on_rtsp_state, on_rtsp_error)
+ peer.rtsp = WFDRTSPServer(self.rtsp_port, on_rtsp_state, on_rtsp_error, self.rtsp_init_values, on_rtsp_props_changed)
self.update_dev_props(dev_path)
self.update_peer_props(dev_path, path)
if peer != device.selected_peer:
@@ -1272,7 +1489,7 @@ class WFDSource(Gtk.Window):
path = self.get_peer_path(device, device.selected_peer)
device.selected_peer = None
self.update_peer_props(dev_path, path)
- self.update_info(dev_path, None)
+ self.update_info_pane(dev_path, None)
if row is None:
return True
@@ -1335,6 +1552,82 @@ class WFDSource(Gtk.Window):
mainloop.quit()
return False
+ @staticmethod
+ def edid_to_text(edid):
+ if edid is None:
+ return 'unavailable'
+ if len(edid) < 128:
+ return 'invalid (too short)'
+ if edid[0:8] != b'\0\xff\xff\xff\xff\xff\xff\0':
+ return 'invalid (bad magic)'
+ if sum(edid[0:128]) & 255 != 0:
+ return 'invalid (bad checksum)'
+
+ header = edid[0:20]
+ manf_id = (header[8] << 8) + header[9]
+ text = [('Header', '')]
+ text.append((WFDSource.indent + 'Version', str(header[18]) + '.' + str(header[19])))
+ text.append((WFDSource.indent + 'Manufacturer ID', chr(64 + ((manf_id >> 10) & 31)) + chr(64 + ((manf_id >> 5) & 31)) + chr(64 + ((manf_id >> 0) & 31))))
+ text.append((WFDSource.indent + 'Product code', hex((header[11] << 8) + header[10])))
+ text.append((WFDSource.indent + 'Serial', hex((header[15] << 24) +(header[14] << 16) + (header[13] << 8) + header[12])))
+ text.append((WFDSource.indent + 'Manufactured', str(1990 + header[17]) + ' week ' + str(header[16])))
+
+ basic_params = edid[20:25]
+ text.append(('Basic parameters', ''))
+ if basic_params[0] & 0x80:
+ intf_table = {
+ 2: 'HDMIa',
+ 3: 'HDMIb',
+ 4: 'MDDI',
+ 5: 'DisplayPort'
+ }
+ dt_table = {
+ 0: 'RGB 4:4:4',
+ 1: 'RGB 4:4:4 + YCrCb 4:4:4',
+ 2: 'RGB 4:4:4 + YCrCb 4:2:2',
+ 3: 'RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2'
+ }
+ bpp = (basic_params[0] >> 4) & 7
+ intf = (basic_params[0] >> 0) & 7
+
+ text.append((WFDSource.indent + 'Video input type', 'digital'))
+ text.append((WFDSource.indent + 'Bit depth', 'undefined' if bpp in [0, 7] else str(4 + bpp * 2)))
+ text.append((WFDSource.indent + 'Interface', 'undefined' if intf not in intf_table else intf_table[intf]))
+ else:
+ level_table = {
+ 0: '+0.7 / -0.3 V',
+ 1: '+0.714 / -0.286 V',
+ 2: '+1.0 / -0.4 V',
+ 3: '+0.7 / 0 V'
+ }
+ dt_table = {
+ 0: 'monochrome/grayscale',
+ 1: 'RGB color',
+ 2: 'non-RGB color',
+ 3: 'undefined'
+ }
+ text.append((WFDSource.indent + 'Video input type', 'analog'))
+ text.append((WFDSource.indent + 'Video white/sync level', level_table[(basic_parmas[0] >> 5) & 3]))
+
+ if basic_params[1] and basic_params[2]:
+ text.append((WFDSource.indent + 'Screen width', str(basic_params[1]) + ' cm'))
+ text.append((WFDSource.indent + 'Screen height', str(basic_params[2]) + ' cm'))
+ elif basic_params[2] == 0:
+ text.append((WFDSource.indent + 'Landscape aspect ratio', str((basic_params[1] + 99) * 0.01)))
+ else:
+ text.append((WFDSource.indent + 'Portrait aspect ratio', str(100.0 / (basic_params[2] + 99))))
+
+ text.append((WFDSource.indent + 'Gamma', str((basic_params[3] + 100) * 0.01)))
+ text.append((WFDSource.indent + 'DPMS Standby', 'supported' if (basic_params[4] >> 7) & 1 else 'unsupported'))
+ text.append((WFDSource.indent + 'DPMS Suspend', 'supported' if (basic_params[4] >> 6) & 1 else 'unsupported'))
+ text.append((WFDSource.indent + 'DPMS Active-off', 'supported' if (basic_params[4] >> 5) & 1 else 'unsupported'))
+ text.append((WFDSource.indent + 'Color type', dt_table[(basic_params[4] >> 3) & 3]))
+ text.append((WFDSource.indent + 'sRGB color space', 'yes' if (basic_params[4] >> 2) & 1 else 'no'))
+ text.append((WFDSource.indent + 'Continuous timings', 'yes' if (basic_params[4] >> 0) & 1 else 'no'))
+
+ # TODO: timing information and extensions
+ return text
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
Gst.init(None)
WFDSource()
--
2.25.1
1 year, 11 months
[PATCH 01/13] frame-xchg: Fix potential use after free
by Andrew Zaborowski
Check if the frame callback has cancelled the frame_xchg before
attempting to free it.
---
I'm adding a frame_xchg_match_ptr here and I think I might have
used a similar function somewhere else. I was wondering if we
want to add a pointer compare function in util.c or accept NULL
as match function in l_queue_find() to mean match by data pointer.
---
src/frame-xchg.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/frame-xchg.c b/src/frame-xchg.c
index 3c1fc279..dfe08f6a 100644
--- a/src/frame-xchg.c
+++ b/src/frame-xchg.c
@@ -975,6 +975,11 @@ static bool frame_xchg_tx_retry(struct wiphy_radio_work_item *item)
return false;
}
+static bool frame_xchg_match_ptr(const void *a, const void *b)
+{
+ return a == b;
+}
+
static bool frame_xchg_resp_handle(const struct mmpdu_header *mpdu,
const void *body, size_t body_len,
int rssi, void *user_data)
@@ -1013,6 +1018,9 @@ static bool frame_xchg_resp_handle(const struct mmpdu_header *mpdu,
done = watch->cb(mpdu, body, body_len, rssi, fx->user_data);
+ if (!l_queue_find(frame_xchgs, frame_xchg_match_ptr, fx))
+ return true;
+
if (done) {
/* NULL callback here since the caller is done */
fx->cb = NULL;
--
2.25.1
1 year, 11 months
[PATCH 1/3] wfd-source: Display some stream properties
by Andrew Zaborowski
Define a bunch of stream parameters each with a getter and an optional
setter. In the right pane of the window show widgets for these
properties, some as just labels and some as editable controls depending
on the type of the property. Parse the EDID data.
---
test/wfd-source | 363 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 328 insertions(+), 35 deletions(-)
diff --git a/test/wfd-source b/test/wfd-source
index 09b47a27..fad5f16e 100755
--- a/test/wfd-source
+++ b/test/wfd-source
@@ -16,6 +16,7 @@ import collections.abc
import random
import dataclasses
import traceback
+import codecs
import gi
gi.require_version('GLib', '2.0')
@@ -27,7 +28,9 @@ class WFDRTSPServer:
class RTSPException(Exception):
pass
- def __init__(self, port, state_handler, error_handler):
+ Prop = collections.namedtuple('Prop', ['name', 'desc', 'getter', 'setter', 'type', 'vals'])
+
+ def __init__(self, port, state_handler, error_handler, init_values, prop_handler):
# Should start the TCP server only on the P2P connection's local IP but we won't
# know the IP or interface name until after the connection is established. At that
# time the sink may try to make the TCP connection at any time so our listen
@@ -44,7 +47,8 @@ class WFDRTSPServer:
self.state_handler = state_handler
self.error_handler = error_handler
- self.sm_init()
+ self.prop_handler = prop_handler
+ self.sm_init(init_values)
def handle_data_out(self, conn, *args):
try:
@@ -200,7 +204,11 @@ class WFDRTSPServer:
def ready(self):
return self._state in ['streaming', 'paused']
- def sm_init(self):
+ @property
+ def props(self):
+ return self._props
+
+ def sm_init(self, init_values):
self._state = 'waiting-rtsp'
self.local_params = {
'wfd_video_formats': '00 00 01 08 00000000 00000000 00000040 00 0000 0000 00 none none'
@@ -226,6 +234,33 @@ class WFDRTSPServer:
self.rtsp_keepalive_timeout = None
self.expected_remote_ip = None
self.remote_ip = None
+ self.init_width = init_values['width']
+ self.init_height = init_values['height']
+ self.rtcp_enabled = init_values['rtcp_enabled']
+
+ self._props = []
+
+ @staticmethod
+ def get_init_props():
+ props = []
+ values = {
+ 'width': 800,
+ 'height': 600,
+ 'rtcp_enabled': True
+ }
+
+ def set_val(key, val):
+ values[key] = val
+ props.append(WFDRTSPServer.Prop('Output width', 'Scale the video stream to this X resolution for sending',
+ lambda: values['width'], lambda x: set_val('width', x), int, (640, 1920)))
+ props.append(WFDRTSPServer.Prop('Output height', 'Scale the video stream to this Y resolution for sending',
+ lambda: values['height'], lambda x: set_val('height', x), int, (480, 1080)))
+ props.append(WFDRTSPServer.Prop('Enable RTCP', 'Use RTCP if the Sink requests it during setup',
+ lambda: values['rtcp_enabled'], lambda x: set_val('rtcp_enabled', x), bool, None))
+ # TODO: Enable Audio
+ # TODO: Audio source
+
+ return props, values
def close(self):
# Avoid passing self to io watches so that the refcount can ever reach 0 and
@@ -431,6 +466,94 @@ class WFDRTSPServer:
self.error('Optional RTCP port not valid in SETUP Transport header: ' + str(rtcp_port))
self.remote_rtcp_port = rtcp_port
+ self._props.append(WFDRTSPServer.Prop('RTP transport', '', lambda: 'TCP' if self.use_tcp else 'UDP', None, str, None))
+ self._props.append(WFDRTSPServer.Prop('Remote RTP port', '', lambda: self.remote_rtp_port, None, int, None))
+ self._props.append(WFDRTSPServer.Prop('Remote RTCP port', '', lambda: self.remote_rtcp_port, None, int, None))
+
+ def parse_display_edid(self):
+ try:
+ len_str, hex_str = self.remote_params['wfd_display_edid'].split(' ', 1)
+ if len(len_str.strip()) != 4:
+ raise Exception('edid-block-count length is not 4 hex digits')
+ blocks = int(len_str, 16)
+ edid = codecs.decode(hex_str.strip(), 'hex')
+ if blocks < 1 or blocks > 256 or blocks * 128 != len(edid):
+ raise Exception('edid-block-count value wrong')
+ except:
+ edid = None
+
+ self._props.append(WFDRTSPServer.Prop('EDID info', 'Remote display\'s EDID data', lambda: edid, None, bytes, None))
+
+ def create_running_props(self):
+ src = self.rtp_pipeline.get_by_name('src')
+ fps = self.rtp_pipeline.get_by_name('fps')
+ enc = self.rtp_pipeline.get_by_name('videnc')
+ res = self.rtp_pipeline.get_by_name('res')
+ sink = self.rtp_pipeline.get_by_name('sink')
+ self.pipeline_props = []
+
+ srcpadcaps = src.srcpads[0].get_allowed_caps()
+ width = srcpadcaps[0]['width']
+ height = srcpadcaps[0]['height']
+ props = []
+ props.append(WFDRTSPServer.Prop('Local width', 'Local screen X resolution', lambda: width, None, int, None))
+ props.append(WFDRTSPServer.Prop('Local height', 'Local screen Y resolution', lambda: height, None, int, None))
+
+ def set_use_damage(val):
+ src.props.use_damage = val
+ props.append(WFDRTSPServer.Prop('Use XDamage', 'Try to use XDamage to reduce bandwidth usage',
+ lambda: src.props.use_damage, set_use_damage, bool, None))
+
+ src.props.endx = width
+ src.props.endy = height
+ def set_startx(val):
+ src.set_property('startx', min(val, src.props.endx - 1))
+ def set_starty(val):
+ src.set_property('starty', min(val, src.props.endy - 1))
+ def set_endx(val):
+ src.set_property('endx', max(val, src.props.startx + 1))
+ def set_endy(val):
+ src.set_property('endy', max(val, src.props.starty + 1))
+ props.append(WFDRTSPServer.Prop('Window min X', 'Skip this many pixels on the left side of the local screen',
+ lambda: src.props.startx, set_startx, int, (0, width - 1)))
+ props.append(WFDRTSPServer.Prop('Window min Y', 'Skip this many pixels on the top of the local screen',
+ lambda: src.props.starty, set_starty, int, (0, height - 1)))
+ props.append(WFDRTSPServer.Prop('Window max X', 'Send screen contents only up to this X coordinate',
+ lambda: src.props.endx, set_endx, int, (1, width)))
+ props.append(WFDRTSPServer.Prop('Window max Y', 'Send screen contents only up to this Y coordinate',
+ lambda: src.props.endy, set_endy, int, (1, height)))
+
+ def set_framerate(val):
+ fps.props.caps[0]['framerate'] = Gst.Fraction(val)
+ def set_width(val):
+ res.props.caps[0]['width'] = val
+ def set_height(val):
+ res.props.caps[0]['height'] = val
+ props.append(WFDRTSPServer.Prop('Framerate', 'Try to output this many frames per second',
+ lambda: int(fps.props.caps[0]['framerate'].num), set_framerate, int, (1, 30)))
+ props.append(WFDRTSPServer.Prop('Output width', 'Scale the video stream to this X resolution for sending',
+ lambda: res.props.caps[0]['width'], set_width, int, (640, 1920)))
+ props.append(WFDRTSPServer.Prop('Output height', 'Scale the video stream to this Y resolution for sending',
+ lambda: res.props.caps[0]['height'], set_height, int, (480, 1080)))
+
+ preset_values = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast', 'placebo']
+ preset_map = {'veryslow': 9, 'slower': 8, 'slow': 7, 'medium': 6, 'fast': 5, 'faster': 4, 'veryfast': 3, 'superfast': 2, 'ultrafast': 1, 'placebo': 10}
+
+ def set_speed_preset(val):
+ enc.props.speed_preset = preset_map[val]
+ props.append(WFDRTSPServer.Prop('H.264 speed preset', 'Speed/quality setting of the H.264 encoder to optimise bandwidth/latency',
+ lambda: enc.props.speed_preset.value_nick, set_speed_preset, str, preset_values))
+
+ def set_max_lateness(val):
+ if val <= 0:
+ sink.props.max_lateness = -1
+ else:
+ sink.props.max_lateness = val * 1000000 # milliseconds to nanoseconds
+ props.append(WFDRTSPServer.Prop('Max lateness', 'Maximum number of milliseconds that a buffer can be late before it is dropped, or 0 for unlimited',
+ lambda: 0 if sink.props.max_lateness == -1 else sink.props.max_lateness / 1000000, set_max_lateness, int, (-1, 3000)))
+
+ return props
+
def on_gst_message(self, bus, message):
t = message.type
if t == Gst.MessageType.EOS:
@@ -438,6 +561,8 @@ class WFDRTSPServer:
elif t == Gst.MessageType.STATE_CHANGED:
old, new, pending = message.parse_state_changed()
self.debug('Gstreamer state change for ' + message.src.name + ' from ' + str(old) + ' to ' + str(new) + ', pending=' + str(pending))
+ if message.src == self.rtp_pipeline:
+ self.prop_handler()
elif t == Gst.MessageType.INFO:
err, debug = message.parse_info()
self.debug('Gstreamer info for ' + message.src.name + ': ' + str(err) + '\nDebug: ' + str(debug))
@@ -511,7 +636,8 @@ class WFDRTSPServer:
# Send M2 response
self.response(public=self.local_methods)
# Send M3
- self.request('GET_PARAMETER', 'rtsp://localhost/wfd1.0', params=['wfd_audio_codecs', 'wfd_video_formats', 'wfd_client_rtp_ports', 'wfd_display_edid', 'wfd_uibc_capability'])
+ params = ['wfd_audio_codecs', 'wfd_video_formats', 'wfd_client_rtp_ports', 'wfd_display_edid', 'wfd_uibc_capability']
+ self.request('GET_PARAMETER', 'rtsp://localhost/wfd1.0', params=params)
self.enter_state('M3')
elif self._state == 'M3':
# Validate M3 response
@@ -520,6 +646,8 @@ class WFDRTSPServer:
self.error('Required parameters missing from GET_PARAMETER response')
self.parse_video_formats(self.remote_params['wfd_video_formats'])
self.parse_client_rtp_ports(self.remote_params['wfd_client_rtp_ports'])
+ self.parse_display_edid()
+ self.prop_handler()
# Send M4
params = {
'wfd_video_formats': self.local_params['wfd_video_formats'],
@@ -547,7 +675,7 @@ class WFDRTSPServer:
self.session_stream_url = target
self.session_id = str(random.randint(a=1, b=999999))
self.local_rtp_port = random.randint(a=20000, b=30000)
- if self.remote_rtcp_port is not None:
+ if self.remote_rtcp_port is not None and self.rtcp_enabled:
self.local_rtcp_port = self.local_rtp_port + 1
profile ='RTP/AVP/TCP;unicast' if self.use_tcp else 'RTP/AVP/UDP;unicast'
client_port = str(self.remote_rtp_port) + (('-' + str(self.remote_rtcp_port)) if self.remote_rtcp_port is not None else '')
@@ -555,22 +683,26 @@ class WFDRTSPServer:
transport = profile + ';client_port' + client_port + ';server_port=' + server_port
# Section B.1
pipeline = ('ximagesrc name=src use-damage=false do-timestamp=true ! capsfilter name=fps caps=video/x-raw,framerate=10/1' +
- ' ! videoscale method=0 ! capsfilter name=res caps=video/x-raw,width=800,height=600' +
+ ' ! videoscale method=0 ! capsfilter name=res caps=video/x-raw,width=' + str(self.init_width) + ',height=' + str(self.init_height) +
' ! videoconvert ! video/x-raw,format=I420 ! x264enc tune=zerolatency speed-preset=ultrafast name=videnc' +
' ! queue' + # TODO: add leaky=downstream
' ! mpegtsmux name=mux' +
' ! rtpmp2tpay pt=33 mtu=1472 ! .send_rtp_sink rtpsession name=session .send_rtp_src' +
- ' ! udpsink host=' + self.remote_ip + ' port=' + str(self.remote_rtp_port) + ' bind-port=' + str(self.local_rtp_port)) # TODO: bind-address
-
+ ' ! udpsink name=sink host=' + self.remote_ip + ' port=' + str(self.remote_rtp_port) + ' bind-port=' + str(self.local_rtp_port)) # TODO: bind-address
if self.local_rtcp_port is not None:
pipeline += ' session.send_rtcp_src ! udpsink name=rtcp_sink host=' + self.remote_ip + \
' port=' + str(self.remote_rtcp_port) + ' bind-port=' + str(self.local_rtcp_port) # TODO: bind-address
+ self._props.append(WFDRTSPServer.Prop('RTCP enabled', 'Whether we\'re currently sending RTCP data',
+ lambda: self.local_rtcp_port is not None, None, bool, None))
self.rtp_pipeline = Gst.parse_launch(pipeline)
bus = self.rtp_pipeline.get_bus()
bus.enable_sync_message_emission()
bus.add_signal_watch()
- bus.connect('sync-message', self.on_gst_message)
+ bus.connect('message', self.on_gst_message)
+
+ self._props += self.create_running_props()
+ self.prop_handler()
# Send M6 response
self.response(session=self.session_id + ';timeout=' + str(self.session_timeout), transport=transport)
@@ -653,12 +785,16 @@ class WFDSource(Gtk.Window):
self.device_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
leftscroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER)
leftscroll.add(self.device_box)
- self.infolabel1 = Gtk.Label()
- self.infolabel1.set_ellipsize(Pango.EllipsizeMode.START)
- infopane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- infopane.pack_start(self.infolabel1, False, False, padding=10)
- rightscroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER, vscrollbar_policy=Gtk.PolicyType.NEVER)
- rightscroll.add(infopane)
+ self.infopane = Gtk.FlowBox(orientation=Gtk.Orientation.VERTICAL)
+ self.infopane.set_selection_mode(Gtk.SelectionMode.NONE)
+ self.infopane.set_max_children_per_line(20)
+ self.infopane.set_min_children_per_line(3)
+ self.infopane.set_column_spacing(20)
+ self.infopane.set_row_spacing(5)
+ self.infopane.set_valign(Gtk.Align.START)
+ self.infopane.set_halign(Gtk.Align.START)
+ rightscroll = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
+ rightscroll.add(self.infopane)
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
paned.pack1(leftscroll, True, True)
paned.pack2(rightscroll, False, False)
@@ -669,6 +805,8 @@ class WFDSource(Gtk.Window):
self.show_all()
self.connect('notify::is-active', self.on_notify_is_active)
+ self.rtsp_props = None
+ self.rtsp_init_values = {}
self.rtsp_port = 7236
self.devices = None
self.objects = {}
@@ -676,6 +814,8 @@ class WFDSource(Gtk.Window):
self.dbus.watch_name_owner('net.connman.iwd', self.on_name_owner_change)
self.on_name_owner_change('dummy' if self.dbus.name_has_owner('net.connman.iwd') else '')
+ self.indent = '\xbb '
+
def on_name_owner_change(self, new_name):
if not new_name:
if self.devices is None:
@@ -1001,11 +1141,13 @@ class WFDSource(Gtk.Window):
peer_list.insert(peer.widget, index)
peer.widget.show_all()
elif (PEER_IF not in props or WFD_IF not in props or WSC_IF not in props or not props[WFD_IF]['Sink']) and peer.widget:
- del device.sorted_peers[peer.widget.get_index()]
- peer_list.remove(peer.widget)
+ tmp = peer.widget
+ peer.widget = None
+ del device.sorted_peers[tmp.get_index()]
+ peer_list.remove(tmp)
if peer == device.selected_peer:
device.selected_peer = None
- self.update_info(dev_path, None)
+ self.update_info_pane(dev_path, None)
if peer == device.connecting_peer:
device.dbus_call.cancel()
device.connecting_peer = None
@@ -1020,7 +1162,6 @@ class WFDSource(Gtk.Window):
peer.peer_proxy = None
peer.wfd_proxy = None
peer.wsc_proxy = None
- peer.widget = None
if peer.rtsp:
peer.rtsp.close()
peer.rtsp = None
@@ -1055,7 +1196,7 @@ class WFDSource(Gtk.Window):
button.hide()
if peer == device.selected_peer:
- self.update_info(dev_path, path)
+ self.update_info_pane(dev_path, path)
def update_selected_peer(self, dev_path):
device = self.devices[dev_path]
@@ -1063,12 +1204,74 @@ class WFDSource(Gtk.Window):
sel_path = self.get_peer_path(device, device.selected_peer)
self.update_peer_props(dev_path, sel_path)
- def update_info(self, dev_path, path):
- device = self.devices[dev_path]
+ def add_info(self, name, desc, valuewidget):
+ namelabel = Gtk.Label(label=name + ':', xalign=0)
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ box.pack_start(namelabel, expand=False, fill=False, padding=3)
+ if valuewidget:
+ box.pack_end(valuewidget, expand=False, fill=False, padding=3)
+ if desc:
+ box.set_tooltip_text(desc)
+ self.infopane.add(box)
+
+ def add_info_str(self, name, value):
+ vlabel = Gtk.Label(xalign=0)
+ vlabel.set_markup('<span weight="bold">' + value + '</span>')
+ self.add_info(name, None, vlabel)
+
+ def add_info_prop(self, prop):
+ val = prop.getter()
+ if prop.setter is None:
+ if val is None:
+ return
+ if prop.type == bool:
+ vals = prop.vals if prop.vals is not None else ['no', 'yes']
+ text = vals[val]
+ elif prop.name == 'EDID info':
+ text = WFDSource.edid_to_text(val)
+ if isinstance(text, collections.abc.Sequence):
+ self.add_info(prop.name, prop.desc, None)
+ for name, val in text:
+ if val:
+ v = Gtk.Label(xalign=0)
+ v.set_markup('<span weight="bold">' + str(val) + '</span>')
+ else:
+ v = None
+ self.add_info(self.indent + name, prop.desc, v)
+ return
+ else:
+ text = str(val)
+ v = Gtk.Label(xalign=0)
+ v.set_markup('<span weight="bold">' + text + '</span>')
+ elif val is None:
+ return
+ elif prop.type == bool:
+ v = Gtk.Switch()
+ v.set_active(val)
+ v.connect('state-set', lambda switch, state: prop.setter(state))
+ elif prop.type == int:
+ v = Gtk.SpinButton.new_with_range(min=prop.vals[0], max=prop.vals[1], step=prop.vals[2] if len(prop.vals) > 2 else 1)
+ v.set_value(val)
+ v.connect('value-changed', lambda sb: prop.setter(int(sb.get_value())))
+ elif prop.type == str:
+ if prop.vals:
+ v = Gtk.ComboBoxText()
+ for option in prop.vals:
+ v.append(option, option)
+ v.set_active_id(val)
+ v.connect('changed', lambda entry: prop.setter(entry.get_active_text()))
+ else:
+ v = Gtk.Entry(text=val)
+ v.connect('changed', lambda entry: prop.setter(entry.get_text()))
+ self.add_info(prop.name, prop.desc, v)
+
+ def update_info_pane(self, dev_path, path):
+ self.infopane.foreach(lambda x, y: self.infopane.remove(x), None)
+
if path is None:
- self.infolabel1.set_text('')
return
+ device = self.devices[dev_path]
peer = device.peers[path]
if peer == device.connecting_peer:
@@ -1085,14 +1288,13 @@ class WFDSource(Gtk.Window):
state = 'connected'
else:
state = 'not connected'
+ self.add_info_str('Connection state', state)
subcat = 'unknown'
if 'DeviceSubcategory' in self.objects[path][PEER_IF]:
subcat = self.objects[path][PEER_IF]['DeviceSubcategory']
-
- text = ('Connection state: ' + state + '\n' +
- 'Device category: ' + self.objects[path][PEER_IF]['DeviceCategory'] + '\n'
- 'Device subcategory: ' + subcat + '\n')
+ self.add_info_str('Peer category', self.objects[path][PEER_IF]['DeviceCategory'])
+ self.add_info_str('Peer subcategory', subcat)
if WFD_IF in self.objects[path]:
if self.objects[path][WFD_IF]['Source']:
@@ -1102,17 +1304,27 @@ class WFDSource(Gtk.Window):
t = 'source'
else:
t = 'sink'
- text += 'WFD device type: ' + t + '\n'
+ self.add_info_str('Peer WFD type', t)
if self.objects[path][WFD_IF]['Sink']:
- text += 'Audio: ' + ('yes' if self.objects[path][WFD_IF]['HasAudio'] else 'no') + '\n'
+ self.add_info_str('Peer audio support', 'yes' if self.objects[path][WFD_IF]['HasAudio'] else 'no')
+
+ self.add_info_str('Peer UIBC support', 'yes' if self.objects[path][WFD_IF]['HasUIBC'] else 'no')
+
+ self.add_info_str('Peer content protection', 'yes' if self.objects[path][WFD_IF]['HasContentProtection'] else 'no')
+
+ if self.rtsp_props is None:
+ self.rtsp_props, self.rtsp_init_values = WFDRTSPServer.get_init_props()
- text += 'UIBC: ' + ('yes' if self.objects[path][WFD_IF]['HasUIBC'] else 'no') + '\n'
+ if peer.rtsp is not None:
+ props = peer.rtsp.props
+ else:
+ props = self.rtsp_props
- text += 'Content protection: ' + ('yes' if self.objects[path][WFD_IF]['HasContentProtection'] else 'no') + '\n'
+ for prop in props:
+ self.add_info_prop(prop)
- self.infolabel1.set_text(text)
- # TODO: more info in labels 2 and so on
+ self.infopane.show_all()
# Direct method calls on dbus.Interface's don't return dbus.lowlevel.PendingCall objects so
# we have to use bus.call_async to make cancellable async calls
@@ -1172,12 +1384,17 @@ class WFDSource(Gtk.Window):
dialog.connect('response', on_ok)
+ def on_rtsp_props_changed():
+ # Should also check if the infopane is currently showing a selected peer on another device...
+ if peer == device.selected_peer:
+ self.update_info_pane(dev_path, path)
+
# Cannot use peer.wsc_proxy.PushButton()
device.dbus_call = self.async_call(peer.wsc_proxy, 'PushButton', reply_handler=on_reply, error_handler=on_error, timeout=120)
device.connecting_peer = peer
# Create the RTSP server now so it's ready as soon as the P2P connection succeeds even if
# we haven't received the DBus reply yet
- peer.rtsp = WFDRTSPServer(self.rtsp_port, on_rtsp_state, on_rtsp_error)
+ peer.rtsp = WFDRTSPServer(self.rtsp_port, on_rtsp_state, on_rtsp_error, self.rtsp_init_values, on_rtsp_props_changed)
self.update_dev_props(dev_path)
self.update_peer_props(dev_path, path)
if peer != device.selected_peer:
@@ -1272,7 +1489,7 @@ class WFDSource(Gtk.Window):
path = self.get_peer_path(device, device.selected_peer)
device.selected_peer = None
self.update_peer_props(dev_path, path)
- self.update_info(dev_path, None)
+ self.update_info_pane(dev_path, None)
if row is None:
return True
@@ -1335,6 +1552,82 @@ class WFDSource(Gtk.Window):
mainloop.quit()
return False
+ @staticmethod
+ def edid_to_text(edid):
+ if edid is None:
+ return 'unavailable'
+ if len(edid) < 128:
+ return 'invalid (too short)'
+ if edid[0:8] != b'\0\xff\xff\xff\xff\xff\xff\0':
+ return 'invalid (bad magic)'
+ if sum(edid[0:128]) & 255 != 0:
+ return 'invalid (bad checksum)'
+
+ header = edid[0:20]
+ manf_id = (header[8] << 8) + header[9]
+ text = [('Header', '')]
+ text.append((self.indent + 'Version', str(header[18]) + '.' + str(header[19])))
+ text.append((self.indent + 'Manufacturer ID', chr(64 + ((manf_id >> 10) & 31)) + chr(64 + ((manf_id >> 5) & 31)) + chr(64 + ((manf_id >> 0) & 31))))
+ text.append((self.indent + 'Product code', hex((header[11] << 8) + header[10])))
+ text.append((self.indent + 'Serial', hex((header[15] << 24) +(header[14] << 16) + (header[13] << 8) + header[12])))
+ text.append((self.indent + 'Manufactured', str(1990 + header[17]) + ' week ' + str(header[16])))
+
+ basic_params = edid[20:25]
+ text.append(('Basic parameters', ''))
+ if basic_params[0] & 0x80:
+ intf_table = {
+ 2: 'HDMIa',
+ 3: 'HDMIb',
+ 4: 'MDDI',
+ 5: 'DisplayPort'
+ }
+ dt_table = {
+ 0: 'RGB 4:4:4',
+ 1: 'RGB 4:4:4 + YCrCb 4:4:4',
+ 2: 'RGB 4:4:4 + YCrCb 4:2:2',
+ 3: 'RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2'
+ }
+ bpp = (basic_params[0] >> 4) & 7
+ intf = (basic_params[0] >> 0) & 7
+
+ text.append((self.indent + 'Video input type', 'digital'))
+ text.append((self.indent + 'Bit depth', 'undefined' if bpp in [0, 7] else str(4 + bpp * 2)))
+ text.append((self.indent + 'Interface', 'undefined' if intf not in intf_table else intf_table[intf]))
+ else:
+ level_table = {
+ 0: '+0.7 / -0.3 V',
+ 1: '+0.714 / -0.286 V',
+ 2: '+1.0 / -0.4 V',
+ 3: '+0.7 / 0 V'
+ }
+ dt_table = {
+ 0: 'monochrome/grayscale',
+ 1: 'RGB color',
+ 2: 'non-RGB color',
+ 3: 'undefined'
+ }
+ text.append((self.indent + 'Video input type', 'analog'))
+ text.append((self.indent + 'Video white/sync level', level_table[(basic_parmas[0] >> 5) & 3]))
+
+ if basic_params[1] and basic_params[2]:
+ text.append((self.indent + 'Screen width', str(basic_params[1]) + ' cm'))
+ text.append((self.indent + 'Screen height', str(basic_params[2]) + ' cm'))
+ elif basic_params[2] == 0:
+ text.append((self.indent + 'Landscape aspect ratio', str((basic_params[1] + 99) * 0.01)))
+ else:
+ text.append((self.indent + 'Portrait aspect ratio', str(100.0 / (basic_params[2] + 99))))
+
+ text.append((self.indent + 'Gamma', str((basic_params[3] + 100) * 0.01)))
+ text.append((self.indent + 'DPMS Standby', 'supported' if (basic_params[4] >> 7) & 1 else 'unsupported'))
+ text.append((self.indent + 'DPMS Suspend', 'supported' if (basic_params[4] >> 6) & 1 else 'unsupported'))
+ text.append((self.indent + 'DPMS Active-off', 'supported' if (basic_params[4] >> 5) & 1 else 'unsupported'))
+ text.append((self.indent + 'Color type', dt_table[(basic_params[4] >> 3) & 3]))
+ text.append((self.indent + 'sRGB color space', 'yes' if (basic_params[4] >> 2) & 1 else 'no'))
+ text.append((self.indent + 'Continuous timings', 'yes' if (basic_params[4] >> 0) & 1 else 'no'))
+
+ # TODO: timing information and extensions
+ return text
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
Gst.init(None)
WFDSource()
--
2.25.1
1 year, 11 months
[PATCH] Fix a side channel leak on the password in SAE
by Daniel DE ALMEIDA BRAGA
Use a constant control flow in the derivation loop, avoiding leakage
in the iteration succesfuly converting the password.
Increase number of iterations (20 to 30) to avoid passwords needing
more iterations.
---
src/sae.c | 135 +++++++++++++++++++++++++++++++++++++----------------
src/util.h | 31 ++++++++++++
2 files changed, 125 insertions(+), 41 deletions(-)
diff --git a/src/sae.c b/src/sae.c
index 97e3348e..54247c92 100644
--- a/src/sae.c
+++ b/src/sae.c
@@ -97,23 +97,47 @@ static bool sae_pwd_seed(const uint8_t *addr1, const uint8_t *addr2,
&counter, (size_t) 1);
}
+/*
+ * Computes KDF-256(pwd_seed, "SAE Hunting and Pecking", p). If the output is
+ * greater than p, the output is set to qnr, a quadratic non-residue.
+ * Since this happens with very low probability, using the same qnr is fine.
+ */
static struct l_ecc_scalar *sae_pwd_value(const struct l_ecc_curve *curve,
- uint8_t *pwd_seed)
+ uint8_t *pwd_seed, uint8_t *qnr)
{
uint8_t pwd_value[L_ECC_SCALAR_MAX_BYTES];
uint8_t prime[L_ECC_SCALAR_MAX_BYTES];
ssize_t len;
+ uint8_t is_in_range;
struct l_ecc_scalar *p = l_ecc_curve_get_prime(curve);
len = l_ecc_scalar_get_data(p, prime, sizeof(prime));
-
l_ecc_scalar_free(p);
if (!kdf_sha256(pwd_seed, 32, "SAE Hunting and Pecking",
- strlen("SAE Hunting and Pecking"), prime, len,
- pwd_value, len))
+ strlen("SAE Hunting and Pecking"), prime, len,
+ pwd_value, len))
return NULL;
+ /*
+ * If pwd_value >= prime, this iteration should fail. We need a smooth
+ * control flow, so we need to continue anyway.
+ */
+ is_in_range = l_secure_memcmp(pwd_value, prime, len);
+ /*
+ * We only consider is_in_range == -1 as valid, meaning the value of the
+ * MSB defines the mask.
+ */
+ is_in_range = util_secure_fill_with_msb(is_in_range);
+
+ /*
+ * libell has public Legendre symbol only for l_ecc_scalar, but they cannot
+ * be created if the coordinate is greater than the p. Hence, to avoid
+ * control flow dependencies, we replace pwd_value by a dummy quadratic
+ * non residue if we generate a value >= prime.
+ */
+ util_secure_select(is_in_range, pwd_value, qnr, pwd_value, sizeof(pwd_value));
+
return l_ecc_scalar_new(curve, pwd_value, sizeof(pwd_value));
}
@@ -190,7 +214,7 @@ static struct l_ecc_scalar *sae_new_residue(const struct l_ecc_curve *curve,
return s;
}
-static bool sae_is_quadradic_residue(const struct l_ecc_curve *curve,
+static uint8_t sae_is_quadradic_residue(const struct l_ecc_curve *curve,
struct l_ecc_scalar *value,
struct l_ecc_scalar *qr,
struct l_ecc_scalar *qnr)
@@ -213,7 +237,7 @@ static bool sae_is_quadradic_residue(const struct l_ecc_curve *curve,
if (bytes <= 0) {
l_ecc_scalar_free(num);
- return false;
+ return 0;
}
if (rbuf[bytes / 8 - 1] & 1) {
@@ -221,20 +245,20 @@ static bool sae_is_quadradic_residue(const struct l_ecc_curve *curve,
if (l_ecc_scalar_legendre(num) == -1) {
l_ecc_scalar_free(num);
- return true;
+ return 1;
}
} else {
l_ecc_scalar_multiply(num, num, qnr);
if (l_ecc_scalar_legendre(num) == 1) {
l_ecc_scalar_free(num);
- return true;
+ return 1;
}
}
l_ecc_scalar_free(num);
- return false;
+ return 0;
}
/*
@@ -244,66 +268,95 @@ static bool sae_is_quadradic_residue(const struct l_ecc_curve *curve,
static bool sae_compute_pwe(struct sae_sm *sm, char *password,
const uint8_t *addr1, const uint8_t *addr2)
{
- bool found = false;
+ uint8_t found = 0;
+ uint8_t is_residue;
+ uint8_t is_odd = 0;
uint8_t counter;
uint8_t pwd_seed[32];
+ uint8_t x[L_ECC_SCALAR_MAX_BYTES];
+ uint8_t x_cand[L_ECC_SCALAR_MAX_BYTES];
struct l_ecc_scalar *pwd_value;
- uint8_t random[32];
- uint8_t *base = (uint8_t *) password;
- size_t base_len = strlen(password);
- uint8_t save[32] = { 0 };
+ uint8_t *dummy;
+ uint8_t *base;
+ size_t base_len;
struct l_ecc_scalar *qr;
struct l_ecc_scalar *qnr;
- uint8_t x[L_ECC_SCALAR_MAX_BYTES];
+ uint8_t qnr_bin[L_ECC_SCALAR_MAX_BYTES] = {0};
/* create qr/qnr prior to beginning hunting-and-pecking loop */
qr = sae_new_residue(sm->curve, true);
qnr = sae_new_residue(sm->curve, false);
+ l_ecc_scalar_get_data(qnr, qnr_bin, sizeof(qnr_bin));
+
+ /*
+ * Allocate memory for the base, and set a random dummy to be used in
+ * additional iterations, once a valid value is found
+ */
+ base_len = strlen(password);
+ base = l_malloc(base_len * sizeof(*base));
+ dummy = l_malloc(base_len * sizeof(*dummy));
+ l_getrandom(dummy, base_len);
- for (counter = 1; counter <= 20; counter++) {
- /* pwd-seed = H(max(addr1, addr2) || min(addr1, addr2),
- * base || counter)
+ /*
+ * Loop with constant time and memory access
+ * We do 30 iterations instead of the 40 recommended to achieve a
+ * resonnable security/complexity trade-off.
+ */
+ for (counter = 1; counter <= 30; counter++) {
+ /*
+ * Set base to either dummy or password, depending on found's value
+ * A non-secure version would be:
+ * base = (found ? dummy : password);
+ */
+ util_secure_select(found, dummy, (uint8_t *)password, base, base_len);
+
+ /*
+ * pwd-seed = H(max(addr1, addr2) || min(addr1, addr2),
+ * base || counter)
* pwd-value = KDF-256(pwd-seed, "SAE Hunting and Pecking", p)
*/
sae_pwd_seed(addr1, addr2, base, base_len, counter, pwd_seed);
+ /*
+ * The case pwd_value > prime is handled inside, so that execution
+ * can continue whatever the result is, without changing the outcome.
+ */
+ pwd_value = sae_pwd_value(sm->curve, pwd_seed, qnr_bin);
- pwd_value = sae_pwd_value(sm->curve, pwd_seed);
- if (!pwd_value)
- continue;
-
- if (sae_is_quadradic_residue(sm->curve, pwd_value, qr, qnr)) {
- if (found == false) {
- l_ecc_scalar_get_data(pwd_value, x, sizeof(x));
-
- memcpy(save, pwd_seed, 32);
+ /*
+ * Check if the candidate is a valid x-coordinate on our curve, and
+ * convert it from scalar to binary.
+ */
+ is_residue = sae_is_quadradic_residue(sm->curve, pwd_value, qr, qnr);
+ l_ecc_scalar_get_data(pwd_value, x_cand, sizeof(x_cand));
- l_getrandom(random, 32);
- base = random;
- base_len = 32;
+ /*
+ * If we already found the point, we overwrite x with itself.
+ * Otherwise, we copy the new candidate into x.
+ */
+ util_secure_select(found, x, x_cand, x, sizeof(x));
+ is_odd = util_secure_select_byte(found, is_odd, pwd_seed[31] & 0x01);
- found = true;
- }
- }
+ /*
+ * found is 0 or 0xff here and is_residue is 0 or 1. Bitwise OR of them
+ * (with is_residue converted to 0/0xff) handles this in constant time.
+ */
+ found |= is_residue * 0xff;
+ memset(pwd_seed, 0, sizeof(pwd_seed));
l_ecc_scalar_free(pwd_value);
}
l_ecc_scalar_free(qr);
l_ecc_scalar_free(qnr);
+ l_free(dummy);
+ l_free(base);
if (!found) {
l_error("max PWE iterations reached!");
return false;
}
+ sm->pwe = l_ecc_point_from_data(sm->curve, !is_odd + 2, x, sizeof(x));
- if (!(save[31] & 1))
- sm->pwe = l_ecc_point_from_data(sm->curve,
- L_ECC_POINT_TYPE_COMPRESSED_BIT1,
- x, sizeof(x));
- else
- sm->pwe = l_ecc_point_from_data(sm->curve,
- L_ECC_POINT_TYPE_COMPRESSED_BIT0,
- x, sizeof(x));
if (!sm->pwe) {
l_error("computing y failed, was x quadratic residue?");
diff --git a/src/util.h b/src/util.h
index edc6e777..dc0e77d0 100644
--- a/src/util.h
+++ b/src/util.h
@@ -71,4 +71,35 @@ static inline void util_set_bit(uint8_t *field, unsigned int bit)
field[bit / 8] = 1 << (bit % 8);
}
+/*
+ * Copy either true_value or false_value (depending if mask is 0xFF or 0x00
+ * respectively) into dest. All three buffers are assumed to be the same size.
+ * This constant time selection method allows to keep an identical memory access
+ * pattern.
+ */
+static inline uint8_t util_secure_select_byte(uint8_t mask, const uint8_t true_value,
+ const uint8_t false_value)
+{
+ return (mask & true_value) | (~mask & false_value);
+}
+
+/*
+ * Copy either true_value or false_value (depending if mask is 0xFF or 0x00
+ * respectively) into dest. All three buffers are assume to be the same size.
+ * This constant time selection method allows to choose and keeping an
+ * identical memory access pattern.
+ */
+static inline void util_secure_select(uint8_t mask, const uint8_t *true_value,
+ const uint8_t *false_value, uint8_t *dest, size_t size)
+{
+ for(int i = 0; i < size; i++)
+ dest[i] = util_secure_select_byte(mask, true_value[i], false_value[i]);
+}
+
+/* Create a value filled with the MSB of the input. */
+static inline uint8_t util_secure_fill_with_msb(uint8_t val)
+{
+ return (uint8_t ) (val >> (sizeof(val)*8 - 1)) * 0xFF;
+}
+
#endif /* __UTIL_H */
--
2.25.4
1 year, 11 months
[PATCH 1/4] frame-xchg: Don't call frame_xchg_destroy directly
by Andrew Zaborowski
frame_xchg_destroy is passed as the wiphy radio work's destroy callback
to wiphy.c. If it's also called directly in frame_xchg_exit, there's
going to be a use-after-free when it's called again from wiphy_exit, so
instead use wiphy_radio_work_done which will call frame_xchg_destroy and
forget the frame_xchg record.
---
src/frame-xchg.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/frame-xchg.c b/src/frame-xchg.c
index 1ba2d907..f07a4d8d 100644
--- a/src/frame-xchg.c
+++ b/src/frame-xchg.c
@@ -1337,7 +1337,7 @@ static void destroy_xchg_data(void *user_data)
{
struct frame_xchg_data *fx = user_data;
- frame_xchg_destroy(&fx->work);
+ wiphy_radio_work_done(wiphy_find_by_wdev(fx->wdev_id), fx->work.id);
}
static void frame_xchg_exit(void)
@@ -1359,3 +1359,4 @@ static void frame_xchg_exit(void)
}
IWD_MODULE(frame_xchg, frame_xchg_init, frame_xchg_exit);
+IWD_MODULE_DEPENDS(frame_xchg, wiphy)
--
2.25.1
1 year, 11 months
[Feature] Send hostname in DHCP request
by Roberto "Roobre" Santalla
Hello,
This is my first time submitting a patch/feature request over email, I
hope I do not mess up :)
First of all, thank you for your work on iwd, I have recently started
to use it on my laptop and seems to be working very well.
I have, however, noticed that when using the built-in IP management, my
hostname was not being sent to the DHCP server. This is unfortunate
since it breaks my home network, which uses dnsmasq to build dns
records on-the-fly for hosts.
I have written and extremely simple, PoC patch which calls gethostname
and pass the returned string to the ell dhcp client.
What are your thoughts about this feature?
Regards,
- ℝ
═════════════════════════════════════
👤 Roberto Santalla Fernández
─────────────────────────────────────
🔑 https://keybase.io/roobre/key.asc
🔑 0E19 86A3 593E 6226
─────────────────────────────────────
🌍 https://roobre.es
═════════════════════════════════════
1 year, 11 months
[PATCH 1/5] p2p: Implement the p2p.ServiceManager interface
by Andrew Zaborowski
The net.connman.iwd.p2p.ServiceManager interface on the /net/connman/iwd
object lets user applications register/unregister the Wi-Fi Display
service. In this commit all it does is it adds local WFD information
as given by the app, to the frames we send out during discovery.
Instead of accepting raw WFD IE contents from the app and exposing
peers' raw WFD IEs to the app, we build the WFD IEs in our code based on
the few meaningful DBus properties that we support and using default
values for the rest. If an app ever needs any of the other WFD
capabilities more properties can be added.
---
src/dbus.h | 2 +
src/p2p.c | 304 +++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 296 insertions(+), 10 deletions(-)
diff --git a/src/dbus.h b/src/dbus.h
index ebae85d4..eaad1f66 100644
--- a/src/dbus.h
+++ b/src/dbus.h
@@ -37,9 +37,11 @@
#define IWD_STATION_INTERFACE "net.connman.iwd.Station"
#define IWD_P2P_INTERFACE "net.connman.iwd.p2p.Device"
#define IWD_P2P_PEER_INTERFACE "net.connman.iwd.p2p.Peer"
+#define IWD_P2P_SERVICE_MANAGER_INTERFACE "net.connman.iwd.p2p.ServiceManager"
#define IWD_BASE_PATH "/net/connman/iwd"
#define IWD_AGENT_MANAGER_PATH IWD_BASE_PATH
+#define IWD_P2P_SERVICE_MANAGER_PATH IWD_BASE_PATH
struct l_dbus;
diff --git a/src/p2p.c b/src/p2p.c
index 002c75c0..2afceb4d 100644
--- a/src/p2p.c
+++ b/src/p2p.c
@@ -136,8 +136,22 @@ struct p2p_peer {
bool group;
};
+struct p2p_wfd_properties {
+ bool available;
+ bool source;
+ bool sink;
+ uint16_t port;
+ uint16_t throughput;
+ bool audio;
+ bool uibc;
+ bool cp;
+ bool r2;
+};
+
static struct l_queue *p2p_device_list;
static struct l_settings *p2p_dhcp_settings;
+static struct p2p_wfd_properties *p2p_own_wfd;
+static unsigned int p2p_wfd_disconnect_watch;
/*
* For now we only scan the common 2.4GHz channels, to be replaced with
@@ -237,6 +251,65 @@ static void p2p_peer_put(void *user_data)
static void p2p_device_discovery_start(struct p2p_device *dev);
static void p2p_device_discovery_stop(struct p2p_device *dev);
+/* Callers should reserve 32 bytes */
+static size_t p2p_build_wfd_ie(const struct p2p_wfd_properties *wfd,
+ uint8_t *buf)
+{
+ /*
+ * Wi-Fi Display Technical Specification v2.1.0
+ * Probe req: Section 5.2.2
+ * Negotiation req: Section 5.2.6.1
+ * Negotiation resp: Section 5.2.6.2
+ * Negotiation confirm: Section 5.2.6.3
+ * Provision disc req: Section 5.2.6.6
+ */
+ size_t size = 0;
+ uint16_t dev_type =
+ wfd->source ? (wfd->sink ? WFD_DEV_INFO_TYPE_DUAL_ROLE :
+ WFD_DEV_INFO_TYPE_SOURCE) :
+ WFD_DEV_INFO_TYPE_PRIMARY_SINK;
+
+ buf[size++] = IE_TYPE_VENDOR_SPECIFIC;
+ size++; /* IE Data length */
+ buf[size++] = wifi_alliance_oui[0];
+ buf[size++] = wifi_alliance_oui[1];
+ buf[size++] = wifi_alliance_oui[2];
+ buf[size++] = 0x0a; /* OUI Type: WFD */
+
+ buf[size++] = WFD_SUBELEM_WFD_DEVICE_INFORMATION;
+ buf[size++] = 0; /* WFD Subelement length */
+ buf[size++] = 6;
+ buf[size++] = 0x00; /* WFD Device Information bitmap: */
+ buf[size++] = dev_type |
+ (wfd->available ? WFD_DEV_INFO_SESSION_AVAILABLE :
+ WFD_DEV_INFO_SESSION_NOT_AVAILABLE) |
+ (wfd->audio ? 0 : WFD_DEV_INFO_NO_AUDIO_AT_PRIMARY_SINK) |
+ (wfd->cp ? WFD_DEV_INFO_CONTENT_PROTECTION_SUPPORT : 0);
+ buf[size++] = wfd->port >> 8; /* Session Mgmt Ctrl Port */
+ buf[size++] = wfd->port & 255;
+ buf[size++] = wfd->throughput >> 8; /* Maximum throughput */
+ buf[size++] = wfd->throughput & 255;
+
+ if (wfd->uibc) {
+ buf[size++] = WFD_SUBELEM_EXTENDED_CAPABILITY;
+ buf[size++] = 0; /* WFD Subelement length */
+ buf[size++] = 2;
+ buf[size++] = 0x00; /* WFD Extended Capability Bitmap: */
+ buf[size++] = 0x01; /* UIBC Support */
+ }
+
+ if (wfd->r2) {
+ buf[size++] = WFD_SUBELEM_R2_DEVICE_INFORMATION;
+ buf[size++] = 0; /* WFD Subelement length */
+ buf[size++] = 2;
+ buf[size++] = 0x00; /* WFD R2 Device Information bitmap: */
+ buf[size++] = wfd->source ? wfd->sink ? 3 : 0 : 1;
+ }
+
+ buf[1] = size - 2;
+ return size;
+}
+
/* TODO: convert to iovecs */
static uint8_t *p2p_build_scan_ies(struct p2p_device *dev, uint8_t *buf,
size_t buf_len, size_t *out_len)
@@ -249,6 +322,8 @@ static uint8_t *p2p_build_scan_ies(struct p2p_device *dev, uint8_t *buf,
size_t wsc_data_size;
L_AUTO_FREE_VAR(uint8_t *, wsc_ie) = NULL;
size_t wsc_ie_size;
+ uint8_t wfd_ie[32];
+ size_t wfd_ie_size;
p2p_info.capability = dev->capability;
memcpy(p2p_info.listen_channel.country, dev->listen_country, 3);
@@ -292,14 +367,21 @@ static uint8_t *p2p_build_scan_ies(struct p2p_device *dev, uint8_t *buf,
if (!wsc_ie)
return NULL;
- /* WFD and other service IEs go here */
+ if (p2p_own_wfd)
+ wfd_ie_size = p2p_build_wfd_ie(p2p_own_wfd, wfd_ie);
+ else
+ wfd_ie_size = 0;
- if (buf_len < wsc_ie_size + p2p_ie_size)
+ if (buf_len < wsc_ie_size + p2p_ie_size + wfd_ie_size)
return NULL;
memcpy(buf + 0, wsc_ie, wsc_ie_size);
memcpy(buf + wsc_ie_size, p2p_ie, p2p_ie_size);
- *out_len = wsc_ie_size + p2p_ie_size;
+
+ if (wfd_ie_size)
+ memcpy(buf + wsc_ie_size + p2p_ie_size, wfd_ie, wfd_ie_size);
+
+ *out_len = wsc_ie_size + p2p_ie_size + wfd_ie_size;
return buf;
}
@@ -2639,6 +2721,7 @@ static void p2p_device_send_probe_resp(struct p2p_device *dev,
size_t wsc_data_size;
uint8_t *wsc_ie;
size_t wsc_ie_size;
+ uint8_t wfd_ie[32];
struct iovec iov[16];
int iov_len = 0;
/* TODO: extract some of these from wiphy features */
@@ -2731,7 +2814,11 @@ static void p2p_device_send_probe_resp(struct p2p_device *dev,
iov[iov_len].iov_len = wsc_ie_size;
iov_len++;
- /* WFD and other service IEs go here */
+ if (p2p_own_wfd) {
+ iov[iov_len].iov_base = wfd_ie;
+ iov[iov_len].iov_len = p2p_build_wfd_ie(p2p_own_wfd, wfd_ie);
+ iov_len++;
+ }
iov[iov_len].iov_base = NULL;
@@ -3613,17 +3700,200 @@ static void p2p_peer_interface_setup(struct l_dbus_interface *interface)
p2p_peer_dbus_disconnect, "", "");
}
+static void p2p_own_wfd_free(void)
+{
+ const struct l_queue_entry *entry;
+
+ l_free(p2p_own_wfd);
+ p2p_own_wfd = NULL;
+
+ for (entry = l_queue_get_entries(p2p_device_list); entry;
+ entry = entry->next) {
+ struct p2p_device *dev = entry->data;
+
+ if (dev->conn_own_wfd)
+ p2p_connect_failed(dev);
+ }
+}
+
+static void p2p_wfd_disconnect_watch_cb(struct l_dbus *dbus, void *user_data)
+{
+ l_debug("P2P WFD service disconnected");
+
+ if (L_WARN_ON(unlikely(!p2p_own_wfd)))
+ return;
+
+ p2p_own_wfd_free();
+}
+
+static void p2p_wfd_disconnect_watch_destroy(void *user_data)
+{
+ p2p_wfd_disconnect_watch = 0;
+}
+
+static struct l_dbus_message *p2p_wfd_register(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ void *user_data)
+{
+ const char *prop_name;
+ struct l_dbus_message_iter prop_iter;
+ struct l_dbus_message_iter prop_variant;
+ struct p2p_wfd_properties props = {};
+ bool have_source = false;
+ bool have_sink = false;
+ bool have_port = false;
+ bool have_has_audio = false;
+ bool have_has_uibc = false;
+ bool have_has_cp = false;
+
+ if (!l_dbus_message_get_arguments(message, "a{sv}", &prop_iter))
+ return dbus_error_invalid_args(message);
+
+ while (l_dbus_message_iter_next_entry(&prop_iter, &prop_name,
+ &prop_variant)) {
+ if (!strcmp(prop_name, "Source")) {
+ if (have_source)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "b",
+ &props.source))
+ return dbus_error_invalid_args(message);
+
+ have_source = true;
+ } else if (!strcmp(prop_name, "Sink")) {
+ if (have_sink)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "b",
+ &props.sink))
+ return dbus_error_invalid_args(message);
+
+ have_sink = true;
+ } else if (!strcmp(prop_name, "Port")) {
+ if (have_port)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "q",
+ &props.port))
+ return dbus_error_invalid_args(message);
+
+ have_port = true;
+ } else if (!strcmp(prop_name, "HasAudio")) {
+ if (have_has_audio)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "b",
+ &props.audio))
+ return dbus_error_invalid_args(message);
+
+ have_has_audio = true;
+ } else if (!strcmp(prop_name, "HasUIBC")) {
+ if (have_has_uibc)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "b",
+ &props.uibc))
+ return dbus_error_invalid_args(message);
+
+ have_has_uibc = true;
+ } else if (!strcmp(prop_name, "HasContentProtection")) {
+ if (have_has_cp)
+ return dbus_error_invalid_args(message);
+
+ if (!l_dbus_message_iter_get_variant(&prop_variant, "b",
+ &props.cp))
+ return dbus_error_invalid_args(message);
+
+ have_has_cp = true;
+ } else
+ return dbus_error_invalid_args(message);
+ }
+
+ if ((!have_source || !props.source) && (!have_sink || !props.sink))
+ return dbus_error_invalid_args(message);
+
+ if (!have_source)
+ props.source = !props.sink;
+ else if (!have_sink)
+ props.sink = !props.source;
+
+ if (have_port && (!props.source || props.port == 0))
+ return dbus_error_invalid_args(message);
+
+ if (props.source && !have_port)
+ props.port = 7236;
+
+ if (have_has_audio && !props.sink)
+ return dbus_error_invalid_args(message);
+ else if (!have_has_audio && props.sink)
+ props.audio = true;
+
+ /*
+ * Should this be calculated based on Wi-Fi connection capacity?
+ * Wi-Fi Display Technical Specification v2.1.0 only mentions this
+ * in the context of the video format selection on the source (D.1.1):
+ * "A WFD Source should determine averaged encoded video data rate
+ * not to exceed the value indicated in the WFD Device Maximum
+ * throughput field at WFD Device Information subelement transmitted
+ * by the targeted WFD Sink [...]"
+ */
+ props.throughput = 10;
+
+ if (p2p_own_wfd)
+ return dbus_error_already_exists(message);
+
+ p2p_wfd_disconnect_watch = l_dbus_add_disconnect_watch(dbus,
+ l_dbus_message_get_sender(message),
+ p2p_wfd_disconnect_watch_cb, NULL,
+ p2p_wfd_disconnect_watch_destroy);
+ p2p_own_wfd = l_memdup(&props, sizeof(props));
+ return l_dbus_message_new_method_return(message);
+}
+
+static struct l_dbus_message *p2p_wfd_unregister(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ void *user_data)
+{
+ if (!l_dbus_message_get_arguments(message, ""))
+ return dbus_error_invalid_args(message);
+
+ if (!p2p_own_wfd)
+ return dbus_error_not_found(message);
+
+ /* TODO: possibly check sender */
+ l_dbus_remove_watch(dbus, p2p_wfd_disconnect_watch);
+ p2p_own_wfd_free();
+ return l_dbus_message_new_method_return(message);
+}
+
+static void p2p_service_manager_interface_setup(
+ struct l_dbus_interface *interface)
+{
+ l_dbus_interface_method(interface, "RegisterDisplayService", 0,
+ p2p_wfd_register, "", "a{sv}", "properties");
+ l_dbus_interface_method(interface, "UnregisterDisplayService", 0,
+ p2p_wfd_unregister, "", "");
+}
+
+static void p2p_service_manager_destroy_cb(void *user_data)
+{
+ if (p2p_own_wfd) {
+ l_dbus_remove_watch(dbus_get_bus(), p2p_wfd_disconnect_watch);
+ p2p_own_wfd_free();
+ }
+}
+
static int p2p_init(void)
{
- if (!l_dbus_register_interface(dbus_get_bus(),
- IWD_P2P_INTERFACE,
+ struct l_dbus *dbus = dbus_get_bus();
+
+ if (!l_dbus_register_interface(dbus, IWD_P2P_INTERFACE,
p2p_interface_setup,
NULL, false))
l_error("Unable to register the %s interface",
IWD_P2P_INTERFACE);
- if (!l_dbus_register_interface(dbus_get_bus(),
- IWD_P2P_PEER_INTERFACE,
+ if (!l_dbus_register_interface(dbus, IWD_P2P_PEER_INTERFACE,
p2p_peer_interface_setup,
NULL, false))
l_error("Unable to register the %s interface",
@@ -3632,13 +3902,27 @@ static int p2p_init(void)
p2p_dhcp_settings = l_settings_new();
p2p_device_list = l_queue_new();
+ if (!l_dbus_register_interface(dbus, IWD_P2P_SERVICE_MANAGER_INTERFACE,
+ p2p_service_manager_interface_setup,
+ p2p_service_manager_destroy_cb, false))
+ l_error("Unable to register the %s interface",
+ IWD_P2P_SERVICE_MANAGER_INTERFACE);
+ else if (!l_dbus_object_add_interface(dbus,
+ IWD_P2P_SERVICE_MANAGER_PATH,
+ IWD_P2P_SERVICE_MANAGER_INTERFACE,
+ NULL))
+ l_error("Unable to register the P2P Service Manager object");
+
return 0;
}
static void p2p_exit(void)
{
- l_dbus_unregister_interface(dbus_get_bus(), IWD_P2P_INTERFACE);
- l_dbus_unregister_interface(dbus_get_bus(), IWD_P2P_PEER_INTERFACE);
+ struct l_dbus *dbus = dbus_get_bus();
+
+ l_dbus_unregister_interface(dbus, IWD_P2P_INTERFACE);
+ l_dbus_unregister_interface(dbus, IWD_P2P_PEER_INTERFACE);
+ l_dbus_unregister_interface(dbus, IWD_P2P_SERVICE_MANAGER_INTERFACE);
l_queue_destroy(p2p_device_list, p2p_device_free);
p2p_device_list = NULL;
l_settings_free(p2p_dhcp_settings);
--
2.25.1
1 year, 11 months
[PATCH 1/4] agent: Don't remove object at IWD_AGENT_MANAGER_PATH
by Andrew Zaborowski
This same object is used for the P2P service manager, let each module
remove the interfaces it added only.
Reported-by: Denis Kenzior
---
src/agent.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/agent.c b/src/agent.c
index d213f3cd..101eaffd 100644
--- a/src/agent.c
+++ b/src/agent.c
@@ -651,7 +651,6 @@ static void agent_exit(void)
{
struct l_dbus *dbus = dbus_get_bus();
- l_dbus_unregister_object(dbus, IWD_AGENT_MANAGER_PATH);
l_dbus_unregister_interface(dbus, IWD_AGENT_MANAGER_INTERFACE);
l_queue_destroy(agents, agent_free);
--
2.25.1
1 year, 11 months
[PATCH v8] netdev: use wiphy radio work queue for connections
by James Prestwood
This adds connection/FT attempts to the radio work queue. This
will ensure that connections aren't delayed or done concurrently
with scanning.
---
src/netdev.c | 191 ++++++++++++++++++++++++++++++++++++--------------
src/station.c | 8 +--
2 files changed, 141 insertions(+), 58 deletions(-)
v8:
- Put auth-proto/normal connects back together as this prevented SAE
from randomizing the address
- Separated out ft work into its own worker function
diff --git a/src/netdev.c b/src/netdev.c
index e469fb1c..9e2690d3 100644
--- a/src/netdev.c
+++ b/src/netdev.c
@@ -136,6 +136,9 @@ struct netdev {
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
+ struct l_genl_msg *connect_cmd;
+ struct wiphy_radio_work_item work;
+
bool connected : 1;
bool operational : 1;
bool rekey_offload_support : 1;
@@ -485,6 +488,9 @@ static void netdev_preauth_destroy(void *data)
static void netdev_connect_free(struct netdev *netdev)
{
+ if (netdev->work.id)
+ wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
+
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
@@ -530,6 +536,11 @@ static void netdev_connect_free(struct netdev *netdev)
netdev->ignore_connect_event = false;
netdev->expect_connect_failure = false;
+ if (netdev->connect_cmd) {
+ l_genl_msg_unref(netdev->connect_cmd);
+ netdev->connect_cmd = NULL;
+ }
+
netdev_rssi_polling_update(netdev);
if (netdev->connect_cmd_id) {
@@ -1019,6 +1030,9 @@ static void netdev_connect_ok(struct netdev *netdev)
}
netdev_rssi_polling_update(netdev);
+
+ if (netdev->work.id)
+ wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs,
@@ -1487,6 +1501,9 @@ void netdev_handshake_failed(struct handshake_state *hs, uint16_t reason_code)
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
l_error("error sending DEL_STATION");
}
+
+ if (netdev->work.id)
+ wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
static void hardware_rekey_cb(struct l_genl_msg *msg, void *data)
@@ -2421,32 +2438,48 @@ static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev,
struct rtnl_data {
struct netdev *netdev;
- struct l_genl_msg *cmd_connect;
uint8_t addr[ETH_ALEN];
int ref;
};
-static int netdev_begin_connection(struct netdev *netdev,
- struct l_genl_msg *cmd_connect)
+static int netdev_begin_connection(struct netdev *netdev)
{
- if (cmd_connect) {
+ if (netdev->connect_cmd) {
netdev->connect_cmd_id = l_genl_family_send(nl80211,
- cmd_connect, netdev_cmd_connect_cb,
- netdev, NULL);
+ netdev->connect_cmd,
+ netdev_cmd_connect_cb, netdev,
+ NULL);
- if (!netdev->connect_cmd_id) {
- l_genl_msg_unref(cmd_connect);
- return -EIO;
- }
+ if (!netdev->connect_cmd_id)
+ goto failed;
+
+ netdev->connect_cmd = NULL;
}
+ /*
+ * Set the supplicant address now, this may have already been done for
+ * a non-randomized address connect, but if we are randomizing we need
+ * to set it again as the address should have now changed.
+ */
handshake_state_set_supplicant_address(netdev->handshake, netdev->addr);
- /* set connected since the auth protocols cannot do so internally */
- if (netdev->ap && auth_proto_start(netdev->ap))
+ if (netdev->ap) {
+ if (!auth_proto_start(netdev->ap))
+ goto failed;
+
+ /*
+ * set connected since the auth protocols cannot do
+ * so internally
+ */
netdev->connected = true;
+ }
return 0;
+
+failed:
+ netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
+ MMPDU_STATUS_CODE_UNSPECIFIED);
+ return -EIO;
}
static void netdev_mac_change_failed(struct netdev *netdev,
@@ -2461,8 +2494,6 @@ static void netdev_mac_change_failed(struct netdev *netdev,
* mac_change_cmd_id was set.
*/
if (!netdev_get_is_up(netdev)) {
- l_genl_msg_unref(req->cmd_connect);
-
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DOWN);
@@ -2474,7 +2505,7 @@ static void netdev_mac_change_failed(struct netdev *netdev,
*/
l_info("Interface still up after failing to change the MAC, "
"continuing with connection");
- if (netdev_begin_connection(netdev, req->cmd_connect) < 0)
+ if (netdev_begin_connection(netdev) < 0)
goto failed;
return;
@@ -2517,7 +2548,7 @@ static void netdev_mac_power_up_cb(int error, uint16_t type,
/*
* Pick up where we left off in netdev_connect_commmon.
*/
- if (netdev_begin_connection(netdev, req->cmd_connect) < 0) {
+ if (netdev_begin_connection(netdev) < 0) {
l_error("Failed to connect after changing MAC");
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
@@ -2576,9 +2607,7 @@ static void netdev_mac_power_down_cb(int error, uint16_t type,
* Returns -EALREADY if the requested MAC matched our current MAC
* Returns -EIO if there was an IO error when powering down
*/
-static int netdev_start_powered_mac_change(struct netdev *netdev,
- struct scan_bss *bss,
- struct l_genl_msg *cmd_connect)
+static int netdev_start_powered_mac_change(struct netdev *netdev)
{
struct rtnl_data *req;
uint8_t new_addr[6];
@@ -2586,8 +2615,8 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
/* No address set in handshake, use per-network MAC generation */
if (util_mem_is_zero(netdev->handshake->spa, ETH_ALEN))
wiphy_generate_address_from_ssid(netdev->wiphy,
- (const char *)bss->ssid,
- new_addr);
+ (const char *)netdev->handshake->ssid,
+ new_addr);
else
memcpy(new_addr, netdev->handshake->spa, ETH_ALEN);
@@ -2600,7 +2629,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
req = l_new(struct rtnl_data, 1);
req->netdev = netdev;
/* This message will need to be unreffed upon any error */
- req->cmd_connect = cmd_connect;
req->ref++;
memcpy(req->addr, new_addr, sizeof(req->addr));
@@ -2609,7 +2637,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
req, netdev_mac_destroy);
if (!netdev->mac_change_cmd_id) {
- l_genl_msg_unref(req->cmd_connect);
l_free(req);
return -EIO;
@@ -2618,6 +2645,35 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
return 0;
}
+static bool netdev_connection_work_ready(struct wiphy_radio_work_item *item)
+{
+ struct netdev *netdev = l_container_of(item, struct netdev, work);
+
+ if (mac_per_ssid) {
+ int ret = netdev_start_powered_mac_change(netdev);
+
+ if (!ret)
+ return false;
+ else if (ret != -EALREADY)
+ goto failed;
+ }
+
+ if (netdev_begin_connection(netdev) < 0)
+ goto failed;
+
+ return false;
+
+failed:
+ netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
+ MMPDU_STATUS_CODE_UNSPECIFIED);
+
+ return true;
+}
+
+static const struct wiphy_radio_work_item_ops connect_work_ops = {
+ .do_work = netdev_connection_work_ready,
+};
+
static int netdev_connect_common(struct netdev *netdev,
struct l_genl_msg *cmd_connect,
struct scan_bss *bss,
@@ -2626,6 +2682,7 @@ static int netdev_connect_common(struct netdev *netdev,
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
+ netdev->connect_cmd = cmd_connect;
netdev->event_filter = event_filter;
netdev->connect_cb = cb;
netdev->user_data = user_data;
@@ -2642,14 +2699,9 @@ static int netdev_connect_common(struct netdev *netdev,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
handshake_state_set_no_rekey(hs, true);
- if (mac_per_ssid) {
- int ret = netdev_start_powered_mac_change(netdev, bss,
- cmd_connect);
- if (ret != -EALREADY)
- return ret;
- }
-
- return netdev_begin_connection(netdev, cmd_connect);
+ wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
+ &connect_work_ops);
+ return 0;
}
int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
@@ -2667,7 +2719,7 @@ int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
- if (netdev->connected || netdev->connect_cmd_id)
+ if (netdev->connected || netdev->connect_cmd_id || netdev->work.id)
return -EISCONN;
switch (hs->akm_suite) {
@@ -2709,23 +2761,36 @@ int netdev_disconnect(struct netdev *netdev,
netdev_disconnect_cb_t cb, void *user_data)
{
struct l_genl_msg *disconnect;
+ bool send_disconnect = true;
if (netdev->type != NL80211_IFTYPE_STATION &&
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
- if (!netdev->connected)
- return -ENOTCONN;
-
if (netdev->disconnect_cmd_id)
return -EINPROGRESS;
/* Only perform this if we haven't successfully fully associated yet */
if (!netdev->operational) {
+ /*
+ * Three possibilities here:
+ * 1. We do not actually have a connect in progress (work.id
+ * is zero), then we can bail out early with an error.
+ * 2. We have sent CMD_CONNECT but not fully connected. The
+ * CMD_CONNECT needs to be canceled and a disconnect should
+ * be sent.
+ * 3. Queued up the connect work, but haven't sent CMD_CONNECT
+ * to the kernel. This case we do not need to send a
+ * disconnect.
+ */
+ if (!netdev->work.id)
+ return -ENOTCONN;
+
if (netdev->connect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
netdev->connect_cmd_id = 0;
- }
+ } else
+ send_disconnect = false;
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
@@ -2733,19 +2798,23 @@ int netdev_disconnect(struct netdev *netdev,
netdev_connect_free(netdev);
}
- disconnect = netdev_build_cmd_disconnect(netdev,
+ if (send_disconnect) {
+ disconnect = netdev_build_cmd_disconnect(netdev,
MMPDU_REASON_CODE_DEAUTH_LEAVING);
- netdev->disconnect_cmd_id = l_genl_family_send(nl80211, disconnect,
- netdev_cmd_disconnect_cb, netdev, NULL);
+ netdev->disconnect_cmd_id = l_genl_family_send(nl80211,
+ disconnect, netdev_cmd_disconnect_cb,
+ netdev, NULL);
- if (!netdev->disconnect_cmd_id) {
- l_genl_msg_unref(disconnect);
- return -EIO;
- }
+ if (!netdev->disconnect_cmd_id) {
+ l_genl_msg_unref(disconnect);
+ return -EIO;
+ }
- netdev->disconnect_cb = cb;
- netdev->user_data = user_data;
- netdev->aborting = true;
+ netdev->disconnect_cb = cb;
+ netdev->user_data = user_data;
+ netdev->aborting = true;
+ } else if (cb)
+ cb(netdev, true, user_data);
return 0;
}
@@ -3087,12 +3156,31 @@ static void netdev_ft_over_ds_tx_authenticate(struct iovec *iov,
netdev_ft_request_cb);
}
+static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item)
+{
+ struct netdev *netdev = l_container_of(item, struct netdev, work);
+
+ if (!auth_proto_start(netdev->ap)) {
+ /* Restore original nonce */
+ memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
+
+ netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
+ MMPDU_STATUS_CODE_UNSPECIFIED);
+ return true;
+ }
+
+ return false;
+}
+
+static const struct wiphy_radio_work_item_ops ft_work_ops = {
+ .do_work = netdev_ft_work_ready,
+};
+
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
bool over_air,
netdev_connect_cb_t cb)
{
struct netdev_handshake_state *nhs;
- int err = -EINVAL;
if (!netdev->operational)
return -ENOTCONN;
@@ -3173,15 +3261,10 @@ static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_ft_over_ds_tx_authenticate,
netdev_ft_tx_associate, netdev);
- if (!auth_proto_start(netdev->ap))
- goto restore_snonce;
+ wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
+ &ft_work_ops);
return 0;
-
-restore_snonce:
- memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
-
- return err;
}
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
diff --git a/src/station.c b/src/station.c
index 35f4a96d..0095d6c8 100644
--- a/src/station.c
+++ b/src/station.c
@@ -2702,10 +2702,6 @@ int station_disconnect(struct station *station)
if (!station->connected_bss)
return -ENOTCONN;
- if (netdev_disconnect(station->netdev,
- station_disconnect_cb, station) < 0)
- return -EIO;
-
if (station->netconfig)
netconfig_reset(station->netconfig);
@@ -2718,6 +2714,10 @@ int station_disconnect(struct station *station)
station_enter_state(station, STATION_STATE_DISCONNECTING);
+ if (netdev_disconnect(station->netdev,
+ station_disconnect_cb, station) < 0)
+ return -EIO;
+
return 0;
}
--
2.21.1
1 year, 11 months