From 32ea740ca4fc34b5e61f8b08b198db36e8209cb5 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:22:52 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`IK8SNET?= =?UTF-8?q?-954`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @pasteley. * https://github.com/vishvananda/netlink/pull/1136#issuecomment-3448626013 The following files were modified: * `filter_linux.go` * `nl/tc_linux.go` --- filter_linux.go | 96 +++++++++++++++++++++++++++- nl/tc_linux.go | 165 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 251 insertions(+), 10 deletions(-) diff --git a/filter_linux.go b/filter_linux.go index fb9bab69..4fd96eea 100644 --- a/filter_linux.go +++ b/filter_linux.go @@ -817,6 +817,12 @@ func EncodeActions(attr *nl.RtAttr, actions []Action) error { return nil } +// parsePolice parses a netlink police attribute and populates the given PoliceAction. +// It handles the following attribute types: +// - nl.TCA_POLICE_RESULT: sets PoliceAction.NotExceedAction. +// - nl.TCA_POLICE_AVRATE: sets PoliceAction.AvRate. +// - nl.TCA_POLICE_TBF: populates ActionAttrs (Index, Bindcnt, Capab, Refcnt) and sets +// ExceedAction, Rate, PeakRate, Burst, Mtu, LinkLayer, and Overhead. func parsePolice(data syscall.NetlinkRouteAttr, police *PoliceAction) { switch data.Attr.Type { case nl.TCA_POLICE_RESULT: @@ -840,6 +846,82 @@ func parsePolice(data syscall.NetlinkRouteAttr, police *PoliceAction) { } } +// parsePeditKeys groups TcPedit keys by header type and updates the corresponding fields on the provided PeditAction. +// It extracts Ethernet, IPv4, IPv6, TCP and UDP key sets and sets SrcMacAddr, DstMacAddr, SrcIP, DstIP, SrcPort, DstPort, +// and Proto on the action when those values are present. +func parsePeditKeys(pedit *nl.TcPedit, action *PeditAction) { + // Group keys by header type + keysByType := make(map[nl.PeditHeaderType][]nl.TcPeditKey) + for i := 0; i < int(pedit.Sel.NKeys); i++ { + if i >= len(pedit.KeysEx) || i >= len(pedit.Keys) { + break + } + hdrType := pedit.KeysEx[i].HeaderType + keysByType[hdrType] = append(keysByType[hdrType], pedit.Keys[i]) + } + + for hdrType, keys := range keysByType { + switch hdrType { + case nl.TCA_PEDIT_KEY_EX_HDR_TYPE_ETH: + srcMac, dstMac := nl.ParsePeditEthKeys(keys) + if srcMac != nil { + action.SrcMacAddr = srcMac + } + if dstMac != nil { + action.DstMacAddr = dstMac + } + + case nl.TCA_PEDIT_KEY_EX_HDR_TYPE_IP4: + srcIP, dstIP := nl.ParsePeditIP4Keys(keys) + if srcIP != nil { + action.SrcIP = srcIP + } + if dstIP != nil { + action.DstIP = dstIP + } + + case nl.TCA_PEDIT_KEY_EX_HDR_TYPE_IP6: + srcIP, dstIP := nl.ParsePeditIP6Keys(keys) + if srcIP != nil { + action.SrcIP = srcIP + } + if dstIP != nil { + action.DstIP = dstIP + } + + case nl.TCA_PEDIT_KEY_EX_HDR_TYPE_TCP: + srcPort, dstPort := nl.ParsePeditL4Keys(keys) + if srcPort > 0 { + action.SrcPort = srcPort + } + if dstPort > 0 { + action.DstPort = dstPort + } + if srcPort > 0 || dstPort > 0 { + action.Proto = unix.IPPROTO_TCP + } + + case nl.TCA_PEDIT_KEY_EX_HDR_TYPE_UDP: + srcPort, dstPort := nl.ParsePeditL4Keys(keys) + if srcPort > 0 { + action.SrcPort = srcPort + } + if dstPort > 0 { + action.DstPort = dstPort + } + if srcPort > 0 || dstPort > 0 { + action.Proto = unix.IPPROTO_UDP + } + } + } +} + +// parseActions parses netlink action tables into a slice of Action values. +// It decodes each table's kind and options, instantiates the corresponding +// concrete Action (mirred, bpf, connmark, csum, sample, gact, vlan, tunnel_key, +// skbedit, police, pedit, etc.), populates action attributes (including +// TcGen-derived fields), associated statistics, and timestamps. It returns an +// error if any nested attribute parsing fails. func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { var actions []Action for _, table := range tables { @@ -884,6 +966,7 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { break nextattr } case nl.TCA_OPTIONS: + var pedit *nl.TcPedit adata, err := nl.ParseRouteAttr(aattr.Value) if err != nil { return nil, err @@ -1018,6 +1101,17 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { } case "police": parsePolice(adatum, action.(*PoliceAction)) + case "pedit": + switch adatum.Attr.Type { + case nl.TCA_PEDIT_PARMS, nl.TCA_PEDIT_PARMS_EX: + pedit = nl.DeserializeTcPedit(adatum.Value) + toAttrs(&pedit.Sel.TcGen, action.Attrs()) + case nl.TCA_PEDIT_KEYS_EX: + if pedit != nil { + pedit.KeysEx = nl.DeserializeTcPeditKeysEx(adatum.Value, int(pedit.Sel.NKeys)) + parsePeditKeys(pedit, action.(*PeditAction)) + } + } } } case nl.TCA_ACT_STATS: @@ -1227,4 +1321,4 @@ func SerializeRtab(rtab [256]uint32) []byte { var w bytes.Buffer _ = binary.Write(&w, native, rtab) return w.Bytes() -} +} \ No newline at end of file diff --git a/nl/tc_linux.go b/nl/tc_linux.go index 67666816..121c81bb 100644 --- a/nl/tc_linux.go +++ b/nl/tc_linux.go @@ -1274,9 +1274,10 @@ func (i IPProto) String() string { } const ( - MaxOffs = 128 - SizeOfPeditSel = 24 - SizeOfPeditKey = 24 + MaxOffs = 128 + SizeOfPeditSel = 24 + SizeOfPeditKey = 24 + SizeOfPeditKeyEx = 4 TCA_PEDIT_KEY_EX_HTYPE = 1 TCA_PEDIT_KEY_EX_CMD = 2 @@ -1320,24 +1321,68 @@ type TcPeditSel struct { Flags uint8 } +// DeserializeTcPeditKey interprets the first SizeOfPeditKey bytes of b as a TcPeditKey and returns a pointer to it. +// The byte slice b must be at least SizeOfPeditKey bytes long. func DeserializeTcPeditKey(b []byte) *TcPeditKey { return (*TcPeditKey)(unsafe.Pointer(&b[0:SizeOfPeditKey][0])) } -func DeserializeTcPedit(b []byte) (*TcPeditSel, []TcPeditKey) { - x := &TcPeditSel{} - copy((*(*[SizeOfPeditSel]byte)(unsafe.Pointer(x)))[:SizeOfPeditSel], b) +// DeserializeTcPedit constructs a *TcPedit by reading a selector and its keys from b. +// +// It reads the TcPeditSel from the first SizeOfPeditSel bytes, then reads sel.NKeys +// consecutive TcPeditKey entries (SizeOfPeditKey each) and returns a TcPedit with +// Sel and Keys populated. KeysEx and Extend are not set by this function. +// +// The caller must provide b with at least SizeOfPeditSel + sel.NKeys*SizeOfPeditKey bytes. +func DeserializeTcPedit(b []byte) *TcPedit { + sel := &TcPeditSel{} + copy((*(*[SizeOfPeditSel]byte)(unsafe.Pointer(sel)))[:SizeOfPeditSel], b) var keys []TcPeditKey - next := SizeOfPeditKey + next := SizeOfPeditSel var i uint8 - for i = 0; i < x.NKeys; i++ { + for i = 0; i < sel.NKeys; i++ { keys = append(keys, *DeserializeTcPeditKey(b[next:])) next += SizeOfPeditKey } - return x, keys + return &TcPedit{Sel: *sel, Keys: keys} +} + +// DeserializeTcPeditKeysEx parses extended pedit key attributes from b and returns a slice +// of length nkeys containing the decoded TcPeditKeyEx entries. +// For each nested key attribute found, it extracts HTYPE and CMD and sets HeaderType and Cmd. +// If attributes are missing or malformed for a key, that entry remains the zero value. +func DeserializeTcPeditKeysEx(b []byte, nkeys int) []TcPeditKeyEx { + keysEx := make([]TcPeditKeyEx, nkeys) + + attrs, err := ParseRouteAttr(b) + if err != nil { + return keysEx + } + + for i, attr := range attrs { + if i >= nkeys { + break + } + + keyAttrs, err := ParseRouteAttr(attr.Value) + if err != nil { + continue + } + + for _, ka := range keyAttrs { + switch ka.Attr.Type { + case TCA_PEDIT_KEY_EX_HTYPE: + keysEx[i].HeaderType = PeditHeaderType(NativeEndian().Uint16(ka.Value[:2])) + case TCA_PEDIT_KEY_EX_CMD: + keysEx[i].Cmd = PeditCmd(NativeEndian().Uint16(ka.Value[:2])) + } + } + } + + return keysEx } type TcPeditKey struct { @@ -1663,3 +1708,105 @@ func (p *TcPedit) SetSrcPort(srcPort uint16, protocol uint8) { p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } + +// ParsePeditEthKeys extracts source and destination Ethernet MAC addresses from a slice of TcPeditKey. +// It assembles MAC bytes from keys whose Off fields are 0, 4, and 8 and returns the resulting +// srcMac and dstMac. If the provided keys do not contain a complete 6-byte address for either +// side, that return value will be nil. +func ParsePeditEthKeys(keys []TcPeditKey) (srcMac, dstMac net.HardwareAddr) { + srcParts := make([]byte, 0, 6) + dstParts := make([]byte, 0, 6) + + for _, key := range keys { + valBytes := make([]byte, 4) + NativeEndian().PutUint32(valBytes, key.Val) + + switch key.Off { + case 0: + dstParts = append(dstParts, valBytes...) + case 4: + dstParts = append(dstParts, valBytes[0:2]...) + srcParts = append(srcParts, valBytes[2:4]...) + case 8: + srcParts = append(srcParts, valBytes...) + } + } + + if len(srcParts) == 6 { + srcMac = net.HardwareAddr(srcParts) + } + if len(dstParts) == 6 { + dstMac = net.HardwareAddr(dstParts) + } + + return srcMac, dstMac +} + +// ParsePeditIP4Keys extracts IPv4 source and destination addresses from a slice of TcPeditKey. +// It returns the address found at Off==12 as srcIP and the address found at Off==16 as dstIP; either return value is nil if the corresponding key is not present. +func ParsePeditIP4Keys(keys []TcPeditKey) (srcIP, dstIP net.IP) { + for _, key := range keys { + valBytes := make([]byte, 4) + NativeEndian().PutUint32(valBytes, key.Val) + + switch key.Off { + case 12: + srcIP = net.IP(valBytes) + case 16: + dstIP = net.IP(valBytes) + } + } + + return srcIP, dstIP +} + +// ParsePeditIP6Keys extracts IPv6 source and destination addresses from a slice of TcPeditKey entries. +// ParsePeditIP6Keys looks for four consecutive keys whose Off values start at 8 for the source and 24 for the destination, +// each key contributing a 32-bit segment (in native endianness) to form the 16-byte IPv6 address. +// It returns the parsed srcIP and dstIP, or nil for any address that is missing or incomplete. +func ParsePeditIP6Keys(keys []TcPeditKey) (srcIP, dstIP net.IP) { + // Helper to parse 4 consecutive keys for a complete IPv6 address + parseIPv6Addr := func(startIdx int) net.IP { + if startIdx+3 >= len(keys) { + return nil + } + + ip := make(net.IP, 16) + baseOffset := keys[startIdx].Off + + for j := 0; j < 4; j++ { + if keys[startIdx+j].Off != baseOffset+uint32(j*4) { + return nil + } + NativeEndian().PutUint32(ip[j*4:], keys[startIdx+j].Val) + } + + return ip + } + + for idx, key := range keys { + switch key.Off { + case 8: + srcIP = parseIPv6Addr(idx) + case 24: + dstIP = parseIPv6Addr(idx) + } + } + + return srcIP, dstIP +} + +// ParsePeditL4Keys extracts transport-layer source and destination ports from the first TcPeditKey. +// If keys is empty both ports are zero. +// It interprets the 32-bit Key.Val as: upper 16 bits = destination port, lower 16 bits = source port; both are returned after swapping byte order. +func ParsePeditL4Keys(keys []TcPeditKey) (srcPort, dstPort uint16) { + if len(keys) == 0 { + return 0, 0 + } + + key := keys[0] + dstPort = Swap16(uint16(key.Val >> 16)) + srcPort = Swap16(uint16(key.Val & 0xFFFF)) + + return srcPort, dstPort +} \ No newline at end of file