Repository navigation
goip link kinds, write keywords and lwt encaps, with the nl_mon workbench (1373 parity rows) - #287
Merged
Merged
Conversation
…nk-kinds) goip-parity-gaps.md is finished, but its inventory counted objects and verbs, not link kinds: iproute2 7.2.0 has 39 iplink_*.c/link_*.c files and goip covers bond, vrf, dummy and veth. goip-link-kinds.md plans the 34 kinds a VM can create with a module, plus the one new write on the 7.2.0 pin, `ip link set dev X inet <devconf>…`, in the same three tiers (KC captures, KP parsers, KG goip and the matrix). goip-link-kinds-status.md tracks it (DOC001). KC0-T01 is planned in full: the kind modules join late_setup, which first sets net.core.fb_tunnels_only_for_init_net=2 so the namespaces the link-write driver builds afterwards get no fallback tunnels; the dump driver's tunnel set, which wants them, loads its modules earlier and is unchanged. A non-fatal `xtcp2-nlcap kinds` probe records which kinds the guest kernel creates. Corrects goip-parity-gaps.md: maddress moved to RTM_GETMULTICAST in b8cdda55, after v7.2.0, so it is still class F at the pin. Notes the pin bump there, points link-write-path.md's "small follow-up" at the new plan, and indexes both new docs in docs/netlink/README.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… close KC0 (KC0-T01) late_setup now sets net.core.fb_tunnels_only_for_init_net=2, loads the link-kind modules after every existing row, checks that a namespace made afterwards holds only lo and nlmon0, and runs a new `xtcp2-nlcap kinds` probe whose late/kinds sidecar records which kinds the guest kernel creates: 31 of 32 on 7.3.0, gtp refused (KC4). The link-write driver's one failure, genl/mptcp_limits_set_over, predates this task (kernel c8646664fbf1 raised the MPTCP limit to 64) and fails identically at 34724ad. KC1 is broken into KC1-T01 with its tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1-T01) 18 `ip link set dev d0 inet ...` rows in the rtnl corpus: one on/off and one numeric param, hex and octal values, medium_id's signed minimum, the kernel's range refusals, several and all 32 params in one nest, a repeated param, the push-back to iplink_parse, and seven refusals ip makes before netlink. Those exit with 255 (exit(-1)) or 1 (parse_on_off's error, negated by do_cmd), which the hermetic check now admits with floor 0 and no readback. KC2 is broken into KC2-T01 (the kind corpus, and bridge) and KC2-T02 (bridge_slave), with their tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kind-write-corpus.exp is the link kinds' write corpus: rows of
{slug floor pre command readbacks rc}, rtnl-write-corpus.exp's shape,
which capture-link-writes.exp runs into link-write/kinds/<kind>/<name>/
after its xfrm rows, once late setup has loaded the kind modules. Each
row the kernel answers reads the device back three ways (ip -d, ip -d -j
-p and ip link show).
bridge has 60 rows: the default, one add per value-taking option (39),
fdb_flush, 7.2.0's three stp_mode names, the boolopts, several options
in one add, four sets (one refused while STP runs), the abbreviation
rules, four kernel refusals and four refusals by ip. kind-write-corpus-
test.exp (34 rows, run by vm-lib-exp) pins them.
The first VM run found that bridge_parse_opt's returned errors exit 1,
not 255; the corpus and its test now say so, and the second run holds
every status, with all 39 options reading back as sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kind-write-corpus.exp gets 42 bridge_slave rows after bridge's, each an `ip link set dev p0 type bridge_slave …` on a dummy enslaved to br0 with p1 beside it: one set per value-taking port option (24), the two flags, 7.2.0's neigh_forward_grat, backup_port by name, the abbreviation rules, seven kernel refusals (state, priority and cost ranges, the MAC pause bit, MAB unlocked, a port backing itself up, a non-port) and four by ip. kind-write-corpus-test.exp grows to 51 rows. The VM run holds every planned status on the first try; every option but state reads back as sent (with STP off the bridge forwards any enabled port, so state 4 reads back forwarding). KC2 is closed, and KC3 is broken into vlan, macvlan/macvtap and ipvlan/ipvtap tasks with their tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 vlan rows in the kind corpus, stacked on a dummy d0: an add per option and flag, the qos maps, the id range, three sets (vlan_changelink ignores the id), the kernel's six refusals and ip's seven. The VM run held every planned status on the first try, and every value read back as sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
34 macvlan rows and 6 macvtap rows in the kind corpus, one parser for two kinds: each mode, both flag spellings, bcqueuelen and bclim (an s32), the source list added, set, deleted and flushed, the kernel's five refusals (passthru changed, a second passthru, sources outside source mode, a multicast source, a loopback lower) and ip's eleven. The VM run held every planned status on the first try, and every value read back as sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (KC3-T03) 18 ipvlan rows and 4 ipvtap rows in the kind corpus: each mode and flag word, the change path (a mode alone clears the flags), the kernel's three refusals (no lower, a loopback lower, a lower whose rx handler a macvlan holds) and ip's five. The VM run held every planned status on the first try. That closes KC3: 90 rows over five kinds. KC4 is broken into vxlan, geneve, bareudp and amt, and gtp, each with its rows argued from iplink_*.c at v7.2.0 and the kernel's checks. KC0's gtp refusal was its probe sending no option, so gtp stays in scope, and 7.2.0's geneve option is one keyword, local, for both families. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 vxlan rows in the kind corpus, the first kind with no lower of its own: id, dstport, remote and group in both families, local, ttl, tos, df, flowlabel, ageing, maxaddress, srcport, the thirteen flag words, external, vnifilter and gpe, three changes the kernel takes and four it refuses, its other eight refusals and ip's fifteen. The VM run held every planned status on the first try, and each readback differs from the base row only in what the row sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 geneve rows in kind-write-corpus.exp: the base and each option, 7.2.0's one `local` keyword with a row per family and `local6`/`loc` refused by ip, the externally controlled conflicts, the VNI and TTL ranges, and the sets the kernel allows and refuses. The VM holds every planned status on the first run (22 at 0, 13 at 2, 7 at 1, 7 at 255). kind-write-corpus-test.exp grows to 104 checks; 4 of 4 mutants killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 bareudp and 20 amt rows in kind-write-corpus.exp. bareudp's required dstport and ethertype are ip's missarg; amt's lower, mode and local are amt_validate's. Neither kind has a changelink, so both sets are -EOPNOTSUPP. The VM holds every planned status on the first run. kind-write-corpus-test.exp grows to 112 checks; 4 of 4 mutants killed. The plan's amt lower test now counts only the rows the kernel answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 gtp rows in the kind corpus: role, hsize (its 1<<17-1 top, and 0 meaning the default) and restart_count, an unknown word skipped against an abbreviated role, the second socket-owning device refused, and the set gtp has no changelink for. 7 accepted, 4 refused by the kernel and 8 by ip, each holding its planned status on the first VM run. KC4 is closed (V026-V037). KC5 is broken into five tasks, one per iproute2 parser or pair, with tables for the twelve IP tunnel kinds; the decision log records that fou is loaded and fou6 isn't, and that the guest kernel has no 6RD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 rows for the three kinds gre_parse_opt parses: every key, seq and csum form, ttl/tos and their aliases, pmtudisc and ignore-df, fwmark, fou encap, external, multicast keys, abbreviations, erspan's versions, index, direction and hwid, and the set path, where ip prefetches the device and resends its values. The refusals cover tunnel collisions, gre's point-to-point change, erspan's seq|key and 10-bit key checks, the collect_md extack, ip's usage text, broadcast check and invargs. 44 accepted, 16 refused by the kernel and 21 by ip, each holding its planned status on the first VM run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-T02) 64 rows for the three kinds gre_parse_opt parses in link_gre6.c: the hoplimit, tclass, flowlabel and encaplimit forms and their tops, the ip6_tnl flags, keys, fou and gue encaps (refused, as fou6 isn't loaded), external, erspan's versions, index, direction and hwid, and the prefetching set path. The refusals cover tunnel collisions (which ip6gretap's set doesn't check), erspan's version 0, seq|key, key and hwid checks, a set naming another kind, ip's usage text and invargs. 36 accepted, 14 refused by the kernel and 14 by ip, each holding its planned status on the first VM run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 rows from link_iptnl.c: 47 ipip and 37 sit. The first VM run found two ip behaviors the draft missed: 6rd-reset segfaults ip 7.2.0 (link_iptnl.c:288 hands get_prefix a string literal it writes into), so it has no row; and a set on sit's wildcard (ttl 64, no DF) exits 255 at the prefetch, so those rows expect 255 and set_remote_on_inherit keeps the kernel's point-to-point refusal. The test has 154 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 rows from link_ip6tnl.c: the modes and their aliases, the hex tclass and flowlabel, the inherit words, encaplimit none, the encap words (fou6 isn't loaded, so encap fou is refused), the prefetched set, the conflicts and ip's refusals. The test has 163 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…KC6 (KC5-T05) 58 rows: 23 vti, 12 vti6 and 23 xfrm, from link_vti.c, link_vti6.c and link_xfrm.c. The draft's bare-set test named three set_no_option rows but its table had two, so vti6 gains one. 27 accepted, 15 refused by the kernel and 16 by ip, each holding its planned status on the first VM run. The test has 178 checks. KC5 is closed (V038-V052). KC6 is broken into four tasks (hsr; netkit; vrf, vrf_slave and vxcan; batadv and the parser-less kinds) with 181 drafted rows. The decision log records that iplink.c's nodev returns (status 1), that a bare set reaches no kind code, that hsr's version/proto checks in ip test get_u8's return rather than the value, netkit's statics and vxcan's usage on stdout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
29 rows from iplink_hsr.c, each on dummy slaves d0 and d1: the supervision byte's range, version and proto (ip's check tests get_u8's return, so version 2 and proto 2 reach the kernel), PRP, the interlink, the abbreviations, hsr_newlink's extacks and the set hsr has no changelink for. All hold their planned statuses on the first VM run. The test has 186 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 netkit rows in the kind corpus, after hsr's: 19 the kernel accepts, 8 it refuses and 16 ip refuses before netlink. The sets naming the peer name the one their pre made (nt1), since a set looks the peer's name up. netkit's parser keeps state in statics, so its rows never run under ip -batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 vrf, 10 vrf_slave and 18 vxcan rows in the kind corpus, after netkit's: 18 the kernel accepts, 10 it refuses and 18 ip refuses before netlink. vrf_slave has no parser, so a word after its type is Garbage; the vxcan sets name the peer their pre made (vc1), since a set looks the peer's name up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… KC6, plan KC7 (KC6-T04) 17 batadv, 9 ifb, 6 nlmon, 13 vcan, 14 team and 4 team_slave rows in the kind corpus, after vxcan's: 29 the kernel accepts, 18 it refuses and 16 ip refuses before netlink. ifb, nlmon, vcan, team and team_slave have no parser, so a word after their type is Garbage, or usage() for a prefix of help. KC6 is done. KC7 becomes two tasks: commit a 7.3.0 link-write corpus beside 7.1.4's, then the completeness gate that reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…KC7-T01) One run of the link-write capture at the iproute2 7.2.0 pin on the 7.3.0 guest kernel, in testdata/7_3_0/link-write: 10558 files, every row of KC1 to KC6 included. Every status matches V064's run. TestKindRowsComplete checks that each kind row has its status and request, and has readbacks exactly when the kernel saw the request. Two provenance rows tie the set to kernel 7.3.0 and iproute2 7.2.0. corpusAll skips the new set: it recaptures the 7.1.4 rows, and 17 corpus tests tally one capture of each row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ier, plan KP0 (KC7-T02) TestLinkKindUniverse requires every link_util id in iproute2 7.2.0's ip/iplink_*.c and ip/link_*.c (46, not the plan's 48) to be captured in the 7.3.0 link-write corpus, or excluded with a reason. A kind is captured when one of its rows is at rc 0 with an ip.pcap and a -d -j readback whose linkinfo names it (info_slave_kind for a _slave kind). 39 are captured and 7 excluded. bond, bond_slave, dummy and veth predate the readbacks, so they pair their write/ rows with a shared -d -j dump. TestLinkKindCaptured drives the capture rule over built rows. Three mutants (stale case, _slave match, rc filter) are all killed. Docs: KC7-T02's section matches the gate; KC7 and the KC tier are done; KP0-T01, the inet devconf codec, is planned with its tables (DOC009). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ip link set dev X inet <param> <value>... is the one new write at the iproute2 7.2.0 pin. xtcpnl_inet_devconf.go adds: - InetDevconfParams: the 32 params iplink_parse_inet writes, in its order, 23 on/off and 9 numeric. - LinkAttrInetDevconf: the whole IFLA_AF_SPEC -> AF_INET -> IFLA_INET_CONF attribute. Only the innermost nest carries NLA_F_NESTED, and repeats are kept. - ParseInetDevconfRequest: the request's nested u32 settings. - ParseInetDevconfValues: the dump's raw IPV4_DEVCONF_MAX array, which is a different shape from the request. The encoder reproduces the 11 KC1 rows the kernel answered byte for byte, and the decoders read them back. Three mutants (NLA_F_NESTED dropped, repeats deduplicated, off-by-one index) are all killed. Docs: KP0 is done; KP1 is broken into the bridge and bridge_slave parsers with their tables (DOC010). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BridgeOptions is the bridge's option table in bridge_print_opt's order: 53 rows, IFLA_BR_MULTI_BOOLOPT as one per bit, with each row's wire, JSON key and match rule. ParseBridgeInfoData decodes IFLA_INFO_DATA into LinkInfo.Bridge; EncodeBridgeInfoData writes it as bridge_parse_opt does, the boolopt last. On the 7.3.0 kind corpus every accepted row's dump, rendered, equals its -d -j readback, and all 55 rows that sent a request are rebuilt byte for byte. vlan_protocol is big-endian on the wire. The keyword ageing_time is one constant, so misspell needs no exclusion. The new LinkInfo field moves linkmonitor's replay digests; regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_bridge_slave.go is the port's 44-row option table (36 printed attributes, mcast_fast_leave, fdb_flush, and six read-only rows for the counters ip never prints), ParseBridgeSlaveData into LinkInfo.BridgeSlave on IFLA_INFO_SLAVE_KIND, and EncodeBridgeSlaveData. Two new wires, on/off and ifindex, join the bridge's; the test helpers become a kindRows harness both kinds share. Every one of the 30 accepted rows' dumps renders as its -d -j readback, and the 37 rows that sent a request rebuild byte for byte. Mutants: flood as MCAST_FLOOD, neigh_forward_grat off by one, the branch keyed on Kind; 3 of 3 killed. Replay golden digests regenerated; value counts unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_vlan.go decodes the vlan kind's IFLA_INFO_DATA into LinkInfo.Vlan (id, big-endian protocol, the flags/mask pair, the two qos maps) and encodes it as vlan_parse_opt does: id, protocol and maps in argument order, then one IFLA_VLAN_FLAGS last. VlanFlags keeps vlan_print_flags' order, which is not bit order. All 15 accepted rows' dumps render as their -d -j readbacks, and the 21 rows that sent a request rebuild byte for byte. Mutants: protocol read little-endian, egress as ingress, egress unknown, flags in argument order; 4 of 4 killed. Replay golden digests regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_macvlan.go decodes both kinds' IFLA_INFO_DATA into LinkInfo.Macvlan (mode, flags, the broadcast queue lengths and cutoff, the source list) and encodes it as macvlan_parse_opt does: macaddr ops, bcqueuelen and bclim in argument order, then the last mode, then the flags, with ip's nopromisc-outside-passthru refusal. All 25 accepted rows' dumps render as their -d -j readbacks, and the 30 rows that sent a request rebuild byte for byte. Mutants: passthru and source swapped, the macvtap branch dropped, mode in argument order; 3 of 3 killed. Replay golden digests regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_ipvlan.go decodes both kinds' IFLA_INFO_DATA into LinkInfo.Ipvlan (mode and flags, read only at exactly 2 bytes, as ipvlan_print_opt does) and encodes it as ipvlan_parse_opt does: one MODE per mode word, then FLAGS always, 0 without a flag word, and nothing without options. All 14 accepted rows' dumps render as their -d -j readbacks, and the 17 rows that sent a request rebuild byte for byte. Mutants: FLAGS only with a flag word, vepa over private, the ipvtap branch dropped; 3 of 3 killed. Replay golden digests regenerated. KP2 is closed. KP3 (vxlan, geneve, bareudp and amt, gtp) is planned with its test tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ParseVxlanInfoData decodes the vxlan kind's IFLA_INFO_DATA into LinkInfo.Vxlan: the id, the group or remote and the local (GROUP over GROUP6, as vxlan_print_opt reads them), the big-endian ports, label and reserved bits, vxlan_opts' 14 u8 booleans, and the flag attributes. All 46 accepted rows' dumps render as their `ip -d -j` readbacks print them. EncodeVxlanInfoData(settings, set) follows vxlan_parse_opt: the words' attributes in argument order, then ID, the local, the group or remote, LEARNING (on an add always; on a set only after a learning word) and PORT (4790 for gpe without dstport). It rebuilds the request of all 58 rows that sent one byte for byte, and refuses ip's conflicts with ErrVxlanConflict. misspell reads the kernel's "ageing", so the keyword is VxlanKeywordAgeing and the id IflaVxlanAgeing, as for the bridge. Mutants 3 of 3 killed; the replay goldens' digests regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five kwlive spaces join wsO5Spaces; all 36 rows are the same. Four kwnh rows first differed only in padding: ipnexthop_modify builds the MPLS encap in an uninitialized stack buffer and rta_addattr_l leaves the alignment padding, so ip sends stack bytes after MPLS_IPTUNNEL_TTL and NHA_ENCAP_TYPE. wsO5ZeroEncapPad zeroes them before comparing, as wsO5ZeroMetricsPad does for RTA_METRICS, and the report counts them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GW5 forms tables read the kwlive spaces too, and the 20 forms that had no row pick 23 kwlive rows, one per form and ip rc; commands.go gains them as gw_kw* write rows, argv, setup and floor from each capture, as the last route rows, since the kwnh rows' encap mpls autoloads the MPLS modules. The vrf forms name their VRF, and the one-label MPLS form no longer matches ttl. The rows stay ungated until LF3-T02's runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first --section route,link,nexthop,rule run at 04bb605 passes 926 of 927 rows; gw_kwnh_ec_003 diverges on NHA_ENCAP, where ip's three pad bytes after MPLS_IPTUNNEL_TTL come from an uninitialised stack buffer (ipnexthop.c) and goip's are zero. A top-level attribute's value already stops at nla_len, so its padding is never compared. samePastPadding gives an NLA_F_NESTED payload the same treatment: two that parse to the end into the same attributes, in the same order with the same types and values (descending into inner nests), are the same. Anything else is still one value finding. Unflagged nests such as IFLA_AF_SPEC are unchanged. Ledger V016-V018; V013-V015 backfilled with 04bb605. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 3f4b704 a --section route,link,nexthop,rule run (927 of 927 PASS) and the full goip-parity run with the rows gated (1304 of 1304 PASS) are clean, so the 23 gw_kw rows go in gated_commands and TestAllowlistCommitted's earned list, and the GW matrix tables' gated check and TestUngatedSurfaceIsNotVacuous lose their gw_kw exceptions. Ledger V019-V021; V017-V018 backfilled with 3f4b704. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in vrf show NAME (VS0-VS4), the linkmonitor RDMA and learn deadline fixes, nicinfo uplink expansion, and the 0.2.0 release work. Two conflicts: - docs/netlink/README.md: both sides added rows; both kept. - internal/goip/service/service.go: main's VrfTableByName and this branch's VRFTable sent the same get. VrfTableByName is kept as main has it, and VRFTable, which route `vrf NAME` uses, is now a wrapper applying ipvrf_get_table's errno ladder to it, so the request is built in one place. A dump-fallback miss, which VrfTableByName reports as ENODEV, now takes the same ladder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
goip refuses a route's and a hop's `encap` and builds only `mpls` for a nexthop object. The plan builds lwt_parse_encap's mpls, ip and ip6 types, with the geneve/vxlan/erspan options, in all three places, then rows, captures, O3, O5, goip-parity rows and a VM gate. The other encap types are a follow-on. The closed plans' next actions point here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PutIPEncap writes parse_encap_ip's and parse_encap_ip6's nest: the items in argv order, the geneve/vxlan/erspan option nests, then LWTUNNEL_IP_FLAGS big-endian (TUNNEL_* are __cpu_to_be16). PutLwtEncap picks mpls or ip and writes nothing on a failure; EncodeLwtEncap gives a route's or a hop's RTA_ENCAP and RTA_ENCAP_TYPE as RouteAttrs within a bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lwtEncapArgs reads lwt_parse_encap's grammar for mpls, ip and ip6 with the geneve, vxlan and erspan options, giving `ip`'s refusal messages. The nexthop object's encap uses it; ip/ip6 there refuse until LE2-T02. dst any/all send an empty address, as get_addr gives them. geneve's class and type read base 0, so the plan's forms use 0x. Closes LE1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A route's encap and a hop's go where the keyword is, from lwtEncapArgs. ip's buffer bounds are refusals: addraw_l's 1024 bytes count the request so far, and a hop's what is left of parse_nexthops' 4096. O5's two fibt rows move from refuses to differs, on ip's stack padding after a hop's RTA_ENCAP_TYPE, which LE3-T03 zeroes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nexthopWrite.encap no longer refuses ip and ip6. EncodeLwtEncapAs takes the two attribute types, so NHA_ENCAP and NHA_ENCAP_TYPE share the route's encoder, under ipnh_modify's addraw_l bound of 1024 bytes over the request so far. mpls is unchanged. Closes LE2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-T01) Four spaces (lwtroute4, lwtroute6, lwthop, lwtnh) write the plan's 27 forms, 21 at rc 0, 2 at rc 2 and 4 at rc 255, and 12 duplicate adds to nix/microvms/workbench/lwt.rows.jsonl. Every row loads kwNhModules. The tunnel's id is tun_id in the axes, apart from a nexthop's id. The plan's counts are corrected to these. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3-T02) Two fresh-boot nlcap-ws batches of lwt.rows.jsonl agree on all 39 rows, every rc as written. Their only byte differences are ip's unzeroed padding, after RTA_ENCAP_TYPE and inside the geneve option nests, which LE3-T03's O5 zeroing covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
O3 checks each captured lwt request's encap against its axes: the type, each sub-attribute's width and byte order, the option nests and the flags. O5 reads the four lwt spaces, and wsO5ZeroEncapPad now reaches a route's RTA_ENCAP, each hop's in RTA_MULTIPATH and every nest inside them, so all 39 rows and the two fibt rows are same. Closes LE3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestGwLwtMatrixRows picks 23 lwt rows, one per encap form and ip rc, and commands.go gains them as gw_lwt* write rows after the kwnh rows. nlparity compares RTA_MULTIPATH hop by hop past each hop's padding, as the captures show ip's stack bytes after a hop's RTA_ENCAP_TYPE. The rows stay ungated until LE4-T02's runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 84260fe, two --section route,nexthop runs (124 of 124 PASS each) and the full goip-parity run with the rows gated (1330 of 1330 PASS) are clean, so the 23 gw_lwt rows go in gated_commands and TestAllowlistCommitted's earned list. The GW matrix tables' gated check and TestUngatedSurfaceIsNotVacuous lose their gw_lwt exceptions, and gwLwtSlugPrefixCst goes with them. Ledger V025-V029, V025 being the main merge's clean full run (1307 of 1307); V022-V024 backfilled with 84260fe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
goip-lwt-encap.md is closed, and lwtEncapArgs refuses every type but mpls, ip and ip6. This plan builds seg6, seg6local, ila, xfrm, ioam6 and rpl in all three places, a 56-form lwtx workbench family, O3, O5, goip-parity rows and a VM gate (LX1-LX4). The guest kernel (nixpkgs linux_latest 7.3) has SEG6_LWTUNNEL, SEG6_HMAC, ILA and XFRM_INTERFACE but not IOAM6_LWTUNNEL or RPL_LWTUNNEL, so the ioam6 and rpl rows compare the request at rc 2 (EOPNOTSUPP). The plan lists the argv quirks goip copies: xfrm swallowing the rest of the command, ioam6's and seg6local srh's end-of-argv step back, hmac ignoring a bad number, seg6 with no mode. Ledger DOC001. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_lwt_seg6.go builds parse_srh's segment routing header (SRH), encap seg6 (Seg6Encap: the int mode, the SRH, tunsrc) and encap seg6local (Seg6LocalEncap: the items in argv order, the counters and flavors nests, the SRH last). LwtEncap gains Seg6 and Seg6Local, and PutLwtEncap refuses anything but exactly one type set. SRH takes the slot count rather than parse_srh's encap flag: strtok skips an empty field between commas, so the slots and the addresses can differ, and slot 0 stays zero. TestSRH (11 rows) and TestPutSeg6Encap (18 rows), goldens hand-built from the uapi headers. Ledger V001-V003; DOC001 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xtcpnl_lwt_other.go builds encap ila (the locator and its u8s in argv order), xfrm (if_id, link), ioam6 (the frequencies, the mode, tunsrc and tundst unless inline, and ioam6_trace_hdr) and rpl (RplSRH, the segments last to first). LwtEncap now takes all eight types, and PutLwtEncap sends the six new ones through one nest writer. TestPutLwtEncapOther (17 rows), goldens hand-built from the uapi headers. netlink-audit flagged ioam6Trace's indexed writes; it now appends. Ledger V004-V006, closing LX1; V001-V003 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lwtEncapArgs now reads every lwt_parse_encap type but bpf (lwt_encap_srv6_args.go), so a route, a hop and a nexthop object build them. The argv quirks are ip's: xfrm swallows the rest of the command; ioam6's trace loop and seg6local's srh segs S at the end of argv step back so the caller reads the last word again; hmac ignores a bad number; seg6 with no mode sends -1. lwtEncapArgs takes the resolver, and routeParseState keeps it, so seg6local's iif and oif and xfrm's link_dev are looked up mid-loop as ip does. nh4/nh6 any is an empty attribute. ila reuses obj_ila.go's parsers and xtcpnl_ila.go's constants; LX1-T02's copies are gone. TestLwtEncapArgsSrv6 (65 rows). The refusal rows that named seg6 now name bpf. Ledger V007-V009; V004-V006 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T02) TestRouteWriteSrv6 (14 rows) and TestNexthopWriteSrv6 (5 rows) check each type's attributes where the keyword is, seg6local's oif looked up before dev, the route bound at 59 segments, and the quirks that change what the caller reads next: xfrm swallowing dev on a route, a hop and a nexthop, and ioam6's trace and seg6local's srh segs S last, whose last word ip reads again as a second destination. No new code was needed. O5's golden is unchanged. Ledger V010-V012, closing LX2; V007-V009 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3-T01) matrix_lwtx.go's eight spaces hold goip-lwt-srv6.md's forms: seg6 on IPv6 and IPv4 routes, seg6local's actions and flavors, ila, xfrm, a hop's seg6, a nexthop's seg6 and seg6local, and lwtxreq, the ioam6 and rpl forms the guest kernel refuses, all Judged negatives. Every row loads ila and xfrm_interface. nix/microvms/workbench/lwtx.rows.jsonl has the 73 rows. TestLwtxSpaces (65 subtests). The plan's space and duplicate counts are corrected. Ledger V013-V015; V010-V012 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…X3-T02) Two fresh-boot nlcap-ws batches of lwtx.rows.jsonl agree on all 73 rows' rcs and message types; every rc is as written. The bytes differ only in ip's padding after a route's RTA_ENCAP_TYPE, in 2 requests. The first batch is in pkg/xtcpnl/testdata/7_3_0/ws/ (228 KB). TestWsCorpusRows corrected two expectations: seg6local's oif nosuch and xfrm's link_dev look the device up first, so they are refused by the kernel and ack-then-fail. The generator and the two cmd.json are corrected; the captures are kept. Ledger V016-V018; V013-V015 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
O3 (xtcpnl_ws_lwtx_test.go) checks each captured lwtx request's encap against its axes: seg6's mode and SRH, seg6local's action, attributes, counters and flavors, ila's locator and u8s, xfrm's if_id and link, ioam6's frequencies, mode, addresses and trace header, and rpl's SRH. Each SRH's segments are checked against the row's own segs, last to first. 60 requests checked; 13 refusals send none. O5 has the eight lwtx spaces: all 73 rows same, no existing row moved. TestWsO5ZeroEncapPad gains a seg6local flavors nest's u8 padding. Ledger V019-V021, closing LX3; V016-V018 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 goip-parity write rows after the lwt rows, one per lwtx form at rc 0 or 2: seg6 on IPv6 and IPv4, seg6local's actions and flavors, ila, xfrm, a hop's seg6, a nexthop's seg6 and seg6local, and ioam6 and rpl, which a guest kernel without them refuses after ip and goip send the same request. TestGwLwtxMatrixRows (38 forms) picks them from the corpus. The gated checks skip them (gwIsLwtx) until LX4-T02. The rc 1 ila form stays O5's, as a GW row is rc 0 or 2; the plan is corrected. Ledger V022-V024; V019-V021 backfilled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On dfe50cf, two --section route,nexthop runs (167 of 167 PASS each) and the full goip-parity run with the rows gated (1373 of 1373 PASS) are clean, so the 43 lwtx rows go in gated_commands and TestAllowlistCommitted's earned list. The GW matrix tables' gated check and TestUngatedSurfaceIsNotVacuous lose their lwtx exception, and gwIsLwtx goes with it. Ledger V025-V028; V022-V024 backfilled with dfe50cf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reproducing the ip bugs LX found showed `flavors next-csid lblen 32` as the last words segfaults ip too (strcmp on argv[argc] for nflen), not only `next-csid` last. goip already refuses it as incomplete; a row in TestLwtEncapArgsSrv6 now pins it. The same repros showed the hdrlen decision was wrong: 128 segments wrap hdrlen to 0 and ip sends an 8-byte SRH, inside the 1024-byte bound, which the kernel refuses (EINVAL). goip refuses before sending, so it exits 1 where ip exits 2. The decision log says so now. Ledger V029. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Building and testing the iproute2 write-up's patches showed the plan's out-of-scope note wrong: 26 segments written out in full pass segs' 1023 bytes with a 424-byte SRH, inside the route bound, so ip installs the route with the last segment cut. The route-bound row said ip refuses; it prints addraw_l's error and sends the route without the encap, which goip-lwt-encap-status.md's decision log already recorded and goip refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (V031) From PR #287's review. - llProtoA2N matched names with strings.EqualFold, which folds Unicode: "ſnap" matched snap and "atal" plus the Kelvin sign matched atalk. proto_a2n uses strcasecmp, so it now uses cStrcaseEq. - cStrtoul read any base other than 8 or 16 as decimal. It now converts nothing for a base other than 0, 8, 10 or 16, as strtoul does for a base it refuses, so a bad base fails. - The link kinds' ifindex conversions go through cint, as the rest of goip's do. TestLLProtoA2N and TestCStrtoulBase are new; ledger V031. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se its memfd (V032) From PR #287's review. - splitSecrets blanked the config with its own copy of ConfigService's redactSecrets list, so a secret added to one and not the other would reach XTCP_CONFIG_JSON and /proc/<pid>/environ. It now calls the exported xtcp.RedactSecrets. TestSplitSecretsMatchesRedactSecrets sets every string field of a config and fails if a redacted field isn't carried back, or a carried secret is left in the redacted JSON. - secretsMemfd refuses a payload past reconfigureSecretsMaxBytes, so the parent logs and keeps running instead of exec'ing into an image that refuses it (TestSecretsMemfdBound). - loadReconfigureSecrets closes a memfd it refuses for missing seals; before, only a memfd it read was closed. Ledger V032 in goip-lwt-srv6-status.md, committed with V031. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
goip's link kinds, write keywords and lwt encaps, plus the nl_mon workbench that captures them. There are 161 commits, one per task, and each plan has a status doc under
docs/netlink/.Link kinds (
goip-link-kinds.md; KC, KP, KG):link set … inetform.-doutput for bridge, the VLAN family, the UDP overlays, the IP tunnels, hsr, netkit, vrf, vxcan, batadv and the kinds with no parser.Write keywords (
goip-write-keywords.md; GW1–GW5): route nhid, pref, expires, tos, realms, vrf and multipath; link group, GSO/GRO, addrgenmode, protodown, xdp off, index and netnsid; nexthop hw_stats and encap mpls; rule realms.Live forms (
goip-live-forms.md; LF1–LF3): live rows for the keyword forms that only O5 had checked.lwt encaps (
goip-lwt-encap.md,goip-lwt-srv6.md; LE, LX): encap ip, ip6, seg6, seg6local, ila, xfrm, ioam6 and rpl, on a route, a multipath hop and a nexthop object.Workbench (W0–W5): an ssh-driven nl_mon VM that runs generated JSONL rows from:
Each row is checked by oracles O1–O7, and W4 adds a coverage report against the kernel's policy tables. The rows produce 9468 pcaps under
pkg/xtcpnl/testdata/*/ws/, within the corpus budget (64 MB).BIRD (B2–B4): bridge FDB, VLAN and MPLS captures and parsers.
Other fixes:
docs/static-analysis.mdrecords the 474 G115 sites the exclusion hides.Coverage
addr show dynamic.ip netnsFDs).Test plan
go vet;nix build --no-linkof the focused tests, netlink-audit, lint-baseline and the nix hygiene checks;goip-paritysection runs.goip-parityVM run:GOIP_PARITY_DRIVER_PASS,GOIP_PARITY_OVERALL_PASS, 1373/1373GOIP_PARITY_PASSandGOIP_PARITY_UNGATED_CLEAN;nix build .#checks.x86_64-linux.test-go-race: pass.🤖 Generated with Claude Code