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:
- 20 repositories published at
github.com/AndreiGosman/*-macos-arm64. - 9-daemon Osmocom 2G+3G core stack runs on B220 at ARFCN 871 DCS1800.
- Iu-CS Location Update Accept and Iu-PS GMM Attach complete against a patched
hnb-test. - GTP-C Create PDP Context accepted, utun tunnel up, address handed to the UE.
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:
- A:
DYLD_INSERT_LIBRARIES(Darwin equivalent ofLD_PRELOAD). Rejected: insertion runs in front of everything includingusrsctp‘s ownsendmsgcalls for UDP encapsulation, so the shim would recurse. SIP restricts insertion on system binaries. - B: export
sendmsg,recvmsg,acceptas strong symbols fromlibsctp.dylib. Consumers linked against-lsctppick them up. Consumers reaching libc directly do not.
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:
INFO_IND1007 bytes at connect: delivered as one.- One message written in two chunks: delivered as one.
- Two messages written together: delivered as two.
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:
osmo-msc: 12msc_vlr_test_*groups plussms_queue.osmo-bsc:handover_test,paging.osmo-pcu:AllocTest,TbfTest,AppInfoTest.osmo-sgsn:sgsn_test(5 wraps),gtphub_test(3 wraps).
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:
- Congestion window bytes to segments (divide by MSS).
- RTT, RTO: ms to us.
tcpi_pmtu,tcpi_unacked: zero. Darwin does not expose them. Deriving fromtcpi_snd_sbbytesis plausible and wrong.
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:
-lrton link lines. Darwin has no librt. Osmocom code usesosmo_clock_gettime(). Remove, or gate withAC_SEARCH_LIBS([clock_gettime], [rt]).sched_setscheduler(2). Replace withpthread_setschedparamon the current thread.signalfd(2). Async handler writes signal number to a pipe, read end in the select loop.<malloc.h>andmemalign. Use<stdlib.h>andposix_memalign.<values.h>. glibc-only, usually unused. Remove.pthread_setname_np: one argument on Darwin (current thread only), not two.- GNU sed
\|alternation in BRE,-iwithout backup suffix. Both broken on BSD sed. - GNU coreutils
install -D -t. Use$(MKDIR_P) + $(INSTALL_SCRIPT). echo -ein/bin/sh. Useprintf '%s\n'.
Subtler:
_POSIX_C_SOURCEwithout_DARWIN_C_SOURCEputs the Darwin SDK in strict POSIX mode. Symptom:#error "Unknown endian"from libosmocoreendian.h, then structs with bit-fields undergsm_04_08.hlose their members. Found inosmo-hlrdb_hlr.c.AF_UNIX SOCK_SEQPACKET, see Problem 7.AF_X25unknown on Darwin. X.25 isAF_CCITTvalue 10.#define AF_X25 AF_CCITT.AF_INET6numeric value: 10 on Linux, 30 on Darwin. Tests must print names, not numbers.- Linux-only errno values:
ENOKEY,ENAVAIL,EBADFD,ENOMEDIUM. Alias with attention to caller semantics.ENOKEYis distinct fromENOENTinosmo-hlrdb.c(four switches); alias toENOATTR(93), notENOENT.
Two Osmocom-specific:
AC_ARG_ENABLE(sysmocom-bts, ...)inosmo-btssetsenable_sysmocom_bts="yes"also for--disable-sysmocom-bts. Third and fourth macro arguments are reversed. Same pattern for--enable-trx,--enable-octphy,--enable-litecell15,--enable-oc2g. Verify by reading the configure summary..tarball-versionwithgit-version-gen --dirtyreports-dirtyaftermake cleanbecause the git index has stale stat data. Fix:git update-index -q --refreshbeforeautoreconf -fi.
Full grep list in the consolidated handover.
Iu-CS and Iu-PS control plane
Requirements: osmo-msc --enable-iu needs libasn1c and libosmo-ranap.
libasn1c: 1 patch.-no-undefinedbecomes empty on Darwin. libtool then links with-undefined dynamic_lookup. Library exportstalloc_asn1_ctxas an extern the application defines. Apple ld64 refuses undefined externs in a shared object under-no-undefined.libosmo-ranap: 3 patches.AF_X25/AF_INET6numeric family in tests (Problem 11), BSD sed-i(src/Makefile.am), missingRequires: libosmo-ranapin.pcforhnbap/rua/sabp.osmo-hnbgw: 0 patches. Mirror trap: GitHub mirrorHEADpoints at a working branch from 2022, notmaster. Tag1.9.0is onorigin/master.git checkout -b main 1.9.0before push.
5-daemon 3G core up:
osmo-hnbgw show cnlink:IuCS: msc-0 PC=0.23.5 <-> PC=0.23.1, RANAP state: CONNECTED(RESET/RESET ACK through STP).osmo-msc show fsm-instances all:ran_peer(UTRAN-Iu:...)READY.
hnb-test (demo peer in osmo-iuh) needed 7 patches on branch option-b:
- 0001
gen_nas_auth_resp()hardcodesN(SD)=2on Authentication Response (comment:simulate sequence nr 2). osmo-msc TS 24.007 duplicate detection drops it:Duplicate DTAP: bin=0, expected n_sd == 1, got 2. LU Reject after 5 s. Wrong since 2018 when duplicate detection landed on osmo-msc;hnb-testis not in CI. One-byte fix:0x80 | AUTH_RESP→0x40 | AUTH_RESP. - 0002 Full Milenage RES (8 bytes) on UTRAN. osmo-msc refuses 4-byte SRES:
AUTH via UTRAN, cannot allow GSM AKA. Route NAS replies by CN domain so a PS connection can answer. - 0003 LU Accept TLV parse after mandatory LAI. Upstream
tlv_parsestarted on LAI, read wrong identity IE. - 0004 N(SD) counter across the whole uplink MM connection. TMSI Reallocation Complete was also dropped as duplicate before this. Squash 0001+0004 for upstream.
- 0005
hnb-test-gmm.cnew file.channel ps attach imsi IMSI, Identity Response, Auth+Ciph Response with RES and IMEISV, Attach Complete. - 0006
channel ps pdp-activate imsi IMSI apn APN. GMM Service Request with P-TMSI, SM Activate PDP Context Request on Service Accept, RAB Assignment Response stub with causeuser-plane-versions-not-supported. - 0007 CO RANAP in RUA DirectTransfer decoded with
ranap_ran_rx_co()(RAN-side) instead ofranap_cn_rx_co()(CN-side). RAN-side has cases for RAB Assignment, Common ID, Security Mode Control, Iu Release, Direct Transfer. Matches what an HNB receives from the CN.
Signalling verified 8 September:
- Iu-CS: TMSI Reallocation Complete, LU Accept,
show subscriberattached in osmo-msc,osmo-hlrlast LU seen on CS. - Iu-PS: Auth+Ciph, InsertSubscriberData over GSUP, Attach Complete with
P-TMSI f36a8828,osmo-hlrlast LU seen on PS. - GTP-C: Create PDP Context Response cause 128,
show pdp-context allactive on both GSNs, GTP Echo running.
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:
- Daemons:
osmo-hlr,osmo-stp,osmo-mgw,osmo-hnbgw,osmo-sgsn,osmo-ggsn,osmo-bsc,osmo-bts-trx,osmo-pcu. - 2G: PDCH TS6 and TS7 enabled, NS UNBLOCKED both ends.
- 3G control: IuCS state CONNECTED, IuPS state CONNECTED. GSUP up. GTP-C between SGSN and GGSN verified.
hnb-test Option B:
- Iu-CS LU Accept complete.
- Iu-PS GMM Attach complete.
- GTP-C Create PDP Context accepted. utun up. Address handed to UE. GTP Echo running.
- RAB Assignment Response returns cause
user-plane-versions-not-supported.osmo-sgsnreceives it and does not act: upstreamiu_client.cranap_handle_co_rab_ass_resp()returns -1 on RAB failure, no Delete PDP Context to GGSN, no Activate PDP Context Reject to UE. Separate upstream defect.
LTE downlink: lte_cell_measure on the same hardware. 5 cells measured.
Not verified:
- Real UE attach over the air.
- Kernel SCTP peer interop. Current cascade runs
usrsctpon both sides with UDP encapsulation. - srsRAN Project (5G) on macOS.