Devices and connectors
In WHAD, devices and connectors are two different components that are used together to allow users and applications to perform various actions on wireless networks, using compatible hardware.
A device defines a compatible hardware device that is plugged to the host
computer running WHAD, could it be an internal device present on a motherboard
or an external device connected through a USB port. Such a device is represented
in WHAD by a class that derives from the Device class
acting as an interface between the framework and the real hardware used to communicate
over the air with other networks and peripherals.
WHAD support the following hardware devices: - Internal bluetooth adapters (installed on motherboard), supporting at least Bluetooth version >= 4.0 - USB Bluetooth dongles, supporting at least Bluetooth version >= 4.0 - [Great Scott Gadgets’ Ubertooth One](https://greatscottgadgets.com/ubertoothone/) - [Great Scott Gadgets’ Yard Stick One](https://greatscottgadgets.com/yardstickone/) - [River Loop Security’s ApiMote](http://apimote.com/) - [Nordic nRF52840 USB dongle](https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle) - [Maker Diary’s nRF52840 MDK USB dongle](https://makerdiary.com/products/nrf52840-mdk-usb-dongle)
Some devices are supported natively (running a custom firmware implementing our WHAD protocol) while others
are supported through a dedicated adaptation layer in a dedicated class inheriting from
VirtualDevice.
Device classes are strongly tied to a specific hardware (and firmware, if required) and basically take WHAD messages in input and output WHAD messages too. WHAD protocol includes a discovery feature allowing each device to tell the host computer what domains it support (think of domains as wireless protocols) and its capabilities for each of them, as well as the set of commands supported for each domain.
Connectors on the other hand are not tied to any specific hardware but rather to a specific domain and a set of capabilities. A connector acts as a specialized role applied to a compatible hardware device, and exposes a set of actions that can be used by any application, no matter the hardware since it at least supports the required domain and capabilities. This way, any high-level interaction implemented in a connector can be used with any hardware that provides the required capabilities for a given domain, making it easier to create custom firmwares for new hardware without having to care about how some wireless attack or procedure is implemented.
Devices
WHAD provides various classes to interact with WHAD-enabled hardware:
Class whad.device.device.Device is the default class that allows WHAD devices
enumeration and access. It is the main class to use to open any device, through
its whad.device.Device.create() method as shown below:
from whad.device import Device
dev = Device.create("uart0")
The whad.device.device.VirtualDevice shall not be directly used. This class
is used to add support for incompatible WHAD devices like the Ubertooth
or the ApiMote and acts as an adaptation layer between the underlying WHAD
protocol and the specific protocol used by the target hardware.
Important
The whad.device.device.WhadDevice class that is still defined in WHAD (and used
in some old example scripts or documentation) is an alias for the new
whad.device.device.Device class, and is meant to be deprecated in the future.
This old class has been renamed to Device for clarity, and the same happened
with the old default connector class whad.device.connector.WhadConnector
that has been renamed to whad.device.connector.Connector.
These old classes will be marked as deprecated in a future release, with a specific EOL date announced. A warning message will be issued in case one of these classes is used in a script or a tool to give time to users to migrate to the new ones (renaming classes is enough to switch to the new implementation, APIs stay the same).
- class whad.device.device.Device(index: int | None = None)[source]
WHAD hardware interface
- busy() bool[source]
Check if the interface is busy.
We consider an interface as busy if there is at least one message in its output messages or in its input messages.
- classmethod check_interface(interface)[source]
Checks dynamically if the device can be instantiated.
- property connector
Connector bound to the interface
- classmethod create(interface_string)[source]
Create a specific device according to the provided interface string, formed as follows:
<device_type>[device_index][:device_identifier]
- Examples:
uart or uart0: defines the first compatible UART device available
uart1: defines the second compatible UART device available
uart:/dev/ttyACMO: defines a compatible UART device identified by /dev/tty/ACMO
ubertooth or ubertooth0: defines the first available Ubertooth device
ubertooth:11223344556677881122334455667788: defines a Ubertooth device with serial number 11223344556677881122334455667788
- classmethod create_inst(interface_string) Type[Device][source]
Helper allowing to get a device according to the interface string provided.
- To make it work, every device class must implement:
a class attribute INTERFACE_NAME, matching the interface name
a class method list, returning the available devices
a property identifier, allowing to identify the device in a unique way
This method should NOT be used outside of this class. Use Device.create instead.
- property device_id: str | None
Return device ID
- discover()[source]
Performs device discovery (synchronously).
Discovery process asks the device to provide its description, including its supported domains and associated capabilities. For each domain we then query the device and get the list of supported commands.
- static find(interface: str) Type[Device][source]
Find a device based on its interface string with lazy loading of available interface implementations.
- Parameters:
interface (str) – Interface name following WHAD’s standard interface pattern
- Returns:
Found interface object
- Return type:
- Raise:
WhadDeviceNotFound
- get_domain_capability(domain) int[source]
Get a device domain capabilities.
- Parameters:
domain (Domain) – Target domain
- Returns:
Domain capabilities
- Return type:
DeviceDomainInfoResp
- get_domain_commands(domain)[source]
Get a device supported domain commands.
- Parameters:
domain (Domain) – Target domain
- Returns:
Bitmask of supported commands
- Return type:
int
- get_domains()[source]
Get device’ supported domains.
- Returns:
list of supported domains
- Return type:
list
- get_pending_message(timeout: float = None) Generator[HubMessage, None, None][source]
Get message waiting to be sent to the interface.
- has_domain(domain) bool[source]
Checks if device supports a specific domain.
- Parameters:
domain (Domain) – Domain
- Returns:
True if domain is supported, False otherwise.
- Return type:
bool
- property hub
Retrieve the device protocol hub (parser/factory)
- property index: int
Get the interface index
- Returns:
Interface index
- Return type:
int
- property info: DeviceInfo | None
Get device info object
- Returns:
Device information object
- Return type:
DeviceInfo
- property interface
Returns the current interface of the device.
- on_discovery_msg(message)[source]
Method called when a discovery message is received. If a connector has been associated with the device, forward this message to this connector.
- property opened: bool
Device is open ?
- classmethod reset_dev_index() None[source]
Reset the device index of the specified class.
- Parameters:
cls (Device) – Device class
- send_command(command: HubMessage, keep: Callable[[...], bool] = None)[source]
Sends a command and awaits a specific response from the device. WHAD commands usualy expect a CmdResult message, if keep is not provided then this method will by default wait for a CmdResult.
- Parameters:
command (Message) – Command message to send to the device
keep – Message queue filter function (optional)
- Returns:
Response message from the device
- Return type:
Message
- send_discover_domain_query(domain)[source]
Sends a DeviceDomainQuery message and awaits for a DeviceDomainResp answer.
- send_discover_info_query(proto_version=256)[source]
Sends a DeviceInfoQuery message and awaits for a DeviceInfoResp answer.
- send_message(message: HubMessage, keep: Callable[[...], bool] = None)[source]
Serializes a message and sends it to the interface, without waiting for an answer. Optionally, you can update the message queue filter if you need to wait for specific messages after the message is sent.
- Parameters:
message (Message) – Message to send
keep – Message queue filter function
- property type
Returns the name of the class linked to the current device.
- wait_for_message(timeout: float = None, keep: Callable[[...], bool] = None, command: bool = False)[source]
Configures the device message queue filter to automatically move messages that matches the filter into the queue, and then waits for the first message that matches this filter and process it.
This method is blocking until a matching message is received.
- Parameters:
timeout (int) – Timeout
filter – Message queue filtering function (optional)
- class whad.device.device.VirtualDevice(index: int | None = None)[source]
Virtual interface implementation.
This variant of the base Interface class provides a way to emulate an interface compatible with WHAD. This emulated compatible interface is used as an adaptation layer between WHAD’s core and third-party hardware that does not run a WHAD-enabled firmware.
Connectors
Connectors shall ensure the device they are linked to does support the
target domain and a mimimal set of commands, and can tailor its behavior
depending on the capabilities of the hardware. If a connector is linked
to a device that either does not support the domain this connector is
supposed to operate or lacks specific commands, a
:py:whad.exceptions.UnsupportedDomain exception or a
whad.exceptions.UnsupportedCapability may be raised.
WHAD provides a default connector class, whad.device.connector.Connector,
that implements a set of features out-of-the-box:
Packet and message sniffing and processing
Event notification mechanism
Synchronous mode
Sniffing packet and messages could be useful to implement packet sniffers or
intercept some specific events like disconnection of the linked hardware device.
Most of the time this feature is used to sniff packets related to a target domain.
The whad.device.connector.Connector.sniff() method is specifically
tailored for this use. When not sniffing, packets received from the hardware device
are forwarded to the connector’s packet processing methods than can be overriden by
inheriting classes.
By default, the default connector class provides methods to add and remove custom
event listeners (whad.device.connector.Connector.add_listener() and
whad.device.connector.Connector.remove_listener()), and an additional
method to send an event to the registered listeners (whad.device.connector.Connector.notify()).
Last but not least, the provided synchronous mode will disable packet forwarding and save all received packets in a reception queue, waiting for the application to retrieve and process them. Service messages will still be processed by the connector, in order to handle any device disconnection or other unexpected event that may occur. When this synchronous mode is disabled, every unprocessed packet stored in the reception queue are automatically forwarded to the connector’s packet processing methods, and will be then dispatched to the corresponding handlers.
- class whad.device.connector.Connector(device: Device | None = None)[source]
Interface connector.
A connector creates a link between a device and a protocol controller.
- __init__(device: Device | None = None)[source]
Constructor.
Link the device with this connector, and this connector with the provided device.
- Parameters:
device (Device) – Device to be used with this connector.
- add_listener(listener: Callable[[...], None], event_cls: List[Event] | Event = None)[source]
Add a connector notification listener with optional event filter.
- Parameters:
listener (callable) – callable to handle events
event_cls (list, ConnectorEvent, optional) – List of event classes or single event class to match
- add_locked_pdu(pdu)[source]
Add a pending Protocol Data Unit (PDU) to our locked pdus queue.
- Parameters:
pdu (scapy.packet.Packet) – Packet to add to locked packets queue
- add_sync_event(event: DeviceEvt)[source]
Add an event to the synchronous event queue when synchronous mode is enabled.
- Parameters:
event (whad.device.DeviceEvt) – Device event to add to our queue of received events
- attach_callback(callback, on_reception=True, on_transmission=True, packet: ~typing.Callable[[~scapy.packet.Packet], bool] = <function Connector.<lambda>>)[source]
Attach a new packet callback to current connector.
- Parameters:
callback – Processing function.
on_reception – Boolean indicating if the callback monitors reception.
on_transmission – Boolean indicating if the callback monitors transmission.
filter – Lambda function filtering packets matching the callback.
- Returns:
Boolean indicating if the callback has been successfully attached.
- attach_error_callback(callback, context=None)[source]
Attach an error callback to this connector.
- Parameters:
callback – function handling errors.
context – context object to pass to the error handling function.
- Returns:
Boolean indicating if the callback has been successfully attached.
- detach_callback(callback, on_reception=True, on_transmission=True)[source]
Detach an existing packet callback from current connector.
- Parameters:
callback – Processing function.
on_reception – Boolean indicating if the callback was monitoring reception.
on_transmission – Boolean indicating if the callback was monitoring transmission.
- Returns:
Boolean indicating if the callback has been successfully detached.
- property device
Get the connector associated device instance
- enable_synchronous(enabled: bool, events: bool = False)[source]
Enable or disable synchronous mode
Synchronous mode is a mode in which the connector expects sone third-party code to retrieve the received packets instead of forwarding them to the on_packet() callback. It is then possible to wait for some packet to be received and avoid the automatic behavior triggered by a call to on_packet().
- Parameters:
enabled (bool) – If set to True, enable synchronous mode. Otherwise disable it.
events (bool, optional) – If set to True, synchronous mode will also capture events sent by the associate device
- get_event(timeout: float | None = None) Generator[DeviceEvt, None, None][source]
Retrieve event from connector’s event queue.
- Parameters:
timeout (float) – Timeout in seconds
- has_locked_pdus() bool[source]
Determine if connector has locked PDUs.
- Returns:
True if connector has locked PDUs, False otherwise.
- Return type:
bool
- property hub: ProtocolHub
Get the connector protocol hub
- Returns:
Instance of ProtocolHub
- Return type:
ProtocolHub
- is_locked() bool[source]
Determine if the connector is locked.
- Returns:
True if lock mode is enabled, False otherwise.
- is_stalled() bool[source]
Determine if the interface associated with this connector is stalled, i.e. has messages awaiting processing even if closed.
- Returns:
True if interface is stalled, False otherwise.
- Return type:
bool
- is_synchronous()[source]
Determine if the conncetor is in synchronous mode.
- Returns:
True if synchronous mode is enabled, False otherwise.
- lock()[source]
Lock connector. A locked connector will not dispatch packets/pdus like in synchronous mode and will keep them in a waiting queue, but will dispatch them all at once when unlocked.
- monitor_packet_rx(packet)[source]
Signals the reception of a packet and triggers execution of matching reception callbacks.
- Parameters:
packet – scapy packet being received by whad-client.
- monitor_packet_tx(packet)[source]
Signals the transmission of a packet and triggers execution of matching transmission callbacks.
- Parameters:
packet – scapy packet being transmitted from whad-client.
- on_any_msg(message)[source]
Callback function to process any incoming messages.
This method MAY be overriden by inherited classes.
- Parameters:
message – WHAD message
- on_device_event(event: DeviceEvt)[source]
Dispatch message to the connector’s handlers.
This method may trigger specific message processing in inherited connector’s classes as well as attached protocol stacks. Since it is only called by the connector’s I/O thread, that’s pretty safe.
- Parameters:
event (whad.device.DeviceEvt) – Device event to process
- on_discovery_msg(message)[source]
Callback function to process incoming discovery messages.
This method MUST be overriden by inherited classes.
- Parameters:
message – Discovery message
- on_domain_msg(domain, message)[source]
Callback function to process incoming domain-related messages.
This method MUST be overriden by inherited classes.
- Parameters:
message – Domain message
- on_error(error)[source]
Triggers a call to the device connector error handling registered callback(s).
- on_event(event)[source]
Callback function to process incoming events.
This method MUST be overriden by inherited classes.
- Parameters:
event (
whad.hub.events.AbstractEvent) – Event to process
- on_generic_msg(message)[source]
Callback function to process incoming generic messages.
This method MUST be overriden by inherited classes.
- Parameters:
message – Generic message
- on_packet(packet)[source]
Callback function to process incoming packets.
This method MUST be overriden by inherited classes.
- Parameters:
packet (
scapy.packet.Packet) – Packet
- remove_listener(listener: Callable[[...], None]) bool[source]
Remove listener from registered listeners.
- reset_callbacks(reception=True, transmission=True)[source]
Detach any packet callback attached to the current connector.
- Parameters:
on_reception – Boolean indicating if the callbacks monitoring reception are detached.
on_transmission – Boolean indicating if the callbacks monitoring transmission are detached.
- send_command(message, keep=None)[source]
Sends a command message to the underlying device and waits for an answer.
By default, this method will wait for a CmdResult message, but you can provide any other filtering function/lambda if you are expecting another message as a reply from the device.
- Parameters:
message (Message) – WHAD message to send to the device
filter – Filtering function used to match the expected response from the device.
- send_event(event: DeviceEvt)[source]
Send an event into the connector event queue.
- Parameters:
event (DeviceEvt) – Event to add to the connector’s event queue
- send_message(message, keep=None)[source]
Sends a message to the underlying device without waiting for an answer.
- Parameters:
message (Message) – WHAD message to send to the device.
filter – optional filter function for incoming message queue.
- set_device(device=None)[source]
Set device linked to this connector.
- Parameters:
device (WhadDevice) – Device to be used with this connector.
- sniff(messages: List = None, timeout: float = None) Generator[HubMessage, None, None][source]
Enable sniffing mode and report any received messages, optionally filtered by their type/classes if messages is provided.
- Parameters:
messages – If specified, sniff only messages that match the given types.
messages – List, optional
timeout (float, optional) – If specified, set a sniffing timeout in seconds
- unlock(dispatch_callback=None)[source]
Unlock connector and dispatch pending PDUs.
- Parameters:
dispatch_callback (callable) – PDU dispatch callback that overrides the internal dispatch routine
- wait_for_message(timeout=None, keep=None, command=False)[source]
Waits for a specific message to be received.
This method reads the message queue and return the first message that matches the provided filter. A timeout can be specified and will cause this method to return None if this timeout is reached.
- wait_packet(timeout: float = None)[source]
Wait for a packet when in synchronous mode. This method should be only used with SYNC_MODE_PKT to avoid discarding any device event.
- Parameters:
timeout (float, optional) – If specified, defines a timeout when querying the PDU queue
- Returns:
Received packet if any, None if empty or when timeout is reached
- Return type:
scapy.packet.Packet
Deprecated classes
In early versions of WHAD, device and connector base classes had different names that were too long and badly chosen. We took the decision to rename them while keeping the old classes as aliases of the new ones. This way, old code and documentation and even tools keep working as expected, until we decide to definitely deprecate these old classes. For now, they are still defined and available, but we will mark them as deprecated starting from version 1.3.
A warning will be displayed each time such a class is used, inciting users and maintainers to update their code to use the new classes, which are strictly compatible as they expose the same methods and properties. This warning will include a deadline after which these classes will be definitely removed.
- class whad.device.device.WhadDevice(index: int | None = None)[source]
Bases:
DeviceThis class is an alias for
whad.device.device.Device, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.
- class whad.device.device.WhadVirtualDevice(index: int | None = None)[source]
Bases:
VirtualDeviceThis class is an alias for
whad.device.device.VirtualDevice, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.
- class whad.device.connector.WhadDeviceConnector(device: Device | None = None)[source]
Bases:
ConnectorThis class is an alias for
whad.device.connector.Connector, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.