java-topology/defects/openbsd/patch/openbsd-0002-ifa-ifwithaddr-nested-scan.md

106 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000200
# openbsd-0002 — `if.c`: O(I×A) nested scan in `ifa_ifwithaddr`
## Status
PATCHED
## Severity
HIGH (called per-packet in ip_input.c, icmp6.c, in_pcb.c, ip_output.c; O(I×A) per
lookup where I=interface count, A=addresses per interface)
## Location
`sys/net/if.c`, function `ifa_ifwithaddr()` (~line 1619)
## Description
`ifa_ifwithaddr` locates an interface address matching a given `sockaddr` by
performing a nested iteration over all interfaces × all addresses per interface:
```c
struct ifaddr *
ifa_ifwithaddr(const struct sockaddr *addr, u_int rtableid)
{
struct ifnet *ifp;
struct ifaddr *ifa;
u_int rdomain = rtable_l2(rtableid);
TAILQ_FOREACH(ifp, &ifnetlist, if_list) { // O(I)
if (ifp->if_rdomain != rdomain)
continue;
TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { // O(A)
if (ifa->ifa_addr->sa_family != addr->sa_family)
continue;
if (equal(addr, ifa->ifa_addr))
return (ifa);
}
}
return (NULL);
}
```
This is O(I×A) per call. The function is called in **packet-reception hot paths**:
- `sys/netinet/ip_input.c` — raw IP input (line 1265, 1399)
- `sys/netinet6/in6_pcb.c` — IPv6 PCB bind (line 191)
- `sys/netinet/in_pcb.c` — IPv4 PCB bind (line 402)
- `sys/netinet/ip_output.c` — IP output multicast (line 1485)
- `sys/netinet/ip_icmp.c` — ICMP error path (line 756)
- `sys/netinet6/icmp6.c` — ICMPv6 path (line 1150)
- `sys/net/route.c` — route add/check (line 709)
On a system with 50 interfaces (each with 3 addresses), each call scans up to 150
entries. Under high packet rates (100k pps), this results in 15M comparisons/sec.
## Complexity
- Slow: O(I × A) per address lookup
- Fast: O(1) amortized with a hash table keyed by `(af, addr)` per rdomain
- At I=50, A=3: 150 comparisons → ~1 with hash
- At I=100, A=10: 1000 comparisons → ~1 with hash
## Root Cause
`ifa_ifwithaddr` predates the routing table infrastructure. Modern OpenBSD routing
uses an RB_TREE (`rtable_lookup`) for route lookups, but local address resolution
still uses the flat `ifnetlist` scan.
## Fix
Add a `struct ifaddr_hash` — a per-rdomain hash table mapping `(af, sa_data[N])`
`struct ifaddr *`. Maintain this hash on `if_addra`/`if_deladdr` events.
`ifa_ifwithaddr` becomes a single hash lookup.
Alternatively: use the existing `rtable` infrastructure —
`rtable_lookup(rtableid, addr, NULL, NULL, RTV_PROTO_LOCAL)` already performs
O(log N) or O(1) lookup for local addresses if the local host route is installed.
## Patch (conceptual — C)
```c
/* Add to sys/net/if.h: */
void ifa_hash_insert(struct ifaddr *);
void ifa_hash_remove(struct ifaddr *);
struct ifaddr *ifa_ifwithaddr(const struct sockaddr *, u_int);
/* In sys/net/if.c: */
#define IFA_HASH_SIZE 256
#define IFA_HASH(sa) /* hash sa_family + first 4 bytes of sa_data */
static struct ifaddr_list ifa_hashtbl[IFA_HASH_SIZE];
struct ifaddr *
ifa_ifwithaddr(const struct sockaddr *addr, u_int rtableid)
{
unsigned int h = IFA_HASH(addr);
struct ifaddr *ifa;
NET_ASSERT_LOCKED();
LIST_FOREACH(ifa, &ifa_hashtbl[h], ifa_hash) {
if (ifa->ifa_ifp->if_rdomain != rtable_l2(rtableid))
continue;
if (ifa->ifa_addr->sa_family == addr->sa_family &&
equal(addr, ifa->ifa_addr))
return (ifa);
}
return (NULL);
}
```
## Patch file
See `openbsd-0002-ifa-ifwithaddr-nested-scan.patch`