Skip to content

Commit

Permalink
feat(src): add support for OpenBSD
Browse files Browse the repository at this point in the history
- add OpenBSD-specifics to route socket handler
- add rc.d daemon file
- update README

Tested on riscv64 with OpenBSD 7.4
  • Loading branch information
christgau committed Mar 30, 2024
1 parent 2a004b7 commit c96e941
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

### Added

- Support for OpenBSD (tested on riscv64 with OpenBSD 7.4)
- Configuration files for firewalld (#186). Thanks to Ondrej Holy.
- Show device type and allow filtering in API's `list` command (#189). Thanks to Ondrej Holy.
- Add option `--metadata-timeout` to set the timeout for the HTTP-based metadata exchange (closes #83)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -37,8 +37,8 @@ Samba at some time in the future.

# Requirements

wsdd requires Python 3.7 and later only. It runs on Linux, FreeBSD and MacOS. Other Unixes, such
as OpenBSD or NetBSD, might work as well but were not tested.
wsdd requires Python 3.7 and later only. It runs on Linux, FreeBSD, OpenBSD and MacOS.
Other Unixes, such as NetBSD, might work as well but were not tested.

Although Samba is not strictly required by wsdd itself, it makes sense to run
wsdd only on hosts with a running Samba daemon. Note that the OpenRC/Gentoo
Expand Down
File renamed without changes.
25 changes: 25 additions & 0 deletions etc/rc.d/wsdd.openbsd
@@ -0,0 +1,25 @@
#!/bin/ksh

workgroup=$(/usr/local/bin/testparm -s --parameter-name workgroup 2>/dev/null)
samba_config_file="/etc/samba/smb.conf"

if [ -z ${workgroup} ] && [ -r "${samba_config_file}" ]; then
workgroup=$(grep -i -E '^[[:space:]]*workgroup' "${samba_config_file}" | cut -f2 -d= | tr -d '[:blank:]')
fi

if [ -n "$workgroup" ]; then
daemon_flags="-w ${workgroup}"
fi

daemon="python3 /usr/local/bin/wsdd"

# create user with
# doas useradd -g =uid -c "WSD Daemon" -L daemon -s /sbin/nologin -d /var/empty -r 100..999 _wsdd
daemon_user="_wsdd"

. /etc/rc.d/rc.subr

rc_bg="YES"
rc_reload="NO"

rc_cmd $1
52 changes: 38 additions & 14 deletions src/wsdd.py
Expand Up @@ -287,8 +287,10 @@ def init_v4(self) -> None:
self.uc_send_socket.bind((self.address.address_str, WSD_UDP_PORT))

self.mc_send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, mreq)
self.mc_send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)
self.mc_send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, args.hoplimit)
# OpenBSD requires the optlen to be sizeof(char) for LOOP and TTL options
# (see also https://github.com/python/cpython/issues/67316)
self.mc_send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, struct.pack('B', 0))
self.mc_send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, struct.pack('B', args.hoplimit))

self.listen_address = (self.address.address_str, WSD_HTTP_PORT)

Expand Down Expand Up @@ -1580,7 +1582,7 @@ def cleanup(self) -> None:

# from sys/net/if.h
IFF_LOOPBACK: int = 0x8
IFF_MULTICAST: int = 0x800
IFF_MULTICAST: int = 0x800 if platform.system() != 'OpenBSD' else 0x8000

# sys/netinet6/in6_var.h
IN6_IFF_TENTATIVE: int = 0x02
Expand All @@ -1595,29 +1597,39 @@ class RouteSocketAddressMonitor(NetworkAddressMonitor):
Implementation of the AddressMonitor for FreeBSD and Darwin using route sockets
"""

# Common definition for beginning part of if(m?a)?_msghdr structs (see net/if.h).
IF_COMMON_HDR_DEF: str = '@HBBii'
# Common definition for beginning part of if(m?a)?_msghdr structs (see net/if.h/man 4 route).
IF_COMMON_HDR_DEF = '@HBBii' if platform.system() != 'OpenBSD' else'@HBBHHHBBiii'

# from net/if.h
RTM_NEWADDR: int = 0xC
RTM_DELADDR: int = 0xD
# not tested for OpenBSD
RTM_IFINFO: int = 0xE

# from route.h (value equals for FreeBSD, Darwin and OpenBSD)
RTM_VERSION: int = 0x5

# from net/if.h (struct ifa_msghdr)
IFA_MSGHDR_DEF: str = IF_COMMON_HDR_DEF + 'hi'
IFA_MSGHDR_DEF: str = IF_COMMON_HDR_DEF + ('hi' if platform.system() != 'OpenBSD' else '')
IFA_MSGHDR_SIZE: int = struct.calcsize(IFA_MSGHDR_DEF)

IF_MSGHDR_DEF_BASE: str = IF_COMMON_HDR_DEF + 'h'
# The struct package does not allow to specify those, thus we hard code them as chars (x4).
IF_DATA_DEFS: Dict[str, str] = {
IF_MSG_DEFS: Dict[str, str] = {
# if_data in if_msghdr is prepended with an u_short _ifm_spare1, thus the 'H' a the beginning)
'FreeBSD': 'H6c2c8c8c104c8c16c',
'FreeBSD': 'hH6c2c8c8c104c8c16c',
# There are 8 bytes and 22 uint32_t in the if_data struct (22 x 4 Bytes + 8 = 96 Bytes)
# It is also aligned on 4-byte boundary necessitating 2 bytes padding inside if_msghdr
'Darwin': '2c8c22I'
'Darwin': 'h2c8c22I',
# struct if_data from /src/sys/net/if.h for if_msghdr
# (includes struct timeval which is a int64 + long
'OpenBSD': '4c3I13Q1Iql'
}

socket: socket.socket
intf_blacklist: List[str]

is_openbsd: bool = False

def __init__(self, aio_loop: asyncio.AbstractEventLoop) -> None:
super().__init__(aio_loop)
self.intf_blacklist = []
Expand All @@ -1627,7 +1639,8 @@ def __init__(self, aio_loop: asyncio.AbstractEventLoop) -> None:
self.socket = socket.socket(socket.AF_ROUTE, socket.SOCK_RAW, socket.AF_UNSPEC)
self.aio_loop.add_reader(self.socket.fileno(), self.handle_change)

self.IF_MSGHDR_SIZE = struct.calcsize(self.IF_MSGHDR_DEF_BASE + self.IF_DATA_DEFS[platform.system()])
self.IF_MSGHDR_SIZE = struct.calcsize(self.IF_COMMON_HDR_DEF + self.IF_MSG_DEFS[platform.system()])
self.is_openbsd = platform.system() == 'OpenBSD'

def do_enumerate(self) -> None:
super().do_enumerate()
Expand Down Expand Up @@ -1661,9 +1674,20 @@ def parse_route_socket_response(self, buf: bytes, keep_intf: bool) -> None:
intf_flags = 0
while offset < len(buf):
# unpack route message response
rtm_len, _, rtm_type, addr_mask, flags = struct.unpack_from(self.IF_COMMON_HDR_DEF, buf, offset)
if not self.is_openbsd:
rtm_len, rtm_version, rtm_type, addr_mask, flags = struct.unpack_from(
self.IF_COMMON_HDR_DEF, buf, offset)
else:
rtm_len, rtm_version, rtm_type, ifa_hdr_len, _, _, _, _, addr_mask, flags, _ = struct.unpack_from(
self.IF_COMMON_HDR_DEF, buf, offset)

# exit condition for OpenBSD where always the complete buffer (ie 4096 bytes) is returned
if rtm_len == 0:
break;

if rtm_type not in [self.RTM_NEWADDR, self.RTM_DELADDR, self.RTM_IFINFO]:
# skip over non-understood packets and versions
if (rtm_type not in [self.RTM_NEWADDR, self.RTM_DELADDR, self.RTM_IFINFO]) or (
rtm_version != self.RTM_VERSION):
offset += rtm_len
continue

Expand Down Expand Up @@ -1946,7 +1970,7 @@ def drop_privileges(uid: int, gid: int) -> bool:
def create_address_monitor(system: str, aio_loop: asyncio.AbstractEventLoop) -> NetworkAddressMonitor:
if system == 'Linux':
return NetlinkAddressMonitor(aio_loop)
elif system in ['FreeBSD', 'Darwin']:
elif system in ['FreeBSD', 'Darwin', 'OpenBSD']:
return RouteSocketAddressMonitor(aio_loop)
else:
raise NotImplementedError('unsupported OS: ' + system)
Expand Down
21 changes: 15 additions & 6 deletions test/routesocket_monitor.py
Expand Up @@ -6,6 +6,7 @@
import socket
import struct
import ctypes.util
import platform

# from sys/net/route.h
RTM_NEWADDR = 0xC
Expand All @@ -20,7 +21,7 @@

# from sys/net/if.h
IFF_LOOPBACK = 0x8
IFF_MULTICAST = 0x800
IFF_MULTICAST = 0x800 if platform.system() != 'OpenBSD' else 0x8000

SA_ALIGNTO = ctypes.sizeof(ctypes.c_long)

Expand All @@ -34,18 +35,26 @@ def parse_route_socket_response(buf, keep_link):
link = None
print(len(buf))
while offset < len(buf):
# mask(addrs) has same offset in if_msghdr and ifs_msghdr
rtm_len, _, rtm_type, addr_mask, flags = struct.unpack_from(
'@HBBii', buf, offset)
if platform.system() != 'OpenBSD':
# mask(addrs) has same offset in if_msghdr and ifs_msghdr
rtm_len, _, rtm_type, addr_mask, flags = struct.unpack_from(
'@HBBii', buf, offset)
else:
rtm_len, _, rtm_type, hdr_len, if_idx, ignore_table, ignore_prio, ignore_mlps, addr_mask, flags, change_mask = struct.unpack_from(
'@HBBHHHBBiii', buf, offset)

msg_type = ''
if rtm_type not in [RTM_NEWADDR, RTM_DELADDR, RTM_IFINFO]:
offset += rtm_len
continue

# those offset may unfortunately be architecture dependent
# (152 is FreeBSD-specific)
sa_offset = offset + ((16 + 152) if rtm_type == RTM_IFINFO else 20)
if platform.system() != 'OpenBSD':
# (152 is FreeBSD-specific)
sa_offset = offset + ((16 + 152) if rtm_type == RTM_IFINFO else 20)
else:
# RTM_IFINFO not tested, offset might be wrong
sa_offset = offset + ((16 + 152) if rtm_type == RTM_IFINFO else 24)

if rtm_type in [RTM_NEWADDR, RTM_IFINFO]:
msg_type = 'NEW'
Expand Down

0 comments on commit c96e941

Please sign in to comment.