Basic and global functions

Global settings and error handling

Set of global and useful classes and functions used within the module.

Exceptions:BOF-specific exceptions raised by the module.
Logging:Functions to enable or disable logging for the module.
String manipulation:
 Functions to make basic changes on strings.
exception bof.base.BOFDeviceError

Bases: bof.base.BOFError

Exceptions related to errors returned by the device.

Raise when the device responds with an error code, but the network connection is still fine.

exception bof.base.BOFError

Bases: Exception

Base class for all BOF exceptions.

Warning

Should not be used directly, please raise or catch subclasses instead.

exception bof.base.BOFLibraryError

Bases: bof.base.BOFError

Library, files and import-related exceptions.

Raise when the library cannot find what it needs to work correctly (such as an external module or a file).

exception bof.base.BOFNetworkError

Bases: bof.base.BOFError

Network-related exceptions.

Raise when the network connection fails or is interrupted.

exception bof.base.BOFProgrammingError

Bases: bof.base.BOFError

Script and module programming-related errors.

Raise when a function or an argument is not used as expected.

Note

As a module user, this exception is the most frequent one.

bof.base.disable_logging() → None

Turn off logging features,

bof.base.enable_logging(filename: str = '', error_only: bool = False) → None

Turn on logging features to store BOF-autogenerated and user events. Relies on Python’s logging module.

Parameters:
  • filename – Optional name of the file in which events will be saved. Default is bof.log.
  • error_only – All types of events are logged (info, warning, error) are saved unless this parameter is set to True.
bof.base.log(message: str, level: str = 'INFO') → bool

Logs an event (message) to a file, if BOF logging is enabled. Requires previous call to bof.`enable_logging()`. A message is recorded along with event-related information:

  • date and time
  • level (can be changed with parameter level)
  • event location in the code (file name, line number)
Parameters:
  • message – Event definition.
  • level – Type of event to record: ERROR, WARNING, DEBUG. INFO` (default). Levels from Python’s logging are used.
Returns:

Current state of logging (enabled/True, disabled/False).

bof.base.to_property(value: str) → str

Lower a string and replace all non alnum characters with _

Basic network protocol implementation

Network protocol global classes and abstract implementations.

Provides classes for asynchronous network connection management on different transport protocols, to be used by higher-level protocol implementation classes. Relies on module asyncio.

UDP:Implementation of asynchronous UDP communication and packet crafting.
TCP:Implementation of asynchronous TCP communication and packet crafting.

Both classes rely on internal class _Transport, which should not be instantiated.

Network connection and exchange example with raw UDP:

from bof import UDP
udp = UDP()
udp.connect("192.168.1.1", 3671)
udp.send(b"Hi!")
udp.disconnect()

Usage is the same with raw TCP.

Warning

Direct initialization of TCP/UDP object is not recommended. The user should use BOF network classes inherited from TCP/UDP (e.g. KNXnet for the KNX protocol).

bof.network.IP_RANGE(ip_range: str)

Convert from an IP range string to a list of IP addresses.

Supported format: X.X.X.X/Y where X is the IPv4 address and Y is the mask

bof.network.IS_IP(ip: str)

Check that ip is a valid IPv4 address.

class bof.network.TCP

Bases: bof.network._Transport

TCP protocol endpoint.

This is the parent class to higher-lever network protocol implementation. It can be instantiated as is, however this is not the expected behavior. Uses protected _TCP classes implementing asyncio TCP handler.

Warning

Should not be instantiated directly.

connect(ip: str, port: int, timeout: float = 1.0) → object

Initialize asynchronous connection using TCP on ip:port.

Parameters:
  • ip – IPv4 address as a string with format A.B.C.D.
  • port – Port number as an integer.
  • timeout – Time out value in seconds, as a float (default is 1.0s).
Returns:

The instance of the TCP class created,

Raises:

BOFNetworkError – if connection fails.

Example:

tcp = bof.TCP().connect("127.0.0.1", 4840)
send(data: bytes, address: tuple = None) → int

Send data to address over TCP.

Parameters:
  • data – Raw byte array or string to send.
  • address – Address to send data to, with format tuple (ipv4_address, port). If address is not specified, uses the address given to connect.
Returns:

The number of bytes sent, as an integer.

Example:

tcp.send("test_send")
tcp.send(b'')
bof.network.TIMEOUT_EXCEPTIONS()

Choose timeout exceptions to handle depending on Python version.

asyncio.exceptions does not exist prior to Python 3.8.

class bof.network.UDP

Bases: bof.network._Transport

UDP protocol endpoint, inheriting from Transport base class.

This is the parent class to higher-lever network protocol implementation. It can be instantiated as is, however this is not the expected behavior. Uses protected _UDP classes implementing asyncio UDP handler.

Warning

Should not be instantiated directly.

static broadcast(data: bytes, address: tuple, timeout: float = 1.0) → list

Broadcasts a request and waits for responses from devices (UDP).

Parameters:
  • data – Raw byte array or string to send.
  • address – Remote network address with format tuple (ip, port).
  • timeout – Time out value in seconds, as a float (default is 1.0s).
Returns:

A list of tuples with format (response, (ip, port)).

Raises:

BOFNetworkError – If multicast parameters are invalid.

Example:

devices = UDP.broadcast(b'...', ('192.168.1.255', 3671))
connect(ip: str, port: int) → object

Initialize asynchronous connection using UDP on ip:port.

Parameters:
  • ip – IPv4 address as a string with format A.B.C.D.
  • port – Port number as an integer.
Returns:

The instance of the UDP class created,

Raises:

BOFNetworkError – if connection fails.

Example:

udp = bof.UDP().connect("127.0.0.1", 13671)
static multicast(data: bytes, address: tuple, timeout: float = 1.0) → list

Sends a multicast request to specified ip address and port (UDP).

Expects devices subscribed to the address to respond and return responses as a list of frames with their source. Opens its own socket.

Parameters:
  • data – Raw byte array or string to send.
  • address – Remote network address with format tuple (ip, port).
  • timeout – Time out value in seconds, as a float (default is 1.0s).
Returns:

A list of tuples with format (response, (ip, port)).

Raises:

BOFNetworkError – If multicast parameters are invalid.

Example:

devices = UDP.multicast(b'...', ('224.0.23.12', 3671))
send(data: bytes, address: tuple = None) → int

Send data to address over UDP.

Parameters:
  • data – Raw byte array or string to send.
  • address – Address to send data to, with format tuple (ipv4_address, port). If address is not specified, uses the address given to connect.
Returns:

The number of bytes sent, as an integer.

Example:

udp.send("test_send")
udp.send(b'')

BOFPacket base class

Interfaces with a packet as a Scapy object, with specific features.

A BOFPacket is a sort of wrapper around a Scapy Packet object, and implements specific features or changes relative to Scapy’s behavior when interacting with this packet.

The Scapy Packet is used as a basis for BOF to manipulate frames with its own syntax. You don’t need to know how to use Scapy to use BOF. However, you can still perform “Scapy stuff” on the packet by directly accessing BOFPacket.scapy_pkt attribute.

Note

BOFPacket DOES NOT inherit from Scapy packet, because we don’t need a “specialized” class, but a “translation” from BOF usage to Scapy objects.

Example (keep in mind that BOFPacket should not be instantiated directly :)):

pkt = BOFPacket(scapy_pkt=ScapyBasicOtterPacket1())
print(pkt.scapy_pkt.basic_otter_1_1, pkt.basic_otter_1_1) # Same output
pkt.basic_otter_1_1 = "192.168.1.2" # Not the expected type, BOF converts it
pkt.show2()
class bof.packet.BOFPacket(_pkt: bytes = None, scapy_pkt: scapy.packet.Packet = None, **kwargs)

Bases: object

Base class for BOF network packet handling, to inherit in subclasses.

This class should not be instantiated directly but protocol-specific Packet classes in BOF shall inherit it. It acts as a wrapper around Scapy-based packets in the specified protocol, either relaying, replacing or modifying Scapy default behaviors on Packets and Fields.

Parameters:
  • _pkt – Raw Packet bytes used to build a packet (mostly done at reception, but you can manually create a packet from bytes)
  • scapy_pkt – Actual Scapy Packet object, used by BOF for protocol implementation-related stuff. Can be referred to directly to do “Scapy stuff” inside BOF.
  • kwargs – Field values to set when instantiating the class. Format is field_name=value, .... If two fields have the same name, it sets the first one.

Example:

class OtterPacket(BOFPacket)
append(other: object, autobind: bool = False, packet=None, value=None) → None

Adds either a BOFPacket, Scapy Packet or Field to current packet.

Parameters:
  • other – BOFPacket or Scapy Packet or field to append as payload.
  • autobind – Whether or not unspecified binding found in Scapy implementation are automatically added.
  • packet – Packet at to append other to.
  • value – Value to set to a newly-created field.
Raises:

BOFProgrammingError – if type is not supported.

copy()

Copies the current instance by rebuilding it from its bytes. Works appropriately only if the original packet is valid. Any attribute not strictly bound to bytes is ignored, you should add it.

Example:

copy_of_pkt = self.copy()
copy_of_pkt.show2() # Should be the same thing as self.show2()
fields

Returns the list of field objects in a BOFPacket.

Can be used to retrieve the list of fields as a name list with:

[x.name for x in pkt.fields]
fuzz(iterations: int = 0, include: list = None, exclude: list = None)

Generator function. Sets a random value to a random field in packet.

Parameters:
  • iterations – Number of packet to create (default is infinite loop)
  • include – List of field names to include to fuzzing.
  • exclude – List of field names to exclude from fuzzing.

Example:

pkt = KNXPacket(type="configuration request")
for frame in pkt.fuzz():
  print(frame)
get(*args) → object

Get a field either from its name, partial or absolute path.

Partial indicates part of the absolute path, in other words where the search for the field should start from.

Parameters:args – Can take from one to many arguments. The last argument must be the field you look for. Previous “path” arguments must be in the right order (even if the path is not complete).
Raises:BOFProgrammingError – If field not found or not supported.
length

Returns the length of the packet (number of bytes).

scapy_pkt
type

Get information about the packet’s type (protocol-dependent).

Should be overriden in subclasses to match a protocol’s different types of packets. For instance, BOF’s packet for the KNX protocol (KNXPacket) returns the type of packet as a name, relying on its identifier fields. If identifier is 0x0203, pkt.type indicates that the packet is a DESCRIPTION REQUEST.

update(value: object, *args) → None

Set value to a field either from its name, partial or absolute path.

Partial indicates part of the absolute path, in other words where the search for the field should start from.

Parameters:
  • value – The value to set to the field. If the type does not match, the type of field will be changed.
  • args – Can take from one to many arguments. The last argument must be the field you look for. Previous “path” arguments must be in the right order (even if the path is not complete).
Raises:

BOFProgrammingError – If field not found or not supported.

BOFDevice base class

Global object for representing industrial devices.

All objects in layers built using data extracted from responses to protocol-specific discovery requests shall inherit BOFDevice.

class bof.device.BOFDevice(name: str = None, description: str = None, mac_address: str = None, ip_address: str = None)

Bases: object

Interface class for devices, to inherit in layer-specific device classes.

Device objects are usually built from device description requests in layers. A device has a set of basic information: a name, a description, a MAC address and an IP address. All of them are attributes to this base object, but not all of them may be provided when asking protocols for device descriptions. On the other hand, most of protocol-specific devices will have additional attributes.

description = None
ip_address = None
mac_address = None
name = None
protocol = 'BOF'