Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion filter_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1227,4 +1321,4 @@ func SerializeRtab(rtab [256]uint32) []byte {
var w bytes.Buffer
_ = binary.Write(&w, native, rtab)
return w.Bytes()
}
}
165 changes: 156 additions & 9 deletions nl/tc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}