This brings xform_tcp.c into line with possible future OCF related imports.
Diff Detail
- Repository
- rG FreeBSD src repository
- Lint
Lint Skipped - Unit
Tests Skipped - Build Status
Buildable 75296 Build 72179: arc lint + arc unit
Event Timeline
This change has been exercised with bird3. And the tcpdump dissector I forgot I wrote almost ~22.25 years to the day...
In bird.conf, "setkey no" actually means "require external setkey", so use "yes" to save yourself a trip to the SADB when bird will do that for you.
Also, "authentication md5" is now required: [[ https://bird.network.cz/pipermail/bird-users/2025-August/018356.html
| sample ]] |
Hi Bruce,
I came across your recent work around TCP-MD5 / xform_tcp.c while investigating a limitation affecting FRR on FreeBSD.
FRR supports dynamic BGP neighbors through peer groups and bgp listen range. Its documentation explicitly describes this model:
For example, the configuration concept is:
neighbor PGNAME peer-group neighbor PGNAME password <secret> bgp listen range 10.0.0.0/24 peer-group PGNAME
FRR's documentation specifically notes that when a peer group used by a BGP listen range has TCP-MD5 authentication configured, the kernel must support TCP-MD5 authentication on prefixes. It also mentions that Linux has supported this since kernel 4.14.
On FreeBSD, TCP-MD5 currently appears to rely on per-host SA matching, so a dynamic peer discovered from a prefix cannot automatically use the shared key.
Since this part of the stack is already being actively worked on, would it be possible to also consider adding support for prefix-based key matching, or another mechanism that would allow a listening BGP socket to authenticate dynamically discovered peers using the same key?
This is particularly useful for modern dynamic infrastructure such as Kubernetes, where BGP-speaking nodes are routinely added, replaced and reprovisioned. It would also benefit FRR, BIRD and other routing software using dynamic BGP peers.
There is also a security motivation. Modern infrastructure increasingly needs defense in depth. Automation and AI-assisted vulnerability discovery are making weaknesses easier and faster to identify and exploit, so avoiding authentication simply because BGP peers are dynamically provisioned is becoming less desirable. Supporting authenticated dynamic peers would allow TCP-MD5 to remain an additional security layer without sacrificing automation.
I would be very interested in testing such functionality with FreeBSD/OPNsense/FRR in a real Kubernetes BGP deployment and providing feedback if this is something you would consider implementing.
I did some additional investigation into what might be required in FreeBSD to support TCP-MD5 authentication for BGP listen ranges / prefixes.
I want to explicitly disclose that I used OpenAI GPT-5.6 Sol to help inspect the relevant FreeBSD and FRR source code and prepare the preliminary analysis below.
I am not a FreeBSD kernel developer, so I cannot independently judge whether the proposed direction is appropriate for FreeBSD, and I am not presenting this as a validated design or patch. My hope is simply that the source-code observations may save you some investigation time. This is the best technical contribution I can currently make, and I am also happy to test any implementation with FreeBSD/OPNsense/FRR.
From the current FreeBSD main sources, the limitation appears to be mainly in SA storage and lookup rather than in the TCP-MD5 digest calculation itself.
xform_tcp.c obtains the real packet source/destination addresses and calls:
id="e9k3zf" sav = key_allocsa_tcpmd5(&saidx);
The returned sav->key_auth is then used to calculate the digest, so the actual TCP-MD5 calculation might not need significant changes.
The current lookup path appears to be exact-host based:
- the SAD hash uses the complete source address, destination address and protocol,
- key_allocsa_tcpmd5() compares exact addresses,
- struct secasindex does not retain source/destination prefix lengths.
At the same time, FreeBSD already seems to have several useful building blocks:
- PF_KEY sadb_address already contains sadb_address_prefixlen,
- the SPD already stores source/destination prefix lengths,
- key_sockaddrcmp_withmask() already provides masked IPv4/IPv6 address matching.
One possible direction suggested by this analysis could therefore be to preserve prefix lengths for TCP-MD5 SAs and add a prefix-aware fallback lookup while keeping the existing exact-host lookup as the fast path.
If multiple entries match, longest-prefix matching would provide predictable behaviour, e.g.:
id="wqk2bf" 10.0.0.0/16 key A 10.0.0.0/24 key B 10.0.0.17/32 key C
would select:
id="1r54dp" 10.0.0.17 -> C 10.0.0.55 -> B 10.0.5.20 -> A
The current address hash is based on full addresses, so simply changing the comparison to masked matching would probably not be sufficient. Prefix TCP-MD5 entries may need a separate lookup structure or another indexing strategy, while leaving the existing ESP/AH SAD path untouched.
There also appears to be a userspace part. setkey already supports prefixes for SPD entries and PF_KEY already carries the prefix length, but normal SA add handling currently appears to use full /32 or /128 addresses.
FRR would likely need a small corresponding FreeBSD-side change afterwards. On Linux it uses TCP_MD5SIG_EXT / TCP_MD5SIG_FLAG_PREFIX, while the BSD path already expects PF_KEY to manage the actual keys.
Again, please treat this only as AI-assisted source analysis, not as an informed FreeBSD kernel design proposal.
Thanks for considering it.
Sorry, I do not support FRR, and I do not use LLM driven tools in my development work, nor do I accept submissions which use them.
Why did you abandon? Sorry for not reviewing in timely manner. From a quick look change doesn't look bad.
It actually can be written as a 90% declarative function. The current version has both C99 initializer that is supposed to zero entire struct and then initialization of certain members. IDK if compiler is smart enough to optimize that. Here is a version where the structure is filled in one declaration (not tested):
static int
ip6_pseudo_compute(struct mbuf *m, MD5_CTX *ctx)
{
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
size_t hdrlen = sizeof(struct ip6_hdr) + (ip6->ip6_nxt == IPPROTO_UDP ? sizeof(struct udphdr) : 0); /* TCP over UDP */
struct ip6_hdr_pseudo ip6ph = {
.ip6ph_src = ip6->ip6_src,
.ip6ph_dst = ip6->ip6_dst,
.ip6ph_len = htonl(m->m_pkthdr.len - hdr_len),
.ip6ph_nxt = IPPROTO_TCP,
};
MD5Update(ctx, &ip6ph, sizeof(ip6ph));
return (hdr_len);
}