diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e480f96..0e3dc59d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,12 +11,12 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: 1.17 + go-version: 1.22 - name: Kernel Modules run: ./.github/scripts/modprobe.sh @@ -30,12 +30,12 @@ jobs: # on macOS, which helps catch missing build tags. runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: 1.17 + go-version: 1.22 - name: Build run: go build ./... diff --git a/addr_linux.go b/addr_linux.go index 9b49baf9..01c2306c 100644 --- a/addr_linux.go +++ b/addr_linux.go @@ -18,6 +18,7 @@ import ( // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. +// If `net.IPv4zero` is given as the broadcast address, broadcast is disabled. func AddrAdd(link Link, addr *Addr) error { return pkgHandle.AddrAdd(link, addr) } @@ -28,6 +29,7 @@ func AddrAdd(link Link, addr *Addr) error { // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. +// If `net.IPv4zero` is given as the broadcast address, broadcast is disabled. func (h *Handle) AddrAdd(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) return h.addrHandle(link, addr, req) @@ -39,6 +41,7 @@ func (h *Handle) AddrAdd(link Link, addr *Addr) error { // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. +// If `net.IPv4zero` is given as the broadcast address, broadcast is disabled. func AddrReplace(link Link, addr *Addr) error { return pkgHandle.AddrReplace(link, addr) } @@ -49,6 +52,7 @@ func AddrReplace(link Link, addr *Addr) error { // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. +// If `net.IPv4zero` is given as the broadcast address, broadcast is disabled. func (h *Handle) AddrReplace(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_REPLACE|unix.NLM_F_ACK) return h.addrHandle(link, addr, req) @@ -57,18 +61,13 @@ func (h *Handle) AddrReplace(link Link, addr *Addr) error { // AddrDel will delete an IP address from a link device. // // Equivalent to: `ip addr del $addr dev $link` -// -// If `addr` is an IPv4 address and the broadcast address is not given, it -// will be automatically computed based on the IP mask if /30 or larger. func AddrDel(link Link, addr *Addr) error { return pkgHandle.AddrDel(link, addr) } // AddrDel will delete an IP address from a link device. -// Equivalent to: `ip addr del $addr dev $link` // -// If `addr` is an IPv4 address and the broadcast address is not given, it -// will be automatically computed based on the IP mask if /30 or larger. +// Equivalent to: `ip addr del $addr dev $link` func (h *Handle) AddrDel(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK) return h.addrHandle(link, addr, req) @@ -142,6 +141,10 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error addr.Broadcast = calcBroadcast } + if net.IPv4zero.Equal(addr.Broadcast) { + addr.Broadcast = nil + } + if addr.Broadcast != nil { req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast)) } diff --git a/conntrack_linux.go b/conntrack_linux.go index 69c5eca0..c566b2a4 100644 --- a/conntrack_linux.go +++ b/conntrack_linux.go @@ -159,13 +159,18 @@ func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFami // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters using the netlink handle passed // conntrack -D [table] parameters Delete conntrack or expectation func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { + var errMsgs []string res, err := h.dumpConntrackTable(table, family) if err != nil { - return 0, err + if !errors.Is(err, ErrDumpInterrupted) { + return 0, err + } + // This allows us to at least do a best effort to try to clean the + // entries matching the filter. + errMsgs = append(errMsgs, err.Error()) } var matched uint - var errMsgs []string for _, dataRaw := range res { flow := parseRawData(dataRaw) for _, filter := range filters { diff --git a/filter.go b/filter.go index 84e1ca7a..a722e0a2 100644 --- a/filter.go +++ b/filter.go @@ -231,6 +231,35 @@ func NewCsumAction() *CsumAction { } } +type VlanAct int8 + +type VlanAction struct { + ActionAttrs + Action VlanAct + VlanID uint16 +} + +const ( + TCA_VLAN_ACT_POP VlanAct = 1 + TCA_VLAN_ACT_PUSH VlanAct = 2 +) + +func (action *VlanAction) Type() string { + return "vlan" +} + +func (action *VlanAction) Attrs() *ActionAttrs { + return &action.ActionAttrs +} + +func NewVlanAction() *VlanAction { + return &VlanAction{ + ActionAttrs: ActionAttrs{ + Action: TC_ACT_PIPE, + }, + } +} + type MirredAct uint8 func (a MirredAct) String() string { diff --git a/filter_linux.go b/filter_linux.go index 19306612..404e50d5 100644 --- a/filter_linux.go +++ b/filter_linux.go @@ -65,6 +65,9 @@ type Flower struct { EncSrcIPMask net.IPMask EncDestPort uint16 EncKeyId uint32 + SrcMac net.HardwareAddr + DestMac net.HardwareAddr + VlanId uint16 SkipHw bool SkipSw bool IPProto *nl.IPProto @@ -135,6 +138,15 @@ func (filter *Flower) encode(parent *nl.RtAttr) error { if filter.EncKeyId != 0 { parent.AddRtAttr(nl.TCA_FLOWER_KEY_ENC_KEY_ID, htonl(filter.EncKeyId)) } + if filter.SrcMac != nil { + parent.AddRtAttr(nl.TCA_FLOWER_KEY_ETH_SRC, filter.SrcMac) + } + if filter.DestMac != nil { + parent.AddRtAttr(nl.TCA_FLOWER_KEY_ETH_DST, filter.DestMac) + } + if filter.VlanId != 0 { + parent.AddRtAttr(nl.TCA_FLOWER_KEY_VLAN_ID, nl.Uint16Attr(filter.VlanId)) + } if filter.IPProto != nil { ipproto := *filter.IPProto parent.AddRtAttr(nl.TCA_FLOWER_KEY_IP_PROTO, ipproto.Serialize()) @@ -201,6 +213,13 @@ func (filter *Flower) decode(data []syscall.NetlinkRouteAttr) error { filter.EncDestPort = ntohs(datum.Value) case nl.TCA_FLOWER_KEY_ENC_KEY_ID: filter.EncKeyId = ntohl(datum.Value) + case nl.TCA_FLOWER_KEY_ETH_SRC: + filter.SrcMac = datum.Value + case nl.TCA_FLOWER_KEY_ETH_DST: + filter.DestMac = datum.Value + case nl.TCA_FLOWER_KEY_VLAN_ID: + filter.VlanId = native.Uint16(datum.Value[0:2]) + filter.EthType = unix.ETH_P_8021Q case nl.TCA_FLOWER_KEY_IP_PROTO: val := new(nl.IPProto) *val = nl.IPProto(datum.Value[0]) @@ -622,6 +641,22 @@ func EncodeActions(attr *nl.RtAttr, actions []Action) error { } toTcGen(action.Attrs(), &mirred.TcGen) aopts.AddRtAttr(nl.TCA_MIRRED_PARMS, mirred.Serialize()) + case *VlanAction: + table := attr.AddRtAttr(tabIndex, nil) + tabIndex++ + table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("vlan")) + aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) + vlan := nl.TcVlan{ + Action: int32(action.Action), + } + toTcGen(action.Attrs(), &vlan.TcGen) + aopts.AddRtAttr(nl.TCA_VLAN_PARMS, vlan.Serialize()) + if action.Action == TCA_VLAN_ACT_PUSH && action.VlanID == 0 { + return fmt.Errorf("vlan id is required for push action") + } + if action.VlanID != 0 { + aopts.AddRtAttr(nl.TCA_VLAN_PUSH_VLAN_ID, nl.Uint16Attr(action.VlanID)) + } case *TunnelKeyAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ @@ -792,6 +827,8 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { action = &CsumAction{} case "gact": action = &GenericAction{} + case "vlan": + action = &VlanAction{} case "tunnel_key": action = &TunnelKeyAction{} case "skbedit": @@ -822,7 +859,17 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } - + case "vlan": + switch adatum.Attr.Type { + case nl.TCA_VLAN_PARMS: + vlan := *nl.DeserializeTcVlan(adatum.Value) + action.(*VlanAction).ActionAttrs = ActionAttrs{} + toAttrs(&vlan.TcGen, action.Attrs()) + action.(*VlanAction).Action = VlanAct(vlan.Action) + case nl.TCA_VLAN_PUSH_VLAN_ID: + vlanId := native.Uint16(adatum.Value[0:2]) + action.(*VlanAction).VlanID = vlanId + } case "tunnel_key": switch adatum.Attr.Type { case nl.TCA_TUNNEL_KEY_PARMS: diff --git a/filter_test.go b/filter_test.go index 3a49f1b8..3b60d1c4 100644 --- a/filter_test.go +++ b/filter_test.go @@ -1768,6 +1768,14 @@ func TestFilterFlowerAddDel(t *testing.T) { } testMask := net.CIDRMask(24, 32) + srcMac, err := net.ParseMAC("2C:54:91:88:C9:E3") + if err != nil { + t.Fatal(err) + } + destMac, err := net.ParseMAC("2C:54:91:88:C9:E5") + if err != nil { + t.Fatal(err) + } ipproto := new(nl.IPProto) *ipproto = nl.IPPROTO_TCP @@ -1790,10 +1798,19 @@ func TestFilterFlowerAddDel(t *testing.T) { EncSrcIPMask: testMask, EncDestPort: 8472, EncKeyId: 1234, + SrcMac: srcMac, + DestMac: destMac, IPProto: ipproto, DestPort: 1111, SrcPort: 1111, Actions: []Action{ + &VlanAction{ + ActionAttrs: ActionAttrs{ + Action: TC_ACT_PIPE, + }, + Action: TCA_VLAN_ACT_PUSH, + VlanID: 1234, + }, &MirredAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_STOLEN, @@ -1871,8 +1888,31 @@ func TestFilterFlowerAddDel(t *testing.T) { if filter.SrcPort != flower.SrcPort { t.Fatalf("Flower SrcPort doesn't match") } + if !(filter.SrcMac.String() == flower.SrcMac.String()) { + t.Fatalf("Flower SrcMac doesn't match") + } + if !(filter.DestMac.String() == flower.DestMac.String()) { + t.Fatalf("Flower DestMac doesn't match") + } - mia, ok := flower.Actions[0].(*MirredAction) + vla, ok := flower.Actions[0].(*VlanAction) + if !ok { + t.Fatal("Unable to find vlan action") + } + + if vla.Attrs().Action != TC_ACT_PIPE { + t.Fatal("Vlan action isn't TC_ACT_PIPE") + } + + if vla.Action != TCA_VLAN_ACT_PUSH { + t.Fatal("Second Vlan action isn't push") + } + + if vla.VlanID != 1234 { + t.Fatal("Second Vlan action vlanId isn't correct") + } + + mia, ok := flower.Actions[1].(*MirredAction) if !ok { t.Fatal("Unable to find mirred action") } @@ -1889,7 +1929,7 @@ func TestFilterFlowerAddDel(t *testing.T) { t.Fatal("Incorrect mirred action stats") } - ga, ok := flower.Actions[1].(*GenericAction) + ga, ok := flower.Actions[2].(*GenericAction) if !ok { t.Fatal("Unable to find generic action") } @@ -1917,6 +1957,94 @@ func TestFilterFlowerAddDel(t *testing.T) { t.Fatal("Failed to remove filter") } + filter = &Flower{ + FilterAttrs: FilterAttrs{ + LinkIndex: link.Attrs().Index, + Parent: MakeHandle(0xffff, 0), + Priority: 1, + Protocol: unix.ETH_P_8021Q, + }, + EthType: unix.ETH_P_8021Q, + VlanId: 2046, + Actions: []Action{ + &VlanAction{ + ActionAttrs: ActionAttrs{ + Action: TC_ACT_PIPE, + }, + Action: TCA_VLAN_ACT_POP, + }, + &MirredAction{ + ActionAttrs: ActionAttrs{ + Action: TC_ACT_STOLEN, + }, + MirredAction: TCA_EGRESS_REDIR, + Ifindex: redir.Attrs().Index, + }, + }, + } + + if err := FilterAdd(filter); err != nil { + t.Fatal(err) + } + + time.Sleep(time.Second) + filters, err = FilterList(link, MakeHandle(0xffff, 0)) + if err != nil { + t.Fatal(err) + } + if len(filters) != 1 { + t.Fatal("Failed to add filter") + } + flower, ok = filters[0].(*Flower) + if !ok { + t.Fatal("Filter is the wrong type") + } + + if filter.VlanId != flower.VlanId { + t.Fatalf("Flower VlanId doesn't match") + } + + vla, ok = flower.Actions[0].(*VlanAction) + if !ok { + t.Fatal("Unable to find vlan action") + } + + if vla.Attrs().Action != TC_ACT_PIPE { + t.Fatal("Vlan action isn't TC_ACT_PIPE") + } + + if vla.Action != TCA_VLAN_ACT_POP { + t.Fatal("First Vlan action isn't pop") + } + + mia, ok = flower.Actions[1].(*MirredAction) + if !ok { + t.Fatal("Unable to find mirred action") + } + + if mia.Attrs().Action != TC_ACT_STOLEN { + t.Fatal("Mirred action isn't TC_ACT_STOLEN") + } + + if mia.Timestamp == nil || mia.Timestamp.Installed == 0 { + t.Fatal("Incorrect mirred action timestamp") + } + + if mia.Statistics == nil { + t.Fatal("Incorrect mirred action stats") + } + + if err := FilterDel(filter); err != nil { + t.Fatal(err) + } + filters, err = FilterList(link, MakeHandle(0xffff, 0)) + if err != nil { + t.Fatal(err) + } + if len(filters) != 0 { + t.Fatal("Failed to remove filter") + } + if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } diff --git a/link_linux.go b/link_linux.go index 52491c58..88bbc6db 100644 --- a/link_linux.go +++ b/link_linux.go @@ -3041,7 +3041,6 @@ func parseMacvlanData(link Link, data []syscall.NetlinkRouteAttr) { } } -// copied from pkg/net_linux.go func linkFlags(rawFlags uint32) net.Flags { var f net.Flags if rawFlags&unix.IFF_UP != 0 { @@ -3059,6 +3058,9 @@ func linkFlags(rawFlags uint32) net.Flags { if rawFlags&unix.IFF_MULTICAST != 0 { f |= net.FlagMulticast } + if rawFlags&unix.IFF_RUNNING != 0 { + f |= net.FlagRunning + } return f } diff --git a/nl/tc_linux.go b/nl/tc_linux.go index 0720729a..b8f50079 100644 --- a/nl/tc_linux.go +++ b/nl/tc_linux.go @@ -115,6 +115,7 @@ const ( SizeofTcConnmark = SizeofTcGen + 0x04 SizeofTcCsum = SizeofTcGen + 0x04 SizeofTcMirred = SizeofTcGen + 0x08 + SizeofTcVlan = SizeofTcGen + 0x04 SizeofTcTunnelKey = SizeofTcGen + 0x04 SizeofTcSkbEdit = SizeofTcGen SizeofTcPolice = 2*SizeofTcRateSpec + 0x20 @@ -816,6 +817,41 @@ func (x *TcMirred) Serialize() []byte { return (*(*[SizeofTcMirred]byte)(unsafe.Pointer(x)))[:] } +const ( + TCA_VLAN_UNSPEC = iota + TCA_VLAN_TM + TCA_VLAN_PARMS + TCA_VLAN_PUSH_VLAN_ID + TCA_VLAN_PUSH_VLAN_PROTOCOL + TCA_VLAN_PAD + TCA_VLAN_PUSH_VLAN_PRIORITY + TCA_VLAN_PUSH_ETH_DST + TCA_VLAN_PUSH_ETH_SRC + TCA_VLAN_MAX +) + +//struct tc_vlan { +// tc_gen; +// int v_action; +//}; + +type TcVlan struct { + TcGen + Action int32 +} + +func (msg *TcVlan) Len() int { + return SizeofTcVlan +} + +func DeserializeTcVlan(b []byte) *TcVlan { + return (*TcVlan)(unsafe.Pointer(&b[0:SizeofTcVlan][0])) +} + +func (x *TcVlan) Serialize() []byte { + return (*(*[SizeofTcVlan]byte)(unsafe.Pointer(x)))[:] +} + const ( TCA_TUNNEL_KEY_UNSPEC = iota TCA_TUNNEL_KEY_TM @@ -1239,8 +1275,8 @@ const ( ) // /* TCA_PEDIT_KEY_EX_HDR_TYPE_NETWROK is a special case for legacy users. It -// * means no specific header type - offset is relative to the network layer -// */ +// - means no specific header type - offset is relative to the network layer +// */ type PeditHeaderType uint16 const ( diff --git a/rule_test.go b/rule_test.go index 59edb405..4420e5b5 100644 --- a/rule_test.go +++ b/rule_test.go @@ -6,6 +6,7 @@ package netlink import ( "net" "testing" + "time" "golang.org/x/sys/unix" ) @@ -583,11 +584,18 @@ func runRuleListFiltered(t *testing.T, family int, srcNet, dstNet *net.IPNet) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rule := tt.preRun() + wantRules, wantErr := tt.setupWant(rule) + rules, err := RuleListFiltered(family, tt.ruleFilter, tt.filterMask) + for i := 0; i < len(wantRules); i++ { + if len(wantRules) == len(rules) { + break + } + time.Sleep(1 * time.Second) // wait rule take effect + rules, err = RuleListFiltered(family, tt.ruleFilter, tt.filterMask) + } tt.postRun(rule) - wantRules, wantErr := tt.setupWant(rule) - if len(wantRules) != len(rules) { t.Errorf("Expected len: %d, got: %d", len(wantRules), len(rules)) } else {