bump netlink

Signed-off-by: Daman Arora <aroradaman@gmail.com>
This commit is contained in:
Daman Arora 2025-02-07 15:17:29 +05:30
parent d2ad0cc7c0
commit 07c279d06b
33 changed files with 682 additions and 217 deletions

2
go.mod
View File

@ -60,7 +60,7 @@ require (
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/vishvananda/netlink v1.3.1-0.20240905180732-b1ce50cfa9be github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa
github.com/vishvananda/netns v0.0.4 github.com/vishvananda/netns v0.0.4
go.etcd.io/etcd/api/v3 v3.5.16 go.etcd.io/etcd/api/v3 v3.5.16
go.etcd.io/etcd/client/pkg/v3 v3.5.16 go.etcd.io/etcd/client/pkg/v3 v3.5.16

4
go.sum
View File

@ -462,8 +462,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/vishvananda/netlink v1.3.1-0.20240905180732-b1ce50cfa9be h1:xdCMvyhnKzaepIUgVpUmTJo/+H1AQ7HuFYn1hv7/Neo= github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa h1:iAhToRwOrdk+pKzclvLM7nKZhsg8f7dVrgkFccDUbUw=
github.com/vishvananda/netlink v1.3.1-0.20240905180732-b1ce50cfa9be/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@ -17,6 +18,7 @@ import (
// //
// If `addr` is an IPv4 address and the broadcast address is not given, it // 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. // 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 { func AddrAdd(link Link, addr *Addr) error {
return pkgHandle.AddrAdd(link, addr) return pkgHandle.AddrAdd(link, addr)
} }
@ -27,6 +29,7 @@ func AddrAdd(link Link, addr *Addr) error {
// //
// If `addr` is an IPv4 address and the broadcast address is not given, it // 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. // 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 { 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) req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK)
return h.addrHandle(link, addr, req) return h.addrHandle(link, addr, req)
@ -38,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 // 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. // 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 { func AddrReplace(link Link, addr *Addr) error {
return pkgHandle.AddrReplace(link, addr) return pkgHandle.AddrReplace(link, addr)
} }
@ -48,6 +52,7 @@ func AddrReplace(link Link, addr *Addr) error {
// //
// If `addr` is an IPv4 address and the broadcast address is not given, it // 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. // 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 { 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) req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_REPLACE|unix.NLM_F_ACK)
return h.addrHandle(link, addr, req) return h.addrHandle(link, addr, req)
@ -56,18 +61,13 @@ func (h *Handle) AddrReplace(link Link, addr *Addr) error {
// AddrDel will delete an IP address from a link device. // AddrDel will delete an IP address from a link device.
// //
// Equivalent to: `ip addr del $addr dev $link` // 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 { func AddrDel(link Link, addr *Addr) error {
return pkgHandle.AddrDel(link, addr) return pkgHandle.AddrDel(link, addr)
} }
// AddrDel will delete an IP address from a link device. // 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 // Equivalent to: `ip addr del $addr dev $link`
// will be automatically computed based on the IP mask if /30 or larger.
func (h *Handle) AddrDel(link Link, addr *Addr) error { func (h *Handle) AddrDel(link Link, addr *Addr) error {
req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK) req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK)
return h.addrHandle(link, addr, req) return h.addrHandle(link, addr, req)
@ -141,6 +141,10 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error
addr.Broadcast = calcBroadcast addr.Broadcast = calcBroadcast
} }
if net.IPv4zero.Equal(addr.Broadcast) {
addr.Broadcast = nil
}
if addr.Broadcast != nil { if addr.Broadcast != nil {
req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast)) req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast))
} }
@ -169,6 +173,9 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error
// AddrList gets a list of IP addresses in the system. // AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`. // Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func AddrList(link Link, family int) ([]Addr, error) { func AddrList(link Link, family int) ([]Addr, error) {
return pkgHandle.AddrList(link, family) return pkgHandle.AddrList(link, family)
} }
@ -176,14 +183,17 @@ func AddrList(link Link, family int) ([]Addr, error) {
// AddrList gets a list of IP addresses in the system. // AddrList gets a list of IP addresses in the system.
// Equivalent to: `ip addr show`. // Equivalent to: `ip addr show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) AddrList(link Link, family int) ([]Addr, error) { func (h *Handle) AddrList(link Link, family int) ([]Addr, error) {
req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP)
msg := nl.NewIfAddrmsg(family) msg := nl.NewIfAddrmsg(family)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
indexFilter := 0 indexFilter := 0
@ -212,7 +222,7 @@ func (h *Handle) AddrList(link Link, family int) ([]Addr, error) {
res = append(res, addr) res = append(res, addr)
} }
return res, nil return res, executeErr
} }
func parseAddr(m []byte) (addr Addr, family int, err error) { func parseAddr(m []byte) (addr Addr, family int, err error) {

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
@ -9,21 +10,27 @@ import (
// BridgeVlanList gets a map of device id to bridge vlan infos. // BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show` // Equivalent to: `bridge vlan show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
return pkgHandle.BridgeVlanList() return pkgHandle.BridgeVlanList()
} }
// BridgeVlanList gets a map of device id to bridge vlan infos. // BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show` // Equivalent to: `bridge vlan show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP)
msg := nl.NewIfInfomsg(unix.AF_BRIDGE) msg := nl.NewIfInfomsg(unix.AF_BRIDGE)
req.AddData(msg) req.AddData(msg)
req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN)))) req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN))))
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
ret := make(map[int32][]*nl.BridgeVlanInfo) ret := make(map[int32][]*nl.BridgeVlanInfo)
for _, m := range msgs { for _, m := range msgs {
@ -51,7 +58,7 @@ func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
} }
} }
} }
return ret, nil return ret, executeErr
} }
// BridgeVlanAdd adds a new vlan filter entry // BridgeVlanAdd adds a new vlan filter entry

View File

@ -1,6 +1,8 @@
package netlink package netlink
import ( import (
"errors"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -56,6 +58,9 @@ func (h *Handle) chainModify(cmd, flags int, link Link, chain Chain) error {
// ChainList gets a list of chains in the system. // ChainList gets a list of chains in the system.
// Equivalent to: `tc chain list`. // Equivalent to: `tc chain list`.
// The list can be filtered by link. // The list can be filtered by link.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func ChainList(link Link, parent uint32) ([]Chain, error) { func ChainList(link Link, parent uint32) ([]Chain, error) {
return pkgHandle.ChainList(link, parent) return pkgHandle.ChainList(link, parent)
} }
@ -63,6 +68,9 @@ func ChainList(link Link, parent uint32) ([]Chain, error) {
// ChainList gets a list of chains in the system. // ChainList gets a list of chains in the system.
// Equivalent to: `tc chain list`. // Equivalent to: `tc chain list`.
// The list can be filtered by link. // The list can be filtered by link.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) {
req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP)
index := int32(0) index := int32(0)
@ -78,9 +86,9 @@ func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) {
} }
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Chain var res []Chain
@ -108,5 +116,5 @@ func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) {
res = append(res, chain) res = append(res, chain)
} }
return res, nil return res, executeErr
} }

View File

@ -201,14 +201,20 @@ func classPayload(req *nl.NetlinkRequest, class Class) error {
// ClassList gets a list of classes in the system. // ClassList gets a list of classes in the system.
// Equivalent to: `tc class show`. // Equivalent to: `tc class show`.
//
// Generally returns nothing if link and parent are not specified. // Generally returns nothing if link and parent are not specified.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func ClassList(link Link, parent uint32) ([]Class, error) { func ClassList(link Link, parent uint32) ([]Class, error) {
return pkgHandle.ClassList(link, parent) return pkgHandle.ClassList(link, parent)
} }
// ClassList gets a list of classes in the system. // ClassList gets a list of classes in the system.
// Equivalent to: `tc class show`. // Equivalent to: `tc class show`.
//
// Generally returns nothing if link and parent are not specified. // Generally returns nothing if link and parent are not specified.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP)
msg := &nl.TcMsg{ msg := &nl.TcMsg{
@ -222,9 +228,9 @@ func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
} }
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Class var res []Class
@ -295,7 +301,7 @@ func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) {
res = append(res, class) res = append(res, class)
} }
return res, nil return res, executeErr
} }
func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) {

View File

@ -5,8 +5,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"net" "net"
"strings"
"time" "time"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
@ -45,6 +45,9 @@ type InetFamily uint8
// ConntrackTableList returns the flow list of a table of a specific family // ConntrackTableList returns the flow list of a table of a specific family
// conntrack -L [table] [options] List conntrack or expectation table // conntrack -L [table] [options] List conntrack or expectation table
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
return pkgHandle.ConntrackTableList(table, family) return pkgHandle.ConntrackTableList(table, family)
} }
@ -84,10 +87,13 @@ func ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters
// ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed
// conntrack -L [table] [options] List conntrack or expectation table // conntrack -L [table] [options] List conntrack or expectation table
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
res, err := h.dumpConntrackTable(table, family) res, executeErr := h.dumpConntrackTable(table, family)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
// Deserialize all the flows // Deserialize all the flows
@ -96,7 +102,7 @@ func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily)
result = append(result, parseRawData(dataRaw)) result = append(result, parseRawData(dataRaw))
} }
return result, nil return result, executeErr
} }
// ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed
@ -153,13 +159,19 @@ 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 // 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 // conntrack -D [table] parameters Delete conntrack or expectation
func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) {
var finalErr error
res, err := h.dumpConntrackTable(table, family) res, err := h.dumpConntrackTable(table, family)
if err != nil { 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.
finalErr = err
} }
var totalFilterErrors int
var matched uint var matched uint
var errMsgs []string
for _, dataRaw := range res { for _, dataRaw := range res {
flow := parseRawData(dataRaw) flow := parseRawData(dataRaw)
for _, filter := range filters { for _, filter := range filters {
@ -167,19 +179,20 @@ func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFam
req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK) req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK)
// skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already // skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already
req2.AddRawData(dataRaw[4:]) req2.AddRawData(dataRaw[4:])
if _, err = req2.Execute(unix.NETLINK_NETFILTER, 0); err == nil { if _, err = req2.Execute(unix.NETLINK_NETFILTER, 0); err == nil || errors.Is(err, fs.ErrNotExist) {
matched++ matched++
// flow is already deleted, no need to match on other filters and continue to the next flow. // flow is already deleted, no need to match on other filters and continue to the next flow.
break break
} else {
totalFilterErrors++
} }
errMsgs = append(errMsgs, fmt.Sprintf("failed to delete conntrack flow '%s': %s", flow.String(), err.Error()))
} }
} }
} }
if len(errMsgs) > 0 { if totalFilterErrors > 0 {
return matched, fmt.Errorf(strings.Join(errMsgs, "; ")) finalErr = errors.Join(finalErr, fmt.Errorf("failed to delete %d conntrack flows with %d filters", totalFilterErrors, len(filters)))
} }
return matched, nil return matched, finalErr
} }
func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest { func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest {

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@ -466,6 +467,8 @@ func (h *Handle) getEswitchAttrs(family *GenlFamily, dev *DevlinkDevice) {
// DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // DevLinkGetDeviceList provides a pointer to devlink devices and nil error,
// otherwise returns an error code. // otherwise returns an error code.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) { func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME)
if err != nil { if err != nil {
@ -478,9 +481,9 @@ func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
req := h.newNetlinkRequest(int(f.ID), req := h.newNetlinkRequest(int(f.ID),
unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
devices, err := parseDevLinkDeviceList(msgs) devices, err := parseDevLinkDeviceList(msgs)
if err != nil { if err != nil {
@ -489,11 +492,14 @@ func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
for _, d := range devices { for _, d := range devices {
h.getEswitchAttrs(f, d) h.getEswitchAttrs(f, d)
} }
return devices, nil return devices, executeErr
} }
// DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // DevLinkGetDeviceList provides a pointer to devlink devices and nil error,
// otherwise returns an error code. // otherwise returns an error code.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func DevLinkGetDeviceList() ([]*DevlinkDevice, error) { func DevLinkGetDeviceList() ([]*DevlinkDevice, error) {
return pkgHandle.DevLinkGetDeviceList() return pkgHandle.DevLinkGetDeviceList()
} }
@ -646,6 +652,8 @@ func parseDevLinkAllPortList(msgs [][]byte) ([]*DevlinkPort, error) {
// DevLinkGetPortList provides a pointer to devlink ports and nil error, // DevLinkGetPortList provides a pointer to devlink ports and nil error,
// otherwise returns an error code. // otherwise returns an error code.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) { func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) {
f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME)
if err != nil { if err != nil {
@ -658,19 +666,21 @@ func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) {
req := h.newNetlinkRequest(int(f.ID), req := h.newNetlinkRequest(int(f.ID),
unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
ports, err := parseDevLinkAllPortList(msgs) ports, err := parseDevLinkAllPortList(msgs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ports, nil return ports, executeErr
} }
// DevLinkGetPortList provides a pointer to devlink ports and nil error, // DevLinkGetPortList provides a pointer to devlink ports and nil error,
// otherwise returns an error code. // otherwise returns an error code.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func DevLinkGetAllPortList() ([]*DevlinkPort, error) { func DevLinkGetAllPortList() ([]*DevlinkPort, error) {
return pkgHandle.DevLinkGetAllPortList() return pkgHandle.DevLinkGetAllPortList()
} }
@ -738,15 +748,18 @@ func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkR
// DevlinkGetDeviceParams returns parameters for devlink device // DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>` // Equivalent to: `devlink dev param show <bus>/<device>`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
_, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Flags |= unix.NLM_F_DUMP req.Flags |= unix.NLM_F_DUMP
respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) respmsg, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var params []*DevlinkParam var params []*DevlinkParam
for _, m := range respmsg { for _, m := range respmsg {
@ -761,11 +774,14 @@ func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkPa
params = append(params, p) params = append(params, p)
} }
return params, nil return params, executeErr
} }
// DevlinkGetDeviceParams returns parameters for devlink device // DevlinkGetDeviceParams returns parameters for devlink device
// Equivalent to: `devlink dev param show <bus>/<device>` // Equivalent to: `devlink dev param show <bus>/<device>`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) {
return pkgHandle.DevlinkGetDeviceParams(bus, device) return pkgHandle.DevlinkGetDeviceParams(bus, device)
} }

View File

@ -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 type MirredAct uint8
func (a MirredAct) String() string { func (a MirredAct) String() string {

View File

@ -65,6 +65,9 @@ type Flower struct {
EncSrcIPMask net.IPMask EncSrcIPMask net.IPMask
EncDestPort uint16 EncDestPort uint16
EncKeyId uint32 EncKeyId uint32
SrcMac net.HardwareAddr
DestMac net.HardwareAddr
VlanId uint16
SkipHw bool SkipHw bool
SkipSw bool SkipSw bool
IPProto *nl.IPProto IPProto *nl.IPProto
@ -135,6 +138,15 @@ func (filter *Flower) encode(parent *nl.RtAttr) error {
if filter.EncKeyId != 0 { if filter.EncKeyId != 0 {
parent.AddRtAttr(nl.TCA_FLOWER_KEY_ENC_KEY_ID, htonl(filter.EncKeyId)) 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 { if filter.IPProto != nil {
ipproto := *filter.IPProto ipproto := *filter.IPProto
parent.AddRtAttr(nl.TCA_FLOWER_KEY_IP_PROTO, ipproto.Serialize()) 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) filter.EncDestPort = ntohs(datum.Value)
case nl.TCA_FLOWER_KEY_ENC_KEY_ID: case nl.TCA_FLOWER_KEY_ENC_KEY_ID:
filter.EncKeyId = ntohl(datum.Value) 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: case nl.TCA_FLOWER_KEY_IP_PROTO:
val := new(nl.IPProto) val := new(nl.IPProto)
*val = nl.IPProto(datum.Value[0]) *val = nl.IPProto(datum.Value[0])
@ -405,14 +424,20 @@ func (h *Handle) filterModify(filter Filter, proto, flags int) error {
// FilterList gets a list of filters in the system. // FilterList gets a list of filters in the system.
// Equivalent to: `tc filter show`. // Equivalent to: `tc filter show`.
//
// Generally returns nothing if link and parent are not specified. // Generally returns nothing if link and parent are not specified.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func FilterList(link Link, parent uint32) ([]Filter, error) { func FilterList(link Link, parent uint32) ([]Filter, error) {
return pkgHandle.FilterList(link, parent) return pkgHandle.FilterList(link, parent)
} }
// FilterList gets a list of filters in the system. // FilterList gets a list of filters in the system.
// Equivalent to: `tc filter show`. // Equivalent to: `tc filter show`.
//
// Generally returns nothing if link and parent are not specified. // Generally returns nothing if link and parent are not specified.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) {
req := h.newNetlinkRequest(unix.RTM_GETTFILTER, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETTFILTER, unix.NLM_F_DUMP)
msg := &nl.TcMsg{ msg := &nl.TcMsg{
@ -426,9 +451,9 @@ func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) {
} }
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTFILTER) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTFILTER)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Filter var res []Filter
@ -516,7 +541,7 @@ func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) {
} }
} }
return res, nil return res, executeErr
} }
func toTcGen(attrs *ActionAttrs, tcgen *nl.TcGen) { func toTcGen(attrs *ActionAttrs, tcgen *nl.TcGen) {
@ -616,6 +641,22 @@ func EncodeActions(attr *nl.RtAttr, actions []Action) error {
} }
toTcGen(action.Attrs(), &mirred.TcGen) toTcGen(action.Attrs(), &mirred.TcGen)
aopts.AddRtAttr(nl.TCA_MIRRED_PARMS, mirred.Serialize()) 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: case *TunnelKeyAction:
table := attr.AddRtAttr(tabIndex, nil) table := attr.AddRtAttr(tabIndex, nil)
tabIndex++ tabIndex++
@ -786,6 +827,8 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) {
action = &CsumAction{} action = &CsumAction{}
case "gact": case "gact":
action = &GenericAction{} action = &GenericAction{}
case "vlan":
action = &VlanAction{}
case "tunnel_key": case "tunnel_key":
action = &TunnelKeyAction{} action = &TunnelKeyAction{}
case "skbedit": case "skbedit":
@ -816,7 +859,17 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) {
tcTs := nl.DeserializeTcf(adatum.Value) tcTs := nl.DeserializeTcf(adatum.Value)
actionTimestamp = toTimeStamp(tcTs) 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": case "tunnel_key":
switch adatum.Attr.Type { switch adatum.Attr.Type {
case nl.TCA_TUNNEL_KEY_PARMS: case nl.TCA_TUNNEL_KEY_PARMS:

View File

@ -1,16 +1,7 @@
package netlink package netlink
import ( import (
"errors" "net"
)
var (
// ErrAttrHeaderTruncated is returned when a netlink attribute's header is
// truncated.
ErrAttrHeaderTruncated = errors.New("attribute header truncated")
// ErrAttrBodyTruncated is returned when a netlink attribute's body is
// truncated.
ErrAttrBodyTruncated = errors.New("attribute body truncated")
) )
type Fou struct { type Fou struct {
@ -18,4 +9,8 @@ type Fou struct {
Port int Port int
Protocol int Protocol int
EncapType int EncapType int
Local net.IP
Peer net.IP
PeerPort int
IfIndex int
} }

View File

@ -1,3 +1,4 @@
//go:build linux
// +build linux // +build linux
package netlink package netlink
@ -5,6 +6,8 @@ package netlink
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"log"
"net"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -29,6 +32,12 @@ const (
FOU_ATTR_IPPROTO FOU_ATTR_IPPROTO
FOU_ATTR_TYPE FOU_ATTR_TYPE
FOU_ATTR_REMCSUM_NOPARTIAL FOU_ATTR_REMCSUM_NOPARTIAL
FOU_ATTR_LOCAL_V4
FOU_ATTR_LOCAL_V6
FOU_ATTR_PEER_V4
FOU_ATTR_PEER_V6
FOU_ATTR_PEER_PORT
FOU_ATTR_IFINDEX
FOU_ATTR_MAX = FOU_ATTR_REMCSUM_NOPARTIAL FOU_ATTR_MAX = FOU_ATTR_REMCSUM_NOPARTIAL
) )
@ -128,10 +137,14 @@ func (h *Handle) FouDel(f Fou) error {
return nil return nil
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func FouList(fam int) ([]Fou, error) { func FouList(fam int) ([]Fou, error) {
return pkgHandle.FouList(fam) return pkgHandle.FouList(fam)
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) FouList(fam int) ([]Fou, error) { func (h *Handle) FouList(fam int) ([]Fou, error) {
fam_id, err := FouFamilyId() fam_id, err := FouFamilyId()
if err != nil { if err != nil {
@ -150,9 +163,9 @@ func (h *Handle) FouList(fam int) ([]Fou, error) {
req.AddRawData(raw) req.AddRawData(raw)
msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil { if executeErr != nil && !errors.Is(err, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
fous := make([]Fou, 0, len(msgs)) fous := make([]Fou, 0, len(msgs))
@ -165,45 +178,32 @@ func (h *Handle) FouList(fam int) ([]Fou, error) {
fous = append(fous, f) fous = append(fous, f)
} }
return fous, nil return fous, executeErr
} }
func deserializeFouMsg(msg []byte) (Fou, error) { func deserializeFouMsg(msg []byte) (Fou, error) {
// we'll skip to byte 4 to first attribute
msg = msg[3:]
var shift int
fou := Fou{} fou := Fou{}
for { for attr := range nl.ParseAttributes(msg[4:]) {
// attribute header is at least 16 bits switch attr.Type {
if len(msg) < 4 {
return fou, ErrAttrHeaderTruncated
}
lgt := int(binary.BigEndian.Uint16(msg[0:2]))
if len(msg) < lgt+4 {
return fou, ErrAttrBodyTruncated
}
attr := binary.BigEndian.Uint16(msg[2:4])
shift = lgt + 3
switch attr {
case FOU_ATTR_AF: case FOU_ATTR_AF:
fou.Family = int(msg[5]) fou.Family = int(attr.Value[0])
case FOU_ATTR_PORT: case FOU_ATTR_PORT:
fou.Port = int(binary.BigEndian.Uint16(msg[5:7])) fou.Port = int(networkOrder.Uint16(attr.Value))
// port is 2 bytes
shift = lgt + 2
case FOU_ATTR_IPPROTO: case FOU_ATTR_IPPROTO:
fou.Protocol = int(msg[5]) fou.Protocol = int(attr.Value[0])
case FOU_ATTR_TYPE: case FOU_ATTR_TYPE:
fou.EncapType = int(msg[5]) fou.EncapType = int(attr.Value[0])
} case FOU_ATTR_LOCAL_V4, FOU_ATTR_LOCAL_V6:
fou.Local = net.IP(attr.Value)
msg = msg[shift:] case FOU_ATTR_PEER_V4, FOU_ATTR_PEER_V6:
fou.Peer = net.IP(attr.Value)
if len(msg) < 4 { case FOU_ATTR_PEER_PORT:
break fou.PeerPort = int(networkOrder.Uint16(attr.Value))
case FOU_ATTR_IFINDEX:
fou.IfIndex = int(native.Uint16(attr.Value))
default:
log.Printf("unknown fou attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK)
} }
} }

View File

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux // +build !linux
package netlink package netlink

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"syscall" "syscall"
@ -126,6 +127,8 @@ func parseFamilies(msgs [][]byte) ([]*GenlFamily, error) {
return families, nil return families, nil
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) { func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) {
msg := &nl.Genlmsg{ msg := &nl.Genlmsg{
Command: nl.GENL_CTRL_CMD_GETFAMILY, Command: nl.GENL_CTRL_CMD_GETFAMILY,
@ -133,13 +136,19 @@ func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) {
} }
req := h.newNetlinkRequest(nl.GENL_ID_CTRL, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.GENL_ID_CTRL, unix.NLM_F_DUMP)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, executeErr
}
families, err := parseFamilies(msgs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parseFamilies(msgs) return families, executeErr
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func GenlFamilyList() ([]*GenlFamily, error) { func GenlFamilyList() ([]*GenlFamily, error) {
return pkgHandle.GenlFamilyList() return pkgHandle.GenlFamilyList()
} }

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@ -74,6 +75,8 @@ func parsePDP(msgs [][]byte) ([]*PDP, error) {
return pdps, nil return pdps, nil
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) GTPPDPList() ([]*PDP, error) { func (h *Handle) GTPPDPList() ([]*PDP, error) {
f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME)
if err != nil { if err != nil {
@ -85,13 +88,19 @@ func (h *Handle) GTPPDPList() ([]*PDP, error) {
} }
req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_DUMP) req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_DUMP)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if executeErr != nil && !errors.Is(err, ErrDumpInterrupted) {
return nil, executeErr
}
pdps, err := parsePDP(msgs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parsePDP(msgs) return pdps, executeErr
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func GTPPDPList() ([]*PDP, error) { func GTPPDPList() ([]*PDP, error) {
return pkgHandle.GTPPDPList() return pkgHandle.GTPPDPList()
} }

View File

@ -56,6 +56,8 @@ type LinkAttrs struct {
Vfs []VfInfo // virtual functions available on link Vfs []VfInfo // virtual functions available on link
Group uint32 Group uint32
PermHWAddr net.HardwareAddr PermHWAddr net.HardwareAddr
ParentDev string
ParentDevBus string
Slave LinkSlave Slave LinkSlave
} }
@ -377,6 +379,13 @@ const (
NETKIT_POLICY_BLACKHOLE NetkitPolicy = 2 NETKIT_POLICY_BLACKHOLE NetkitPolicy = 2
) )
type NetkitScrub int
const (
NETKIT_SCRUB_NONE NetkitScrub = 0
NETKIT_SCRUB_DEFAULT NetkitScrub = 1
)
func (n *Netkit) IsPrimary() bool { func (n *Netkit) IsPrimary() bool {
return n.isPrimary return n.isPrimary
} }
@ -391,6 +400,9 @@ type Netkit struct {
Mode NetkitMode Mode NetkitMode
Policy NetkitPolicy Policy NetkitPolicy
PeerPolicy NetkitPolicy PeerPolicy NetkitPolicy
Scrub NetkitScrub
PeerScrub NetkitScrub
supportsScrub bool
isPrimary bool isPrimary bool
peerLinkAttrs LinkAttrs peerLinkAttrs LinkAttrs
} }
@ -403,6 +415,10 @@ func (n *Netkit) Type() string {
return "netkit" return "netkit"
} }
func (n *Netkit) SupportsScrub() bool {
return n.supportsScrub
}
// Veth devices must specify PeerName on create // Veth devices must specify PeerName on create
type Veth struct { type Veth struct {
LinkAttrs LinkAttrs
@ -761,19 +777,19 @@ const (
) )
var bondXmitHashPolicyToString = map[BondXmitHashPolicy]string{ var bondXmitHashPolicyToString = map[BondXmitHashPolicy]string{
BOND_XMIT_HASH_POLICY_LAYER2: "layer2", BOND_XMIT_HASH_POLICY_LAYER2: "layer2",
BOND_XMIT_HASH_POLICY_LAYER3_4: "layer3+4", BOND_XMIT_HASH_POLICY_LAYER3_4: "layer3+4",
BOND_XMIT_HASH_POLICY_LAYER2_3: "layer2+3", BOND_XMIT_HASH_POLICY_LAYER2_3: "layer2+3",
BOND_XMIT_HASH_POLICY_ENCAP2_3: "encap2+3", BOND_XMIT_HASH_POLICY_ENCAP2_3: "encap2+3",
BOND_XMIT_HASH_POLICY_ENCAP3_4: "encap3+4", BOND_XMIT_HASH_POLICY_ENCAP3_4: "encap3+4",
BOND_XMIT_HASH_POLICY_VLAN_SRCMAC: "vlan+srcmac", BOND_XMIT_HASH_POLICY_VLAN_SRCMAC: "vlan+srcmac",
} }
var StringToBondXmitHashPolicyMap = map[string]BondXmitHashPolicy{ var StringToBondXmitHashPolicyMap = map[string]BondXmitHashPolicy{
"layer2": BOND_XMIT_HASH_POLICY_LAYER2, "layer2": BOND_XMIT_HASH_POLICY_LAYER2,
"layer3+4": BOND_XMIT_HASH_POLICY_LAYER3_4, "layer3+4": BOND_XMIT_HASH_POLICY_LAYER3_4,
"layer2+3": BOND_XMIT_HASH_POLICY_LAYER2_3, "layer2+3": BOND_XMIT_HASH_POLICY_LAYER2_3,
"encap2+3": BOND_XMIT_HASH_POLICY_ENCAP2_3, "encap2+3": BOND_XMIT_HASH_POLICY_ENCAP2_3,
"encap3+4": BOND_XMIT_HASH_POLICY_ENCAP3_4, "encap3+4": BOND_XMIT_HASH_POLICY_ENCAP3_4,
"vlan+srcmac": BOND_XMIT_HASH_POLICY_VLAN_SRCMAC, "vlan+srcmac": BOND_XMIT_HASH_POLICY_VLAN_SRCMAC,
} }

View File

@ -3,6 +3,7 @@ package netlink
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@ -1807,20 +1808,20 @@ func (h *Handle) LinkDel(link Link) error {
} }
func (h *Handle) linkByNameDump(name string) (Link, error) { func (h *Handle) linkByNameDump(name string) (Link, error) {
links, err := h.LinkList() links, executeErr := h.LinkList()
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
for _, link := range links { for _, link := range links {
if link.Attrs().Name == name { if link.Attrs().Name == name {
return link, nil return link, executeErr
} }
// support finding interfaces also via altnames // support finding interfaces also via altnames
for _, altName := range link.Attrs().AltNames { for _, altName := range link.Attrs().AltNames {
if altName == name { if altName == name {
return link, nil return link, executeErr
} }
} }
} }
@ -1828,25 +1829,33 @@ func (h *Handle) linkByNameDump(name string) (Link, error) {
} }
func (h *Handle) linkByAliasDump(alias string) (Link, error) { func (h *Handle) linkByAliasDump(alias string) (Link, error) {
links, err := h.LinkList() links, executeErr := h.LinkList()
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
for _, link := range links { for _, link := range links {
if link.Attrs().Alias == alias { if link.Attrs().Alias == alias {
return link, nil return link, executeErr
} }
} }
return nil, LinkNotFoundError{fmt.Errorf("Link alias %s not found", alias)} return nil, LinkNotFoundError{fmt.Errorf("Link alias %s not found", alias)}
} }
// LinkByName finds a link by name and returns a pointer to the object. // LinkByName finds a link by name and returns a pointer to the object.
//
// If the kernel doesn't support IFLA_IFNAME, this method will fall back to
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func LinkByName(name string) (Link, error) { func LinkByName(name string) (Link, error) {
return pkgHandle.LinkByName(name) return pkgHandle.LinkByName(name)
} }
// LinkByName finds a link by name and returns a pointer to the object. // LinkByName finds a link by name and returns a pointer to the object.
//
// If the kernel doesn't support IFLA_IFNAME, this method will fall back to
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func (h *Handle) LinkByName(name string) (Link, error) { func (h *Handle) LinkByName(name string) (Link, error) {
if h.lookupByDump { if h.lookupByDump {
return h.linkByNameDump(name) return h.linkByNameDump(name)
@ -1879,12 +1888,20 @@ func (h *Handle) LinkByName(name string) (Link, error) {
// LinkByAlias finds a link by its alias and returns a pointer to the object. // LinkByAlias finds a link by its alias and returns a pointer to the object.
// If there are multiple links with the alias it returns the first one // If there are multiple links with the alias it returns the first one
//
// If the kernel doesn't support IFLA_IFALIAS, this method will fall back to
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func LinkByAlias(alias string) (Link, error) { func LinkByAlias(alias string) (Link, error) {
return pkgHandle.LinkByAlias(alias) return pkgHandle.LinkByAlias(alias)
} }
// LinkByAlias finds a link by its alias and returns a pointer to the object. // LinkByAlias finds a link by its alias and returns a pointer to the object.
// If there are multiple links with the alias it returns the first one // If there are multiple links with the alias it returns the first one
//
// If the kernel doesn't support IFLA_IFALIAS, this method will fall back to
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func (h *Handle) LinkByAlias(alias string) (Link, error) { func (h *Handle) LinkByAlias(alias string) (Link, error) {
if h.lookupByDump { if h.lookupByDump {
return h.linkByAliasDump(alias) return h.linkByAliasDump(alias)
@ -2246,6 +2263,10 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) {
break break
} }
} }
case unix.IFLA_PARENT_DEV_NAME:
base.ParentDev = string(attr.Value[:len(attr.Value)-1])
case unix.IFLA_PARENT_DEV_BUS_NAME:
base.ParentDevBus = string(attr.Value[:len(attr.Value)-1])
} }
} }
@ -2321,6 +2342,9 @@ func LinkList() ([]Link, error) {
// LinkList gets a list of link devices. // LinkList gets a list of link devices.
// Equivalent to: `ip link show` // Equivalent to: `ip link show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) LinkList() ([]Link, error) { func (h *Handle) LinkList() ([]Link, error) {
// NOTE(vish): This duplicates functionality in net/iface_linux.go, but we need // NOTE(vish): This duplicates functionality in net/iface_linux.go, but we need
// to get the message ourselves to parse link type. // to get the message ourselves to parse link type.
@ -2331,9 +2355,9 @@ func (h *Handle) LinkList() ([]Link, error) {
attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF))
req.AddData(attr) req.AddData(attr)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Link var res []Link
@ -2345,7 +2369,7 @@ func (h *Handle) LinkList() ([]Link, error) {
res = append(res, link) res = append(res, link)
} }
return res, nil return res, executeErr
} }
// LinkUpdate is used to pass information back from LinkSubscribe() // LinkUpdate is used to pass information back from LinkSubscribe()
@ -2381,6 +2405,10 @@ type LinkSubscribeOptions struct {
// LinkSubscribeWithOptions work like LinkSubscribe but enable to // LinkSubscribeWithOptions work like LinkSubscribe but enable to
// provide additional options to modify the behavior. Currently, the // provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback. // namespace can be provided as well as an error callback.
//
// When options.ListExisting is true, options.ErrorCallback may be
// called with [ErrDumpInterrupted] to indicate that results from
// the initial dump of links may be inconsistent or incomplete.
func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error { func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error {
if options.Namespace == nil { if options.Namespace == nil {
none := netns.None() none := netns.None()
@ -2440,6 +2468,9 @@ func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-c
continue continue
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil {
cberr(ErrDumpInterrupted)
}
if m.Header.Type == unix.NLMSG_DONE { if m.Header.Type == unix.NLMSG_DONE {
continue continue
} }
@ -2649,6 +2680,8 @@ func addNetkitAttrs(nk *Netkit, linkInfo *nl.RtAttr, flag int) error {
data.AddRtAttr(nl.IFLA_NETKIT_MODE, nl.Uint32Attr(uint32(nk.Mode))) data.AddRtAttr(nl.IFLA_NETKIT_MODE, nl.Uint32Attr(uint32(nk.Mode)))
data.AddRtAttr(nl.IFLA_NETKIT_POLICY, nl.Uint32Attr(uint32(nk.Policy))) data.AddRtAttr(nl.IFLA_NETKIT_POLICY, nl.Uint32Attr(uint32(nk.Policy)))
data.AddRtAttr(nl.IFLA_NETKIT_PEER_POLICY, nl.Uint32Attr(uint32(nk.PeerPolicy))) data.AddRtAttr(nl.IFLA_NETKIT_PEER_POLICY, nl.Uint32Attr(uint32(nk.PeerPolicy)))
data.AddRtAttr(nl.IFLA_NETKIT_SCRUB, nl.Uint32Attr(uint32(nk.Scrub)))
data.AddRtAttr(nl.IFLA_NETKIT_PEER_SCRUB, nl.Uint32Attr(uint32(nk.PeerScrub)))
if (flag & unix.NLM_F_EXCL) == 0 { if (flag & unix.NLM_F_EXCL) == 0 {
// Modifying peer link attributes will not take effect // Modifying peer link attributes will not take effect
@ -2709,6 +2742,12 @@ func parseNetkitData(link Link, data []syscall.NetlinkRouteAttr) {
netkit.Policy = NetkitPolicy(native.Uint32(datum.Value[0:4])) netkit.Policy = NetkitPolicy(native.Uint32(datum.Value[0:4]))
case nl.IFLA_NETKIT_PEER_POLICY: case nl.IFLA_NETKIT_PEER_POLICY:
netkit.PeerPolicy = NetkitPolicy(native.Uint32(datum.Value[0:4])) netkit.PeerPolicy = NetkitPolicy(native.Uint32(datum.Value[0:4]))
case nl.IFLA_NETKIT_SCRUB:
netkit.supportsScrub = true
netkit.Scrub = NetkitScrub(native.Uint32(datum.Value[0:4]))
case nl.IFLA_NETKIT_PEER_SCRUB:
netkit.supportsScrub = true
netkit.PeerScrub = NetkitScrub(native.Uint32(datum.Value[0:4]))
} }
} }
} }
@ -3006,7 +3045,6 @@ func parseMacvlanData(link Link, data []syscall.NetlinkRouteAttr) {
} }
} }
// copied from pkg/net_linux.go
func linkFlags(rawFlags uint32) net.Flags { func linkFlags(rawFlags uint32) net.Flags {
var f net.Flags var f net.Flags
if rawFlags&unix.IFF_UP != 0 { if rawFlags&unix.IFF_UP != 0 {
@ -3024,6 +3062,9 @@ func linkFlags(rawFlags uint32) net.Flags {
if rawFlags&unix.IFF_MULTICAST != 0 { if rawFlags&unix.IFF_MULTICAST != 0 {
f |= net.FlagMulticast f |= net.FlagMulticast
} }
if rawFlags&unix.IFF_RUNNING != 0 {
f |= net.FlagRunning
}
return f return f
} }

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"syscall" "syscall"
@ -206,6 +207,9 @@ func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error {
// NeighList returns a list of IP-MAC mappings in the system (ARP table). // NeighList returns a list of IP-MAC mappings in the system (ARP table).
// Equivalent to: `ip neighbor show`. // Equivalent to: `ip neighbor show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func NeighList(linkIndex, family int) ([]Neigh, error) { func NeighList(linkIndex, family int) ([]Neigh, error) {
return pkgHandle.NeighList(linkIndex, family) return pkgHandle.NeighList(linkIndex, family)
} }
@ -213,6 +217,9 @@ func NeighList(linkIndex, family int) ([]Neigh, error) {
// NeighProxyList returns a list of neighbor proxies in the system. // NeighProxyList returns a list of neighbor proxies in the system.
// Equivalent to: `ip neighbor show proxy`. // Equivalent to: `ip neighbor show proxy`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func NeighProxyList(linkIndex, family int) ([]Neigh, error) { func NeighProxyList(linkIndex, family int) ([]Neigh, error) {
return pkgHandle.NeighProxyList(linkIndex, family) return pkgHandle.NeighProxyList(linkIndex, family)
} }
@ -220,6 +227,9 @@ func NeighProxyList(linkIndex, family int) ([]Neigh, error) {
// NeighList returns a list of IP-MAC mappings in the system (ARP table). // NeighList returns a list of IP-MAC mappings in the system (ARP table).
// Equivalent to: `ip neighbor show`. // Equivalent to: `ip neighbor show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) { func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) {
return h.NeighListExecute(Ndmsg{ return h.NeighListExecute(Ndmsg{
Family: uint8(family), Family: uint8(family),
@ -230,6 +240,9 @@ func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) {
// NeighProxyList returns a list of neighbor proxies in the system. // NeighProxyList returns a list of neighbor proxies in the system.
// Equivalent to: `ip neighbor show proxy`. // Equivalent to: `ip neighbor show proxy`.
// The list can be filtered by link, ip family. // The list can be filtered by link, ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) { func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) {
return h.NeighListExecute(Ndmsg{ return h.NeighListExecute(Ndmsg{
Family: uint8(family), Family: uint8(family),
@ -239,18 +252,24 @@ func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) {
} }
// NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func NeighListExecute(msg Ndmsg) ([]Neigh, error) { func NeighListExecute(msg Ndmsg) ([]Neigh, error) {
return pkgHandle.NeighListExecute(msg) return pkgHandle.NeighListExecute(msg)
} }
// NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) { func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) {
req := h.newNetlinkRequest(unix.RTM_GETNEIGH, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETNEIGH, unix.NLM_F_DUMP)
req.AddData(&msg) req.AddData(&msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNEIGH) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNEIGH)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Neigh var res []Neigh
@ -281,7 +300,7 @@ func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) {
res = append(res, *neigh) res = append(res, *neigh)
} }
return res, nil return res, executeErr
} }
func NeighDeserialize(m []byte) (*Neigh, error) { func NeighDeserialize(m []byte) (*Neigh, error) {
@ -364,6 +383,10 @@ type NeighSubscribeOptions struct {
// NeighSubscribeWithOptions work like NeighSubscribe but enable to // NeighSubscribeWithOptions work like NeighSubscribe but enable to
// provide additional options to modify the behavior. Currently, the // provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback. // namespace can be provided as well as an error callback.
//
// When options.ListExisting is true, options.ErrorCallback may be
// called with [ErrDumpInterrupted] to indicate that results from
// the initial dump of links may be inconsistent or incomplete.
func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, options NeighSubscribeOptions) error { func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, options NeighSubscribeOptions) error {
if options.Namespace == nil { if options.Namespace == nil {
none := netns.None() none := netns.None()
@ -428,6 +451,9 @@ func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <
continue continue
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil {
cberr(ErrDumpInterrupted)
}
if m.Header.Type == unix.NLMSG_DONE { if m.Header.Type == unix.NLMSG_DONE {
if listExisting { if listExisting {
// This will be called after handling AF_UNSPEC // This will be called after handling AF_UNSPEC

View File

@ -9,3 +9,6 @@ const (
FAMILY_V6 = nl.FAMILY_V6 FAMILY_V6 = nl.FAMILY_V6
FAMILY_MPLS = nl.FAMILY_MPLS FAMILY_MPLS = nl.FAMILY_MPLS
) )
// ErrDumpInterrupted is an alias for [nl.ErrDumpInterrupted].
var ErrDumpInterrupted = nl.ErrDumpInterrupted

View File

@ -38,6 +38,8 @@ const (
IFLA_NETKIT_POLICY IFLA_NETKIT_POLICY
IFLA_NETKIT_PEER_POLICY IFLA_NETKIT_PEER_POLICY
IFLA_NETKIT_MODE IFLA_NETKIT_MODE
IFLA_NETKIT_SCRUB
IFLA_NETKIT_PEER_SCRUB
IFLA_NETKIT_MAX = IFLA_NETKIT_MODE IFLA_NETKIT_MAX = IFLA_NETKIT_MODE
) )

View File

@ -45,6 +45,26 @@ var SocketTimeoutTv = unix.Timeval{Sec: 60, Usec: 0}
// ErrorMessageReporting is the default error message reporting configuration for the new netlink sockets // ErrorMessageReporting is the default error message reporting configuration for the new netlink sockets
var EnableErrorMessageReporting bool = false var EnableErrorMessageReporting bool = false
// ErrDumpInterrupted is an instance of errDumpInterrupted, used to report that
// a netlink function has set the NLM_F_DUMP_INTR flag in a response message,
// indicating that the results may be incomplete or inconsistent.
var ErrDumpInterrupted = errDumpInterrupted{}
// errDumpInterrupted is an error type, used to report that NLM_F_DUMP_INTR was
// set in a netlink response.
type errDumpInterrupted struct{}
func (errDumpInterrupted) Error() string {
return "results may be incomplete or inconsistent"
}
// Before errDumpInterrupted was introduced, EINTR was returned when a netlink
// response had NLM_F_DUMP_INTR. Retain backward compatibility with code that
// may be checking for EINTR using Is.
func (e errDumpInterrupted) Is(target error) bool {
return target == unix.EINTR
}
// GetIPFamily returns the family type of a net.IP. // GetIPFamily returns the family type of a net.IP.
func GetIPFamily(ip net.IP) int { func GetIPFamily(ip net.IP) int {
if len(ip) <= net.IPv4len { if len(ip) <= net.IPv4len {
@ -494,22 +514,26 @@ func (req *NetlinkRequest) AddRawData(data []byte) {
// Execute the request against the given sockType. // Execute the request against the given sockType.
// Returns a list of netlink messages in serialized format, optionally filtered // Returns a list of netlink messages in serialized format, optionally filtered
// by resType. // by resType.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) { func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) {
var res [][]byte var res [][]byte
err := req.ExecuteIter(sockType, resType, func(msg []byte) bool { err := req.ExecuteIter(sockType, resType, func(msg []byte) bool {
res = append(res, msg) res = append(res, msg)
return true return true
}) })
if err != nil { if err != nil && !errors.Is(err, ErrDumpInterrupted) {
return nil, err return nil, err
} }
return res, nil return res, err
} }
// ExecuteIter executes the request against the given sockType. // ExecuteIter executes the request against the given sockType.
// Calls the provided callback func once for each netlink message. // Calls the provided callback func once for each netlink message.
// If the callback returns false, it is not called again, but // If the callback returns false, it is not called again, but
// the remaining messages are consumed/discarded. // the remaining messages are consumed/discarded.
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
// //
// Thread safety: ExecuteIter holds a lock on the socket until // Thread safety: ExecuteIter holds a lock on the socket until
// it finishes iteration so the callback must not call back into // it finishes iteration so the callback must not call back into
@ -561,6 +585,8 @@ func (req *NetlinkRequest) ExecuteIter(sockType int, resType uint16, f func(msg
return err return err
} }
dumpIntr := false
done: done:
for { for {
msgs, from, err := s.Receive() msgs, from, err := s.Receive()
@ -582,7 +608,7 @@ done:
} }
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 { if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 {
return syscall.Errno(unix.EINTR) dumpIntr = true
} }
if m.Header.Type == unix.NLMSG_DONE || m.Header.Type == unix.NLMSG_ERROR { if m.Header.Type == unix.NLMSG_DONE || m.Header.Type == unix.NLMSG_ERROR {
@ -636,6 +662,9 @@ done:
} }
} }
} }
if dumpIntr {
return ErrDumpInterrupted
}
return nil return nil
} }

View File

@ -115,6 +115,7 @@ const (
SizeofTcConnmark = SizeofTcGen + 0x04 SizeofTcConnmark = SizeofTcGen + 0x04
SizeofTcCsum = SizeofTcGen + 0x04 SizeofTcCsum = SizeofTcGen + 0x04
SizeofTcMirred = SizeofTcGen + 0x08 SizeofTcMirred = SizeofTcGen + 0x08
SizeofTcVlan = SizeofTcGen + 0x04
SizeofTcTunnelKey = SizeofTcGen + 0x04 SizeofTcTunnelKey = SizeofTcGen + 0x04
SizeofTcSkbEdit = SizeofTcGen SizeofTcSkbEdit = SizeofTcGen
SizeofTcPolice = 2*SizeofTcRateSpec + 0x20 SizeofTcPolice = 2*SizeofTcRateSpec + 0x20
@ -816,6 +817,41 @@ func (x *TcMirred) Serialize() []byte {
return (*(*[SizeofTcMirred]byte)(unsafe.Pointer(x)))[:] 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 ( const (
TCA_TUNNEL_KEY_UNSPEC = iota TCA_TUNNEL_KEY_UNSPEC = iota
TCA_TUNNEL_KEY_TM TCA_TUNNEL_KEY_TM
@ -1239,8 +1275,8 @@ const (
) )
// /* TCA_PEDIT_KEY_EX_HDR_TYPE_NETWROK is a special case for legacy users. It // /* 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 type PeditHeaderType uint16
const ( const (

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"syscall" "syscall"
@ -8,10 +9,14 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func LinkGetProtinfo(link Link) (Protinfo, error) { func LinkGetProtinfo(link Link) (Protinfo, error) {
return pkgHandle.LinkGetProtinfo(link) return pkgHandle.LinkGetProtinfo(link)
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) {
base := link.Attrs() base := link.Attrs()
h.ensureIndex(base) h.ensureIndex(base)
@ -19,9 +24,9 @@ func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) {
req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP)
msg := nl.NewIfInfomsg(unix.AF_BRIDGE) msg := nl.NewIfInfomsg(unix.AF_BRIDGE)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return pi, err return pi, executeErr
} }
for _, m := range msgs { for _, m := range msgs {
@ -43,7 +48,7 @@ func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) {
} }
pi = parseProtinfo(infos) pi = parseProtinfo(infos)
return pi, nil return pi, executeErr
} }
} }
return pi, fmt.Errorf("Device with index %d not found", base.Index) return pi, fmt.Errorf("Device with index %d not found", base.Index)

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strconv" "strconv"
@ -338,6 +339,9 @@ func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error {
// QdiscList gets a list of qdiscs in the system. // QdiscList gets a list of qdiscs in the system.
// Equivalent to: `tc qdisc show`. // Equivalent to: `tc qdisc show`.
// The list can be filtered by link. // The list can be filtered by link.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func QdiscList(link Link) ([]Qdisc, error) { func QdiscList(link Link) ([]Qdisc, error) {
return pkgHandle.QdiscList(link) return pkgHandle.QdiscList(link)
} }
@ -345,6 +349,9 @@ func QdiscList(link Link) ([]Qdisc, error) {
// QdiscList gets a list of qdiscs in the system. // QdiscList gets a list of qdiscs in the system.
// Equivalent to: `tc qdisc show`. // Equivalent to: `tc qdisc show`.
// The list can be filtered by link. // The list can be filtered by link.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { func (h *Handle) QdiscList(link Link) ([]Qdisc, error) {
req := h.newNetlinkRequest(unix.RTM_GETQDISC, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETQDISC, unix.NLM_F_DUMP)
index := int32(0) index := int32(0)
@ -359,9 +366,9 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) {
} }
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []Qdisc var res []Qdisc
@ -497,7 +504,7 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) {
res = append(res, qdisc) res = append(res, qdisc)
} }
return res, nil return res, executeErr
} }
func parsePfifoFastData(qdisc Qdisc, value []byte) error { func parsePfifoFastData(qdisc Qdisc, value []byte) error {

View File

@ -3,6 +3,7 @@ package netlink
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
@ -85,19 +86,25 @@ func execRdmaSetLink(req *nl.NetlinkRequest) error {
// RdmaLinkList gets a list of RDMA link devices. // RdmaLinkList gets a list of RDMA link devices.
// Equivalent to: `rdma dev show` // Equivalent to: `rdma dev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func RdmaLinkList() ([]*RdmaLink, error) { func RdmaLinkList() ([]*RdmaLink, error) {
return pkgHandle.RdmaLinkList() return pkgHandle.RdmaLinkList()
} }
// RdmaLinkList gets a list of RDMA link devices. // RdmaLinkList gets a list of RDMA link devices.
// Equivalent to: `rdma dev show` // Equivalent to: `rdma dev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) { func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) {
proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_GET) proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_GET)
req := h.newNetlinkRequest(proto, unix.NLM_F_ACK|unix.NLM_F_DUMP) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK|unix.NLM_F_DUMP)
msgs, err := req.Execute(unix.NETLINK_RDMA, 0) msgs, executeErr := req.Execute(unix.NETLINK_RDMA, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []*RdmaLink var res []*RdmaLink
@ -109,17 +116,23 @@ func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) {
res = append(res, link) res = append(res, link)
} }
return res, nil return res, executeErr
} }
// RdmaLinkByName finds a link by name and returns a pointer to the object if // RdmaLinkByName finds a link by name and returns a pointer to the object if
// found and nil error, otherwise returns error code. // found and nil error, otherwise returns error code.
//
// If the returned error is [ErrDumpInterrupted], the result may be missing or
// outdated and the caller should retry.
func RdmaLinkByName(name string) (*RdmaLink, error) { func RdmaLinkByName(name string) (*RdmaLink, error) {
return pkgHandle.RdmaLinkByName(name) return pkgHandle.RdmaLinkByName(name)
} }
// RdmaLinkByName finds a link by name and returns a pointer to the object if // RdmaLinkByName finds a link by name and returns a pointer to the object if
// found and nil error, otherwise returns error code. // found and nil error, otherwise returns error code.
//
// If the returned error is [ErrDumpInterrupted], the result may be missing or
// outdated and the caller should retry.
func (h *Handle) RdmaLinkByName(name string) (*RdmaLink, error) { func (h *Handle) RdmaLinkByName(name string) (*RdmaLink, error) {
links, err := h.RdmaLinkList() links, err := h.RdmaLinkList()
if err != nil { if err != nil {
@ -288,6 +301,8 @@ func RdmaLinkDel(name string) error {
} }
// RdmaLinkDel deletes an rdma link. // RdmaLinkDel deletes an rdma link.
//
// If the returned error is [ErrDumpInterrupted], the caller should retry.
func (h *Handle) RdmaLinkDel(name string) error { func (h *Handle) RdmaLinkDel(name string) error {
link, err := h.RdmaLinkByName(name) link, err := h.RdmaLinkByName(name)
if err != nil { if err != nil {
@ -307,6 +322,7 @@ func (h *Handle) RdmaLinkDel(name string) error {
// RdmaLinkAdd adds an rdma link for the specified type to the network device. // RdmaLinkAdd adds an rdma link for the specified type to the network device.
// Similar to: rdma link add NAME type TYPE netdev NETDEV // Similar to: rdma link add NAME type TYPE netdev NETDEV
//
// NAME - specifies the new name of the rdma link to add // NAME - specifies the new name of the rdma link to add
// TYPE - specifies which rdma type to use. Link types: // TYPE - specifies which rdma type to use. Link types:
// rxe - Soft RoCE driver // rxe - Soft RoCE driver

View File

@ -3,6 +3,7 @@ package netlink
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -1163,6 +1164,9 @@ func (h *Handle) prepareRouteReq(route *Route, req *nl.NetlinkRequest, msg *nl.R
// RouteList gets a list of routes in the system. // RouteList gets a list of routes in the system.
// Equivalent to: `ip route show`. // Equivalent to: `ip route show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func RouteList(link Link, family int) ([]Route, error) { func RouteList(link Link, family int) ([]Route, error) {
return pkgHandle.RouteList(link, family) return pkgHandle.RouteList(link, family)
} }
@ -1170,6 +1174,9 @@ func RouteList(link Link, family int) ([]Route, error) {
// RouteList gets a list of routes in the system. // RouteList gets a list of routes in the system.
// Equivalent to: `ip route show`. // Equivalent to: `ip route show`.
// The list can be filtered by link and ip family. // The list can be filtered by link and ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RouteList(link Link, family int) ([]Route, error) { func (h *Handle) RouteList(link Link, family int) ([]Route, error) {
routeFilter := &Route{} routeFilter := &Route{}
if link != nil { if link != nil {
@ -1188,6 +1195,9 @@ func RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, e
// RouteListFiltered gets a list of routes in the system filtered with specified rules. // RouteListFiltered gets a list of routes in the system filtered with specified rules.
// All rules must be defined in RouteFilter struct // All rules must be defined in RouteFilter struct
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) {
var res []Route var res []Route
err := h.RouteListFilteredIter(family, filter, filterMask, func(route Route) (cont bool) { err := h.RouteListFilteredIter(family, filter, filterMask, func(route Route) (cont bool) {
@ -1202,17 +1212,22 @@ func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64)
// RouteListFilteredIter passes each route that matches the filter to the given iterator func. Iteration continues // RouteListFilteredIter passes each route that matches the filter to the given iterator func. Iteration continues
// until all routes are loaded or the func returns false. // until all routes are loaded or the func returns false.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { func RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error {
return pkgHandle.RouteListFilteredIter(family, filter, filterMask, f) return pkgHandle.RouteListFilteredIter(family, filter, filterMask, f)
} }
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error {
req := h.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_DUMP) req := h.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_DUMP)
rtmsg := &nl.RtMsg{} rtmsg := &nl.RtMsg{}
rtmsg.Family = uint8(family) rtmsg.Family = uint8(family)
var parseErr error var parseErr error
err := h.routeHandleIter(filter, req, rtmsg, func(m []byte) bool { executeErr := h.routeHandleIter(filter, req, rtmsg, func(m []byte) bool {
msg := nl.DeserializeRtMsg(m) msg := nl.DeserializeRtMsg(m)
if family != FAMILY_ALL && msg.Family != uint8(family) { if family != FAMILY_ALL && msg.Family != uint8(family) {
// Ignore routes not matching requested family // Ignore routes not matching requested family
@ -1270,13 +1285,13 @@ func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uin
} }
return f(route) return f(route)
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return err return executeErr
} }
if parseErr != nil { if parseErr != nil {
return parseErr return parseErr
} }
return nil return executeErr
} }
// deserializeRoute decodes a binary netlink message into a Route struct // deserializeRoute decodes a binary netlink message into a Route struct
@ -1684,6 +1699,10 @@ type RouteSubscribeOptions struct {
// RouteSubscribeWithOptions work like RouteSubscribe but enable to // RouteSubscribeWithOptions work like RouteSubscribe but enable to
// provide additional options to modify the behavior. Currently, the // provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback. // namespace can be provided as well as an error callback.
//
// When options.ListExisting is true, options.ErrorCallback may be
// called with [ErrDumpInterrupted] to indicate that results from
// the initial dump of links may be inconsistent or incomplete.
func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error { func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error {
if options.Namespace == nil { if options.Namespace == nil {
none := netns.None() none := netns.None()
@ -1743,6 +1762,9 @@ func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <
continue continue
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil {
cberr(ErrDumpInterrupted)
}
if m.Header.Type == unix.NLMSG_DONE { if m.Header.Type == unix.NLMSG_DONE {
continue continue
} }

View File

@ -2,6 +2,7 @@ package netlink
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"net" "net"
@ -183,12 +184,18 @@ func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error {
// RuleList lists rules in the system. // RuleList lists rules in the system.
// Equivalent to: ip rule list // Equivalent to: ip rule list
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func RuleList(family int) ([]Rule, error) { func RuleList(family int) ([]Rule, error) {
return pkgHandle.RuleList(family) return pkgHandle.RuleList(family)
} }
// RuleList lists rules in the system. // RuleList lists rules in the system.
// Equivalent to: ip rule list // Equivalent to: ip rule list
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RuleList(family int) ([]Rule, error) { func (h *Handle) RuleList(family int) ([]Rule, error) {
return h.RuleListFiltered(family, nil, 0) return h.RuleListFiltered(family, nil, 0)
} }
@ -196,20 +203,26 @@ func (h *Handle) RuleList(family int) ([]Rule, error) {
// RuleListFiltered gets a list of rules in the system filtered by the // RuleListFiltered gets a list of rules in the system filtered by the
// specified rule template `filter`. // specified rule template `filter`.
// Equivalent to: ip rule list // Equivalent to: ip rule list
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { func RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) {
return pkgHandle.RuleListFiltered(family, filter, filterMask) return pkgHandle.RuleListFiltered(family, filter, filterMask)
} }
// RuleListFiltered lists rules in the system. // RuleListFiltered lists rules in the system.
// Equivalent to: ip rule list // Equivalent to: ip rule list
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) {
req := h.newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP|unix.NLM_F_REQUEST) req := h.newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP|unix.NLM_F_REQUEST)
msg := nl.NewIfInfomsg(family) msg := nl.NewIfInfomsg(family)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWRULE) msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWRULE)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res = make([]Rule, 0) var res = make([]Rule, 0)
@ -306,7 +319,7 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) (
res = append(res, *rule) res = append(res, *rule)
} }
return res, nil return res, executeErr
} }
func (pr *RulePortRange) toRtAttrData() []byte { func (pr *RulePortRange) toRtAttrData() []byte {

View File

@ -157,6 +157,9 @@ func (u *UnixSocket) deserialize(b []byte) error {
} }
// SocketGet returns the Socket identified by its local and remote addresses. // SocketGet returns the Socket identified by its local and remote addresses.
//
// If the returned error is [ErrDumpInterrupted], the search for a result may
// be incomplete and the caller should retry.
func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) { func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) {
var protocol uint8 var protocol uint8
var localIP, remoteIP net.IP var localIP, remoteIP net.IP
@ -232,6 +235,9 @@ func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) {
} }
// SocketGet returns the Socket identified by its local and remote addresses. // SocketGet returns the Socket identified by its local and remote addresses.
//
// If the returned error is [ErrDumpInterrupted], the search for a result may
// be incomplete and the caller should retry.
func SocketGet(local, remote net.Addr) (*Socket, error) { func SocketGet(local, remote net.Addr) (*Socket, error) {
return pkgHandle.SocketGet(local, remote) return pkgHandle.SocketGet(local, remote)
} }
@ -283,6 +289,9 @@ func SocketDestroy(local, remote net.Addr) error {
} }
// SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) {
// Construct the request // Construct the request
req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
@ -295,9 +304,9 @@ func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error)
// Do the query and parse the result // Do the query and parse the result
var result []*InetDiagTCPInfoResp var result []*InetDiagTCPInfoResp
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &Socket{} sockInfo := &Socket{}
var err error
if err = sockInfo.deserialize(msg); err != nil { if err = sockInfo.deserialize(msg); err != nil {
return false return false
} }
@ -315,18 +324,24 @@ func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error)
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { func SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) {
return pkgHandle.SocketDiagTCPInfo(family) return pkgHandle.SocketDiagTCPInfo(family)
} }
// SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) { func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) {
// Construct the request // Construct the request
req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
@ -339,27 +354,32 @@ func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) {
// Do the query and parse the result // Do the query and parse the result
var result []*Socket var result []*Socket
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &Socket{} sockInfo := &Socket{}
if err = sockInfo.deserialize(msg); err != nil { if err := sockInfo.deserialize(msg); err != nil {
return false return false
} }
result = append(result, sockInfo) result = append(result, sockInfo)
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func SocketDiagTCP(family uint8) ([]*Socket, error) { func SocketDiagTCP(family uint8) ([]*Socket, error) {
return pkgHandle.SocketDiagTCP(family) return pkgHandle.SocketDiagTCP(family)
} }
// SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) {
// Construct the request // Construct the request
var extensions uint8 var extensions uint8
@ -377,14 +397,14 @@ func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error)
// Do the query and parse the result // Do the query and parse the result
var result []*InetDiagUDPInfoResp var result []*InetDiagUDPInfoResp
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &Socket{} sockInfo := &Socket{}
if err = sockInfo.deserialize(msg); err != nil { if err := sockInfo.deserialize(msg); err != nil {
return false return false
} }
var attrs []syscall.NetlinkRouteAttr var attrs []syscall.NetlinkRouteAttr
var err error
if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil {
return false return false
} }
@ -397,18 +417,24 @@ func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error)
result = append(result, res) result = append(result, res)
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { func SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) {
return pkgHandle.SocketDiagUDPInfo(family) return pkgHandle.SocketDiagUDPInfo(family)
} }
// SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) { func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) {
// Construct the request // Construct the request
req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
@ -421,27 +447,32 @@ func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) {
// Do the query and parse the result // Do the query and parse the result
var result []*Socket var result []*Socket
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &Socket{} sockInfo := &Socket{}
if err = sockInfo.deserialize(msg); err != nil { if err := sockInfo.deserialize(msg); err != nil {
return false return false
} }
result = append(result, sockInfo) result = append(result, sockInfo)
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func SocketDiagUDP(family uint8) ([]*Socket, error) { func SocketDiagUDP(family uint8) ([]*Socket, error) {
return pkgHandle.SocketDiagUDP(family) return pkgHandle.SocketDiagUDP(family)
} }
// UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) {
// Construct the request // Construct the request
var extensions uint8 var extensions uint8
@ -456,10 +487,9 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) {
}) })
var result []*UnixDiagInfoResp var result []*UnixDiagInfoResp
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &UnixSocket{} sockInfo := &UnixSocket{}
if err = sockInfo.deserialize(msg); err != nil { if err := sockInfo.deserialize(msg); err != nil {
return false return false
} }
@ -469,6 +499,7 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) {
} }
var attrs []syscall.NetlinkRouteAttr var attrs []syscall.NetlinkRouteAttr
var err error
if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil {
return false return false
} }
@ -480,18 +511,24 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) {
result = append(result, res) result = append(result, res)
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { func UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) {
return pkgHandle.UnixSocketDiagInfo() return pkgHandle.UnixSocketDiagInfo()
} }
// UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) { func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) {
// Construct the request // Construct the request
req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
@ -501,10 +538,9 @@ func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) {
}) })
var result []*UnixSocket var result []*UnixSocket
var err error executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool {
sockInfo := &UnixSocket{} sockInfo := &UnixSocket{}
if err = sockInfo.deserialize(msg); err != nil { if err := sockInfo.deserialize(msg); err != nil {
return false return false
} }
@ -514,13 +550,16 @@ func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) {
} }
return true return true
}) })
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
return result, nil return result, executeErr
} }
// UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func UnixSocketDiag() ([]*UnixSocket, error) { func UnixSocketDiag() ([]*UnixSocket, error) {
return pkgHandle.UnixSocketDiag() return pkgHandle.UnixSocketDiag()
} }

View File

@ -52,8 +52,10 @@ func (s *XDPSocket) deserialize(b []byte) error {
return nil return nil
} }
// XDPSocketGet returns the XDP socket identified by its inode number and/or // SocketXDPGetInfo returns the XDP socket identified by its inode number and/or
// socket cookie. Specify the cookie as SOCK_ANY_COOKIE if // socket cookie. Specify the cookie as SOCK_ANY_COOKIE if
//
// If the returned error is [ErrDumpInterrupted], the caller should retry.
func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) { func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) {
// We have a problem here: dumping AF_XDP sockets currently does not support // We have a problem here: dumping AF_XDP sockets currently does not support
// filtering. We thus need to dump all XSKs and then only filter afterwards // filtering. We thus need to dump all XSKs and then only filter afterwards
@ -85,6 +87,9 @@ func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) {
} }
// SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets. // SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func SocketDiagXDP() ([]*XDPDiagInfoResp, error) { func SocketDiagXDP() ([]*XDPDiagInfoResp, error) {
var result []*XDPDiagInfoResp var result []*XDPDiagInfoResp
err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error { err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error {
@ -105,10 +110,10 @@ func SocketDiagXDP() ([]*XDPDiagInfoResp, error) {
result = append(result, res) result = append(result, res)
return nil return nil
}) })
if err != nil { if err != nil && !errors.Is(err, ErrDumpInterrupted) {
return nil, err return nil, err
} }
return result, nil return result, err
} }
// socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets. // socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets.
@ -128,6 +133,7 @@ func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error {
return err return err
} }
dumpIntr := false
loop: loop:
for { for {
msgs, from, err := s.Receive() msgs, from, err := s.Receive()
@ -142,6 +148,9 @@ loop:
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 {
dumpIntr = true
}
switch m.Header.Type { switch m.Header.Type {
case unix.NLMSG_DONE: case unix.NLMSG_DONE:
break loop break loop
@ -154,6 +163,9 @@ loop:
} }
} }
} }
if dumpIntr {
return ErrDumpInterrupted
}
return nil return nil
} }

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"syscall" "syscall"
@ -118,6 +119,9 @@ func VDPADelDev(name string) error {
// VDPAGetDevList returns list of VDPA devices // VDPAGetDevList returns list of VDPA devices
// Equivalent to: `vdpa dev show` // Equivalent to: `vdpa dev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func VDPAGetDevList() ([]*VDPADev, error) { func VDPAGetDevList() ([]*VDPADev, error) {
return pkgHandle.VDPAGetDevList() return pkgHandle.VDPAGetDevList()
} }
@ -130,6 +134,9 @@ func VDPAGetDevByName(name string) (*VDPADev, error) {
// VDPAGetDevConfigList returns list of VDPA devices configurations // VDPAGetDevConfigList returns list of VDPA devices configurations
// Equivalent to: `vdpa dev config show` // Equivalent to: `vdpa dev config show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func VDPAGetDevConfigList() ([]*VDPADevConfig, error) { func VDPAGetDevConfigList() ([]*VDPADevConfig, error) {
return pkgHandle.VDPAGetDevConfigList() return pkgHandle.VDPAGetDevConfigList()
} }
@ -148,6 +155,9 @@ func VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStats, error) {
// VDPAGetMGMTDevList returns list of mgmt devices // VDPAGetMGMTDevList returns list of mgmt devices
// Equivalent to: `vdpa mgmtdev show` // Equivalent to: `vdpa mgmtdev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { func VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) {
return pkgHandle.VDPAGetMGMTDevList() return pkgHandle.VDPAGetMGMTDevList()
} }
@ -261,9 +271,9 @@ func (h *Handle) vdpaRequest(command uint8, extraFlags int, attrs []*nl.RtAttr)
req.AddData(a) req.AddData(a)
} }
resp, err := req.Execute(unix.NETLINK_GENERIC, 0) resp, executeErr := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
messages := make([]vdpaNetlinkMessage, 0, len(resp)) messages := make([]vdpaNetlinkMessage, 0, len(resp))
for _, m := range resp { for _, m := range resp {
@ -273,10 +283,13 @@ func (h *Handle) vdpaRequest(command uint8, extraFlags int, attrs []*nl.RtAttr)
} }
messages = append(messages, attrs) messages = append(messages, attrs)
} }
return messages, nil return messages, executeErr
} }
// dump all devices if dev is nil // dump all devices if dev is nil
//
// If dev is nil and the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) { func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) {
var extraFlags int var extraFlags int
var attrs []*nl.RtAttr var attrs []*nl.RtAttr
@ -285,9 +298,9 @@ func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) {
} else { } else {
extraFlags = extraFlags | unix.NLM_F_DUMP extraFlags = extraFlags | unix.NLM_F_DUMP
} }
messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_GET, extraFlags, attrs) messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_DEV_GET, extraFlags, attrs)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
devs := make([]*VDPADev, 0, len(messages)) devs := make([]*VDPADev, 0, len(messages))
for _, m := range messages { for _, m := range messages {
@ -295,10 +308,13 @@ func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) {
d.parseAttributes(m) d.parseAttributes(m)
devs = append(devs, d) devs = append(devs, d)
} }
return devs, nil return devs, executeErr
} }
// dump all devices if dev is nil // dump all devices if dev is nil
//
// If dev is nil, and the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) { func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) {
var extraFlags int var extraFlags int
var attrs []*nl.RtAttr var attrs []*nl.RtAttr
@ -307,9 +323,9 @@ func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) {
} else { } else {
extraFlags = extraFlags | unix.NLM_F_DUMP extraFlags = extraFlags | unix.NLM_F_DUMP
} }
messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_CONFIG_GET, extraFlags, attrs) messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_DEV_CONFIG_GET, extraFlags, attrs)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
cfgs := make([]*VDPADevConfig, 0, len(messages)) cfgs := make([]*VDPADevConfig, 0, len(messages))
for _, m := range messages { for _, m := range messages {
@ -317,10 +333,13 @@ func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) {
cfg.parseAttributes(m) cfg.parseAttributes(m)
cfgs = append(cfgs, cfg) cfgs = append(cfgs, cfg)
} }
return cfgs, nil return cfgs, executeErr
} }
// dump all devices if dev is nil // dump all devices if dev is nil
//
// If dev is nil and the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) { func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) {
var extraFlags int var extraFlags int
var attrs []*nl.RtAttr var attrs []*nl.RtAttr
@ -336,9 +355,9 @@ func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) {
} else { } else {
extraFlags = extraFlags | unix.NLM_F_DUMP extraFlags = extraFlags | unix.NLM_F_DUMP
} }
messages, err := h.vdpaRequest(nl.VDPA_CMD_MGMTDEV_GET, extraFlags, attrs) messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_MGMTDEV_GET, extraFlags, attrs)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
cfgs := make([]*VDPAMGMTDev, 0, len(messages)) cfgs := make([]*VDPAMGMTDev, 0, len(messages))
for _, m := range messages { for _, m := range messages {
@ -346,7 +365,7 @@ func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) {
cfg.parseAttributes(m) cfg.parseAttributes(m)
cfgs = append(cfgs, cfg) cfgs = append(cfgs, cfg)
} }
return cfgs, nil return cfgs, executeErr
} }
// VDPANewDev adds new VDPA device // VDPANewDev adds new VDPA device
@ -385,6 +404,9 @@ func (h *Handle) VDPADelDev(name string) error {
// VDPAGetDevList returns list of VDPA devices // VDPAGetDevList returns list of VDPA devices
// Equivalent to: `vdpa dev show` // Equivalent to: `vdpa dev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) VDPAGetDevList() ([]*VDPADev, error) { func (h *Handle) VDPAGetDevList() ([]*VDPADev, error) {
return h.vdpaDevGet(nil) return h.vdpaDevGet(nil)
} }
@ -404,6 +426,9 @@ func (h *Handle) VDPAGetDevByName(name string) (*VDPADev, error) {
// VDPAGetDevConfigList returns list of VDPA devices configurations // VDPAGetDevConfigList returns list of VDPA devices configurations
// Equivalent to: `vdpa dev config show` // Equivalent to: `vdpa dev config show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) VDPAGetDevConfigList() ([]*VDPADevConfig, error) { func (h *Handle) VDPAGetDevConfigList() ([]*VDPADevConfig, error) {
return h.vdpaDevConfigGet(nil) return h.vdpaDevConfigGet(nil)
} }
@ -441,6 +466,9 @@ func (h *Handle) VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStat
// VDPAGetMGMTDevList returns list of mgmt devices // VDPAGetMGMTDevList returns list of mgmt devices
// Equivalent to: `vdpa mgmtdev show` // Equivalent to: `vdpa mgmtdev show`
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { func (h *Handle) VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) {
return h.vdpaMGMTDevGet(nil, nil) return h.vdpaMGMTDevGet(nil, nil)
} }

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
@ -215,6 +216,9 @@ func (h *Handle) XfrmPolicyDel(policy *XfrmPolicy) error {
// XfrmPolicyList gets a list of xfrm policies in the system. // XfrmPolicyList gets a list of xfrm policies in the system.
// Equivalent to: `ip xfrm policy show`. // Equivalent to: `ip xfrm policy show`.
// The list can be filtered by ip family. // The list can be filtered by ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func XfrmPolicyList(family int) ([]XfrmPolicy, error) { func XfrmPolicyList(family int) ([]XfrmPolicy, error) {
return pkgHandle.XfrmPolicyList(family) return pkgHandle.XfrmPolicyList(family)
} }
@ -222,15 +226,18 @@ func XfrmPolicyList(family int) ([]XfrmPolicy, error) {
// XfrmPolicyList gets a list of xfrm policies in the system. // XfrmPolicyList gets a list of xfrm policies in the system.
// Equivalent to: `ip xfrm policy show`. // Equivalent to: `ip xfrm policy show`.
// The list can be filtered by ip family. // The list can be filtered by ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) { func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) {
req := h.newNetlinkRequest(nl.XFRM_MSG_GETPOLICY, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.XFRM_MSG_GETPOLICY, unix.NLM_F_DUMP)
msg := nl.NewIfInfomsg(family) msg := nl.NewIfInfomsg(family)
req.AddData(msg) req.AddData(msg)
msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWPOLICY) msgs, executeErr := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWPOLICY)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []XfrmPolicy var res []XfrmPolicy
@ -243,7 +250,7 @@ func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) {
return nil, err return nil, err
} }
} }
return res, nil return res, executeErr
} }
// XfrmPolicyGet gets a the policy described by the index or selector, if found. // XfrmPolicyGet gets a the policy described by the index or selector, if found.

View File

@ -1,6 +1,7 @@
package netlink package netlink
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"time" "time"
@ -382,6 +383,9 @@ func (h *Handle) XfrmStateDel(state *XfrmState) error {
// XfrmStateList gets a list of xfrm states in the system. // XfrmStateList gets a list of xfrm states in the system.
// Equivalent to: `ip [-4|-6] xfrm state show`. // Equivalent to: `ip [-4|-6] xfrm state show`.
// The list can be filtered by ip family. // The list can be filtered by ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func XfrmStateList(family int) ([]XfrmState, error) { func XfrmStateList(family int) ([]XfrmState, error) {
return pkgHandle.XfrmStateList(family) return pkgHandle.XfrmStateList(family)
} }
@ -389,12 +393,15 @@ func XfrmStateList(family int) ([]XfrmState, error) {
// XfrmStateList gets a list of xfrm states in the system. // XfrmStateList gets a list of xfrm states in the system.
// Equivalent to: `ip xfrm state show`. // Equivalent to: `ip xfrm state show`.
// The list can be filtered by ip family. // The list can be filtered by ip family.
//
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
// or incomplete.
func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) { func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) {
req := h.newNetlinkRequest(nl.XFRM_MSG_GETSA, unix.NLM_F_DUMP) req := h.newNetlinkRequest(nl.XFRM_MSG_GETSA, unix.NLM_F_DUMP)
msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWSA) msgs, executeErr := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWSA)
if err != nil { if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
return nil, err return nil, executeErr
} }
var res []XfrmState var res []XfrmState
@ -407,7 +414,7 @@ func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) {
return nil, err return nil, err
} }
} }
return res, nil return res, executeErr
} }
// XfrmStateGet gets the xfrm state described by the ID, if found. // XfrmStateGet gets the xfrm state described by the ID, if found.

2
vendor/modules.txt vendored
View File

@ -547,7 +547,7 @@ github.com/stretchr/testify/require
# github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 # github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75
## explicit; go 1.15 ## explicit; go 1.15
github.com/tmc/grpc-websocket-proxy/wsproxy github.com/tmc/grpc-websocket-proxy/wsproxy
# github.com/vishvananda/netlink v1.3.1-0.20240905180732-b1ce50cfa9be # github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa
## explicit; go 1.12 ## explicit; go 1.12
github.com/vishvananda/netlink github.com/vishvananda/netlink
github.com/vishvananda/netlink/nl github.com/vishvananda/netlink/nl