Continuation from Part 1

Part 1 stopped at srsRAN 4G downlink working on the LibreSDR B220 Mini. cell_search decoded MIB and SIB1/2/3 on live data.

Status two weeks later:

Problem 6: SCTP shim scope insufficient for Osmocom

Part 1 shipped libsctp-compat v0.1.0 with 8 symbols forwarded to usrsctp (sctp_bindx, sctp_connectx, sctp_sendmsg, relatives). Enough for srsRAN 4G. Not enough for Osmocom.

osmo-stp (M3UA signalling gateway) expects accept(2) to return a new socket per SCTP association. On Linux this reaches the kernel SCTP module. On Darwin there is no kernel SCTP module. usrsctp runs in userspace and does not know libc accept exists. osmo-stp called libc accept, got the internal socketpair endpoint the shim uses for poll integration, spun at 100% CPU on an fd that reported readable with no data.

Two design options:

Picked B. Single dylib, no env vars, no process-start interposition.

Implementation traps:

  1. Shim internally calls sendmsg and recvmsg to drive the socketpair. Those calls resolve to the shim’s own symbols and recurse. Route through private lsc_real_* wrappers. 2. usrsctp_set_ulpinfo returns 1 on success, not POSIX 0. The check != 0 reads success as failure.

Accept path redesign in v0.3.0. Upcall accepts the association, adopts the socket, queues under mutex, one token per queued entry. accept() pops from the queue, never blocks. v0.3.1: usrsctp notifications on the listening socket no longer route onto the accept pump. v0.3.2: SO_NOSIGPIPE becomes no-op to stop log flood.

Relink rule for consumers, non-obvious. libtool default on Apple ld-1267 places auto-added -lsctp after the object files. ld-1267 then binds interposed symbols to libSystem regardless of what libsctp offers. Shim bypassed. Verified with -Wl,-t.

Fix: pass -Wl,-lsctp in LDFLAGS at position. libtool keeps it ahead of the objects.

make LDFLAGS="-Wl,-undefined,dynamic_lookup \
             -Wl,-L$HOME/sdr-lab/local/lib \
             -Wl,-lsctp"

Applies to every consumer daemon rebuild after a shim symbol addition. make clean && make && make install alone does not rebind.

Problem 7: no AF_UNIX SOCK_SEQPACKET on Darwin

osmo-bts listens on /tmp/pcu_bts with SOCK_SEQPACKET. Preserves message boundaries. Linux implements it. Darwin returns EPROTONOSUPPORT (errno 43) from socket(2). Reproduced with a plain C program, no Osmocom code involved. Limit is in libc, not in libsctp-compat.

osmo-bts opens the listener unconditionally at startup. Daemon dies before BSC or TRX connect. Same call in osmo-pcu pcuif_sock.c (connect side), osmo-msc behind --pcu-socket, osmo-bsc behind external MNCC.

Fix: on EPROTONOSUPPORT, reopen as SOCK_STREAM. Install osmo_stream_srv_set_segmentation_cb returning 1007 (sizeof(struct gsm_pcu_if) in 1.12.0). All PCUIF messages are 1007 bytes on the wire; pcu_msgb_alloc does msgb_put on the union. libosmo-netif reassembly stitches partial reads.

Cross-process test with a Python client:

Linux path unchanged. Live on B220: OML up, RSL connected, POWERON, SETSLOT 0..7, PDCH TS6 and TS7 enabled after osmo-pcu connects. 95 s traffic, zero framing errors.

Problem 8: -Wl,--wrap is GNU ld and lld only

Osmocom tests use -Wl,--wrap=symbol to substitute a function with a test double at link time. Apple ld does not implement --wrap.

Affected test groups, 12 total across 4 daemons:

Fix: same pattern upstream uses for the opt-in smpp group. AC_LINK_IFELSE probe in configure.ac (void foo() {} int main() { return __wrap_foo != 0; }), AM_CONDITIONAL(HAVE_LD_WRAP) around the test SUBDIRS, exit 77 in testsuite.at. 77 is the automake skip code.

macOS builds complete clean. Wrapped groups reported skipped. VLR regression coverage on macOS deferred to Linux CI.

Problem 9: no SCTP_INFO, plus sin_len trap

libosmo-sigtran reports SCTP association tuning via getsockopt(SCTP_INFO), returning struct tcp_info (upstream naming, TCP struct on an SCTP getsockopt). Darwin has neither.

Darwin has TCP_CONNECTION_INFO and struct tcp_connection_info, for TCP proper.

Fix: single accessor in ss7_asp_get_tcp_info(). On Darwin, getsockopt(TCP_CONNECTION_INFO) on the underlying socket the shim exposes. Field mapping:

Separate sin_len bug in osmo-sgsn integration. osmo_sockaddr_cmp() in libosmocore does memcmp on struct sockaddr_in. Darwin fills sin_len on recvfrom(), leaves it zero on configuration copies. osmo-pcu sends NS RESET, osmo-sgsn replies RESET_ACK, osmo-pcu logs Ignoring NS RESET ACK from newconnection for non-existing NS-VC. memcmp returned -16 for two addresses logically equal.

Fix: compare by field (port, address; for IPv6 also flowinfo, scope_id). Affects all BSDs. Upstream candidate patch 011 in libosmocore-macos-arm64.

Problem 10: utun creation, addressing, root

osmo-ggsn allocates addresses to UEs through a Linux TUN interface. Darwin has utun. Three distinct issues.

utun creation

Linux: open("/dev/net/tun") plus ioctl.

Darwin sequence:

  1. socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL). 2. ioctl(CTLIOCGINFO) on com.apple.net.utun_control. 3. connect(2) with the returned ctl_id. 4. Kernel picks the name (utun6 here). getsockopt(UTUN_OPT_IFNAME) reads it back.

Every packet on the wire carries a 4-byte AF prefix. Strip on read, prepend on write. readv/writev with two-element iovec avoids an extra copy. Consumer sees plain IP.

Addressing

SIOCAIFADDR on IFF_POINTOPOINT requires a destination. Darwin’s struct ifaliasreq has no ifra_dstaddr; destination goes in ifra_broadaddr. On a P2P interface, destination equals the address itself. Matches ifconfig utun6 inet 10.45.0.1 10.45.0.1 netmask 255.255.0.0.

Darwin installs only the host route. Linux derives the prefix route from the address. Extra step: PF_ROUTE RTM_ADD with the prefix mask after the address is set. Patch 012 in the libosmocore kit.

Root

utun creation requires root. First end-to-end run used a passwordless sudoers drop-in scoped to the daemon binary at /etc/sudoers.d/osmo-ggsn.

Log output on first successful run:

Blacklist tun IP 10.45.0.1/16
utun6: inet 10.45.0.1 --> 10.45.0.1 netmask 0xffff0000
Create PDP Context Response, cause 128 (Request accepted)
PDP Address: IPv4 10.45.0.2

show pdp-context all active on both osmo-sgsn and osmo-ggsn. GTP echo running.

Bug found in the same run

osmo-ggsn pool blacklist reads the interface name from config (tun-device tun4) and looks up addresses under that name. Kernel named the interface utun6. Lookup returned empty. Pool allocator handed the tun’s own address to the first UE.

Fix in patch 007 of osmo-ggsn-macos-arm64: read the name from osmo_tundev_get_dev_name() (kernel-assigned). Same for the IPv6 link-local lookup that made inet6 and inet46 APNs refuse to start.

Problem 11: GNU-isms checklist

Each Osmocom port turned up two or three GNU-isms Linux hides. After the sixth port, the pattern is a checklist. Most are not Osmocom bugs, they are portability defects any BSD user hits.

Recurring:

Subtler:

Two Osmocom-specific:

Full grep list in the consolidated handover.

Iu-CS and Iu-PS control plane

Requirements: osmo-msc --enable-iu needs libasn1c and libosmo-ranap.

5-daemon 3G core up:

hnb-test (demo peer in osmo-iuh) needed 7 patches on branch option-b:

Signalling verified 8 September:

Not verified: user plane from a real UE. hnb-test has no Iu-UP handling.

LTE measurement kit

Independent thread on the srsRAN 4G side, 22 September.

Landon Messer (SigInt-OS) maintains a fork of srsRAN_4G with lte_cell_measure. Self-contained utility. Measures RSRP, RSRQ, SNR, CFO from a target cell over a chosen bandwidth. One JSON line per measurement. Downlink-only. Not in upstream srsRAN.

Port scope: 2 files, +1227 lines. lib/examples/CMakeLists.txt modified. lib/examples/lte_cell_measure.c new. No external dependencies. No touch on srsue, srsenb, srsepc, srsgNB, lib/src/phy, lib/src/common. Cherry-pick both files, drop the unbuilt cell_monitor target the branch also declares, build. 3 s wall-clock.

Empirical run on LibreSDR B220, SIRIO SO 4G LTE-M3 antenna on RX A, indoors, worked just fine.

Upstream defect found. srsran_band_get_fd_band computes end - start. For a single EARFCN, -s N -e N answers No EARFCNs for band. Workaround at runtime: -e N+1.

Published as patch 026 in AndreiGosman/srsRAN-4G-macos-arm64.

Current lab state

20 repositories at github.com/AndreiGosman/*-macos-arm64.

9-daemon Osmocom stack on B220, ARFCN 871 DCS1800:

hnb-test Option B:

LTE downlink: lte_cell_measure on the same hardware. 5 cells measured.

Not verified: