},
{
"ImportPath": "github.com/vishvananda/netlink",
- "Rev": "fe3b5664d23a11b52ba59bece4ff29c52772a56b"
+ "Rev": "b71e0bb214aebd980216cb2516e8bd7bca9e9672"
},
{
"ImportPath": "github.com/vishvananda/netlink/nl",
- "Rev": "fe3b5664d23a11b52ba59bece4ff29c52772a56b"
+ "Rev": "b71e0bb214aebd980216cb2516e8bd7bca9e9672"
},
{
"ImportPath": "github.com/vishvananda/netns",
- sudo sed -i -e 's/^Defaults\tsecure_path.*$//' /etc/sudoers
# modprobe ip_gre or else the first gre device can't be deleted
- sudo modprobe ip_gre
+ # modprobe nf_conntrack for the conntrack testing
+ - sudo modprobe nf_conntrack
+ - sudo modprobe nf_conntrack_netlink
+ - sudo modprobe nf_conntrack_ipv4
+ - sudo modprobe nf_conntrack_ipv6
install:
- go get github.com/vishvananda/netns
// include a mask, so it stores the address as a net.IPNet.
type Addr struct {
*net.IPNet
- Label string
- Flags int
- Scope int
- Peer *net.IPNet
- Broadcast net.IP
+ Label string
+ Flags int
+ Scope int
+ Peer *net.IPNet
+ Broadcast net.IP
+ PreferedLft int
+ ValidLft int
}
// String returns $ip/$netmask $label
return h.addrHandle(link, addr, req)
}
+// AddrReplace will replace (or, if not present, add) an IP address on a link device.
+// Equivalent to: `ip addr replace $addr dev $link`
+func AddrReplace(link Link, addr *Addr) error {
+ return pkgHandle.AddrReplace(link, addr)
+}
+
+// AddrReplace will replace (or, if not present, add) an IP address on a link device.
+// Equivalent to: `ip addr replace $addr dev $link`
+func (h *Handle) AddrReplace(link Link, addr *Addr) error {
+ req := h.newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_REPLACE|syscall.NLM_F_ACK)
+ return h.addrHandle(link, addr, req)
+}
+
// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func AddrDel(link Link, addr *Addr) error {
addr.Label = string(attr.Value[:len(attr.Value)-1])
case IFA_FLAGS:
addr.Flags = int(native.Uint32(attr.Value[0:4]))
+ case nl.IFA_CACHEINFO:
+ ci := nl.DeserializeIfaCacheInfo(attr.Value)
+ addr.PreferedLft = int(ci.IfaPrefered)
+ addr.ValidLft = int(ci.IfaValid)
}
}
type AddrUpdate struct {
LinkAddress net.IPNet
LinkIndex int
+ Flags int
+ Scope int
+ PreferedLft int
+ ValidLft int
NewAddr bool // true=added false=deleted
}
continue
}
- ch <- AddrUpdate{LinkAddress: *addr.IPNet, LinkIndex: ifindex, NewAddr: msgType == syscall.RTM_NEWADDR}
+ ch <- AddrUpdate{LinkAddress: *addr.IPNet,
+ LinkIndex: ifindex,
+ NewAddr: msgType == syscall.RTM_NEWADDR,
+ Flags: addr.Flags,
+ Scope: addr.Scope,
+ PreferedLft: addr.PreferedLft,
+ ValidLft: addr.ValidLft}
}
}
}()
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "net"
+ "os"
+ "syscall"
+ "testing"
+)
+
+func TestAddrAdd(t *testing.T) {
+ DoTestAddr(t, AddrAdd)
+}
+
+func TestAddrReplace(t *testing.T) {
+ DoTestAddr(t, AddrReplace)
+}
+
+func DoTestAddr(t *testing.T, FunctionUndertest func(Link, *Addr) error) {
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Fails in travis with: addr_test.go:68: Address flags not set properly, got=0, expected=128")
+ }
+ // TODO: IFA_F_PERMANENT does not seem to be set by default on older kernels?
+ var address = &net.IPNet{IP: net.IPv4(127, 0, 0, 2), Mask: net.CIDRMask(24, 32)}
+ var peer = &net.IPNet{IP: net.IPv4(127, 0, 0, 3), Mask: net.CIDRMask(24, 32)}
+ var addrTests = []struct {
+ addr *Addr
+ expected *Addr
+ }{
+ {
+ &Addr{IPNet: address},
+ &Addr{IPNet: address, Label: "lo", Scope: syscall.RT_SCOPE_UNIVERSE, Flags: syscall.IFA_F_PERMANENT},
+ },
+ {
+ &Addr{IPNet: address, Label: "local"},
+ &Addr{IPNet: address, Label: "local", Scope: syscall.RT_SCOPE_UNIVERSE, Flags: syscall.IFA_F_PERMANENT},
+ },
+ {
+ &Addr{IPNet: address, Flags: syscall.IFA_F_OPTIMISTIC},
+ &Addr{IPNet: address, Label: "lo", Flags: syscall.IFA_F_OPTIMISTIC | syscall.IFA_F_PERMANENT, Scope: syscall.RT_SCOPE_UNIVERSE},
+ },
+ {
+ &Addr{IPNet: address, Flags: syscall.IFA_F_OPTIMISTIC | syscall.IFA_F_DADFAILED},
+ &Addr{IPNet: address, Label: "lo", Flags: syscall.IFA_F_OPTIMISTIC | syscall.IFA_F_DADFAILED | syscall.IFA_F_PERMANENT, Scope: syscall.RT_SCOPE_UNIVERSE},
+ },
+ {
+ &Addr{IPNet: address, Scope: syscall.RT_SCOPE_NOWHERE},
+ &Addr{IPNet: address, Label: "lo", Flags: syscall.IFA_F_PERMANENT, Scope: syscall.RT_SCOPE_NOWHERE},
+ },
+ {
+ &Addr{IPNet: address, Peer: peer},
+ &Addr{IPNet: address, Peer: peer, Label: "lo", Scope: syscall.RT_SCOPE_UNIVERSE, Flags: syscall.IFA_F_PERMANENT},
+ },
+ }
+
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, tt := range addrTests {
+ if err = FunctionUndertest(link, tt.addr); err != nil {
+ t.Fatal(err)
+ }
+
+ addrs, err := AddrList(link, FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 1 {
+ t.Fatal("Address not added properly")
+ }
+
+ if !addrs[0].Equal(*tt.expected) {
+ t.Fatalf("Address ip no set properly, got=%s, expected=%s", addrs[0], tt.expected)
+ }
+
+ if addrs[0].Label != tt.expected.Label {
+ t.Fatalf("Address label not set properly, got=%s, expected=%s", addrs[0].Label, tt.expected.Label)
+ }
+
+ if addrs[0].Flags != tt.expected.Flags {
+ t.Fatalf("Address flags not set properly, got=%d, expected=%d", addrs[0].Flags, tt.expected.Flags)
+ }
+
+ if addrs[0].Scope != tt.expected.Scope {
+ t.Fatalf("Address scope not set properly, got=%d, expected=%d", addrs[0].Scope, tt.expected.Scope)
+ }
+
+ if tt.expected.Peer != nil {
+ if !addrs[0].PeerEqual(*tt.expected) {
+ t.Fatalf("Peer Address ip no set properly, got=%s, expected=%s", addrs[0].Peer, tt.expected.Peer)
+ }
+ }
+
+ // Pass FAMILY_V4, we should get the same results as FAMILY_ALL
+ addrs, err = AddrList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(addrs) != 1 {
+ t.Fatal("Address not added properly")
+ }
+
+ // Pass a wrong family number, we should get nil list
+ addrs, err = AddrList(link, 0x8)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 0 {
+ t.Fatal("Address not expected")
+ }
+
+ if err = AddrDel(link, tt.addr); err != nil {
+ t.Fatal(err)
+ }
+
+ addrs, err = AddrList(link, FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 0 {
+ t.Fatal("Address not removed properly")
+ }
+ }
+
+}
+
+func TestAddrAddReplace(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ var address = &net.IPNet{IP: net.IPv4(127, 0, 0, 2), Mask: net.CIDRMask(24, 32)}
+ var addr = &Addr{IPNet: address}
+
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = AddrAdd(link, addr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ addrs, err := AddrList(link, FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 1 {
+ t.Fatal("Address not added properly")
+ }
+
+ err = AddrAdd(link, addr)
+ if err == nil {
+ t.Fatal("Re-adding address should fail (but succeeded unexpectedly).")
+ }
+
+ err = AddrReplace(link, addr)
+ if err != nil {
+ t.Fatal("Replacing address failed.")
+ }
+
+ addrs, err = AddrList(link, FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 1 {
+ t.Fatal("Address not added properly")
+ }
+
+ if err = AddrDel(link, addr); err != nil {
+ t.Fatal(err)
+ }
+
+ addrs, err = AddrList(link, FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(addrs) != 0 {
+ t.Fatal("Address not removed properly")
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "testing"
+)
+
+func TestClassAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_ROOT,
+ }
+ qdisc := NewHtb(attrs)
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ classattrs := ClassAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Handle: MakeHandle(0xffff, 2),
+ }
+
+ htbclassattrs := HtbClassAttrs{
+ Rate: 1234000,
+ Cbuffer: 1690,
+ }
+ class := NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassAdd(class); err != nil {
+ t.Fatal(err)
+ }
+ classes, err := ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatal("Failed to add class")
+ }
+
+ htb, ok := classes[0].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+ if htb.Rate != class.Rate {
+ t.Fatal("Rate doesn't match")
+ }
+ if htb.Ceil != class.Ceil {
+ t.Fatal("Ceil doesn't match")
+ }
+ if htb.Buffer != class.Buffer {
+ t.Fatal("Buffer doesn't match")
+ }
+ if htb.Cbuffer != class.Cbuffer {
+ t.Fatal("Cbuffer doesn't match")
+ }
+
+ qattrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0x2, 0),
+ Parent: MakeHandle(0xffff, 2),
+ }
+ nattrs := NetemQdiscAttrs{
+ Latency: 20000,
+ Loss: 23.4,
+ Duplicate: 14.3,
+ LossCorr: 8.34,
+ Jitter: 1000,
+ DelayCorr: 12.3,
+ ReorderProb: 23.4,
+ CorruptProb: 10.0,
+ CorruptCorr: 10,
+ }
+ qdiscnetem := NewNetem(qattrs, nattrs)
+ if err := QdiscAdd(qdiscnetem); err != nil {
+ t.Fatal(err)
+ }
+
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 2 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok = qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ netem, ok := qdiscs[1].(*Netem)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ // Compare the record we got from the list with the one we created
+ if netem.Loss != qdiscnetem.Loss {
+ t.Fatal("Loss does not match")
+ }
+ if netem.Latency != qdiscnetem.Latency {
+ t.Fatal("Latency does not match")
+ }
+ if netem.CorruptProb != qdiscnetem.CorruptProb {
+ t.Fatal("CorruptProb does not match")
+ }
+ if netem.Jitter != qdiscnetem.Jitter {
+ t.Fatal("Jitter does not match")
+ }
+ if netem.LossCorr != qdiscnetem.LossCorr {
+ t.Fatal("Loss does not match")
+ }
+ if netem.DuplicateCorr != qdiscnetem.DuplicateCorr {
+ t.Fatal("DuplicateCorr does not match")
+ }
+
+ // Deletion
+ if err := ClassDel(class); err != nil {
+ t.Fatal(err)
+ }
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 0 {
+ t.Fatal("Failed to remove class")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestHtbClassAddHtbClassChangeDel(t *testing.T) {
+ /**
+ This test first set up a interface ans set up a Htb qdisc
+ A HTB class is attach to it and a Netem qdisc is attached to that class
+ Next, we test changing the HTB class in place and confirming the Netem is
+ still attached. We also check that invoting ClassChange with a non-existing
+ class will fail.
+ Finally, we test ClassReplace. We confirm it correctly behave like
+ ClassChange when the parent/handle pair exists and that it will create a
+ new class if the handle is modified.
+ */
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_ROOT,
+ }
+ qdisc := NewHtb(attrs)
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ classattrs := ClassAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Handle: MakeHandle(0xffff, 2),
+ }
+
+ htbclassattrs := HtbClassAttrs{
+ Rate: 1234000,
+ Cbuffer: 1690,
+ }
+ class := NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassAdd(class); err != nil {
+ t.Fatal(err)
+ }
+ classes, err := ClassList(link, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatal("Failed to add class")
+ }
+
+ htb, ok := classes[0].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+
+ qattrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0x2, 0),
+ Parent: MakeHandle(0xffff, 2),
+ }
+ nattrs := NetemQdiscAttrs{
+ Latency: 20000,
+ Loss: 23.4,
+ Duplicate: 14.3,
+ LossCorr: 8.34,
+ Jitter: 1000,
+ DelayCorr: 12.3,
+ ReorderProb: 23.4,
+ CorruptProb: 10.0,
+ CorruptCorr: 10,
+ }
+ qdiscnetem := NewNetem(qattrs, nattrs)
+ if err := QdiscAdd(qdiscnetem); err != nil {
+ t.Fatal(err)
+ }
+
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 2 {
+ t.Fatal("Failed to add qdisc")
+ }
+
+ _, ok = qdiscs[1].(*Netem)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ // Change
+ // For change to work, the handle and parent cannot be changed.
+
+ // First, test it fails if we change the Handle.
+ oldHandle := classattrs.Handle
+ classattrs.Handle = MakeHandle(0xffff, 3)
+ class = NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassChange(class); err == nil {
+ t.Fatal("ClassChange should not work when using a different handle.")
+ }
+ // It should work with the same handle
+ classattrs.Handle = oldHandle
+ htbclassattrs.Rate = 4321000
+ class = NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassChange(class); err != nil {
+ t.Fatal(err)
+ }
+
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatalf(
+ "1 class expected, %d found",
+ len(classes),
+ )
+ }
+
+ htb, ok = classes[0].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+ // Verify that the rate value has changed.
+ if htb.Rate != class.Rate {
+ t.Fatal("Rate did not get changed while changing the class.")
+ }
+
+ // Check that we still have the netem child qdisc
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(qdiscs) != 2 {
+ t.Fatalf("2 qdisc expected, %d found", len(qdiscs))
+ }
+ _, ok = qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ _, ok = qdiscs[1].(*Netem)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ // Replace
+ // First replace by keeping the same handle, class will be changed.
+ // Then, replace by providing a new handle, n new class will be created.
+
+ // Replace acting as Change
+ class = NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassReplace(class); err != nil {
+ t.Fatal("Failed to replace class that is existing.")
+ }
+
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatalf(
+ "1 class expected, %d found",
+ len(classes),
+ )
+ }
+
+ htb, ok = classes[0].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+ // Verify that the rate value has changed.
+ if htb.Rate != class.Rate {
+ t.Fatal("Rate did not get changed while changing the class.")
+ }
+
+ // It should work with the same handle
+ classattrs.Handle = MakeHandle(0xffff, 3)
+ class = NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassReplace(class); err != nil {
+ t.Fatal(err)
+ }
+
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 2 {
+ t.Fatalf(
+ "2 classes expected, %d found",
+ len(classes),
+ )
+ }
+
+ htb, ok = classes[1].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+ // Verify that the rate value has changed.
+ if htb.Rate != class.Rate {
+ t.Fatal("Rate did not get changed while changing the class.")
+ }
+
+ // Deletion
+ for _, class := range classes {
+ if err := ClassDel(class); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 0 {
+ t.Fatal("Failed to remove class")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
--- /dev/null
+package netlink
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "net"
+ "syscall"
+
+ "github.com/vishvananda/netlink/nl"
+)
+
+// ConntrackTableType Conntrack table for the netlink operation
+type ConntrackTableType uint8
+
+const (
+ // ConntrackTable Conntrack table
+ // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK 1
+ ConntrackTable = 1
+ // ConntrackExpectTable Conntrack expect table
+ // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK_EXP 2
+ ConntrackExpectTable = 2
+)
+
+const (
+ // backward compatibility with golang 1.6 which does not have io.SeekCurrent
+ seekCurrent = 1
+)
+
+// InetFamily Family type
+type InetFamily uint8
+
+// -L [table] [options] List conntrack or expectation table
+// -G [table] parameters Get conntrack or expectation
+
+// -I [table] parameters Create a conntrack or expectation
+// -U [table] parameters Update a conntrack
+// -E [table] [options] Show events
+
+// -C [table] Show counter
+// -S Show statistics
+
+// ConntrackTableList returns the flow list of a table of a specific family
+// conntrack -L [table] [options] List conntrack or expectation table
+func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
+ return pkgHandle.ConntrackTableList(table, family)
+}
+
+// ConntrackTableFlush flushes all the flows of a specified table
+// conntrack -F [table] Flush table
+// The flush operation applies to all the family types
+func ConntrackTableFlush(table ConntrackTableType) error {
+ return pkgHandle.ConntrackTableFlush(table)
+}
+
+// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
+// conntrack -D [table] parameters Delete conntrack or expectation
+func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
+ return pkgHandle.ConntrackDeleteFilter(table, family, filter)
+}
+
+// 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
+func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
+ res, err := h.dumpConntrackTable(table, family)
+ if err != nil {
+ return nil, err
+ }
+
+ // Deserialize all the flows
+ var result []*ConntrackFlow
+ for _, dataRaw := range res {
+ result = append(result, parseRawData(dataRaw))
+ }
+
+ return result, nil
+}
+
+// ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed
+// conntrack -F [table] Flush table
+// The flush operation applies to all the family types
+func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
+ req := h.newConntrackRequest(table, syscall.AF_INET, nl.IPCTNL_MSG_CT_DELETE, syscall.NLM_F_ACK)
+ _, err := req.Execute(syscall.NETLINK_NETFILTER, 0)
+ return err
+}
+
+// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
+// conntrack -D [table] parameters Delete conntrack or expectation
+func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
+ res, err := h.dumpConntrackTable(table, family)
+ if err != nil {
+ return 0, err
+ }
+
+ var matched uint
+ for _, dataRaw := range res {
+ flow := parseRawData(dataRaw)
+ if match := filter.MatchConntrackFlow(flow); match {
+ req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, syscall.NLM_F_ACK)
+ // skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already
+ req2.AddRawData(dataRaw[4:])
+ req2.Execute(syscall.NETLINK_NETFILTER, 0)
+ matched++
+ }
+ }
+
+ return matched, nil
+}
+
+func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest {
+ // Create the Netlink request object
+ req := h.newNetlinkRequest((int(table)<<8)|operation, flags)
+ // Add the netfilter header
+ msg := &nl.Nfgenmsg{
+ NfgenFamily: uint8(family),
+ Version: nl.NFNETLINK_V0,
+ ResId: 0,
+ }
+ req.AddData(msg)
+ return req
+}
+
+func (h *Handle) dumpConntrackTable(table ConntrackTableType, family InetFamily) ([][]byte, error) {
+ req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_GET, syscall.NLM_F_DUMP)
+ return req.Execute(syscall.NETLINK_NETFILTER, 0)
+}
+
+// The full conntrack flow structure is very complicated and can be found in the file:
+// http://git.netfilter.org/libnetfilter_conntrack/tree/include/internal/object.h
+// For the time being, the structure below allows to parse and extract the base information of a flow
+type ipTuple struct {
+ SrcIP net.IP
+ DstIP net.IP
+ Protocol uint8
+ SrcPort uint16
+ DstPort uint16
+}
+
+type ConntrackFlow struct {
+ FamilyType uint8
+ Forward ipTuple
+ Reverse ipTuple
+}
+
+func (s *ConntrackFlow) String() string {
+ // conntrack cmd output:
+ // udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001
+ return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d\tsrc=%s dst=%s sport=%d dport=%d",
+ nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol,
+ s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort,
+ s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort)
+}
+
+// This method parse the ip tuple structure
+// The message structure is the following:
+// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
+// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
+// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
+// <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
+// <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
+func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) {
+ for i := 0; i < 2; i++ {
+ _, t, _, v := parseNfAttrTLV(reader)
+ switch t {
+ case nl.CTA_IP_V4_SRC, nl.CTA_IP_V6_SRC:
+ tpl.SrcIP = v
+ case nl.CTA_IP_V4_DST, nl.CTA_IP_V6_DST:
+ tpl.DstIP = v
+ }
+ }
+ // Skip the next 4 bytes nl.NLA_F_NESTED|nl.CTA_TUPLE_PROTO
+ reader.Seek(4, seekCurrent)
+ _, t, _, v := parseNfAttrTLV(reader)
+ if t == nl.CTA_PROTO_NUM {
+ tpl.Protocol = uint8(v[0])
+ }
+ // Skip some padding 3 bytes
+ reader.Seek(3, seekCurrent)
+ for i := 0; i < 2; i++ {
+ _, t, _ := parseNfAttrTL(reader)
+ switch t {
+ case nl.CTA_PROTO_SRC_PORT:
+ parseBERaw16(reader, &tpl.SrcPort)
+ case nl.CTA_PROTO_DST_PORT:
+ parseBERaw16(reader, &tpl.DstPort)
+ }
+ // Skip some padding 2 byte
+ reader.Seek(2, seekCurrent)
+ }
+}
+
+func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) {
+ isNested, attrType, len = parseNfAttrTL(r)
+
+ value = make([]byte, len)
+ binary.Read(r, binary.BigEndian, &value)
+ return isNested, attrType, len, value
+}
+
+func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
+ binary.Read(r, nl.NativeEndian(), &len)
+ len -= nl.SizeofNfattr
+
+ binary.Read(r, nl.NativeEndian(), &attrType)
+ isNested = (attrType & nl.NLA_F_NESTED) == nl.NLA_F_NESTED
+ attrType = attrType & (nl.NLA_F_NESTED - 1)
+
+ return isNested, attrType, len
+}
+
+func parseBERaw16(r *bytes.Reader, v *uint16) {
+ binary.Read(r, binary.BigEndian, v)
+}
+
+func parseRawData(data []byte) *ConntrackFlow {
+ s := &ConntrackFlow{}
+ // First there is the Nfgenmsg header
+ // consume only the family field
+ reader := bytes.NewReader(data)
+ binary.Read(reader, nl.NativeEndian(), &s.FamilyType)
+
+ // skip rest of the Netfilter header
+ reader.Seek(3, seekCurrent)
+ // The message structure is the following:
+ // <len, NLA_F_NESTED|CTA_TUPLE_ORIG> 4 bytes
+ // <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
+ // flow information of the forward flow
+ // <len, NLA_F_NESTED|CTA_TUPLE_REPLY> 4 bytes
+ // <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
+ // flow information of the reverse flow
+ for reader.Len() > 0 {
+ nested, t, l := parseNfAttrTL(reader)
+ if nested && t == nl.CTA_TUPLE_ORIG {
+ if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
+ parseIpTuple(reader, &s.Forward)
+ }
+ } else if nested && t == nl.CTA_TUPLE_REPLY {
+ if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
+ parseIpTuple(reader, &s.Reverse)
+
+ // Got all the useful information stop parsing
+ break
+ } else {
+ // Header not recognized skip it
+ reader.Seek(int64(l), seekCurrent)
+ }
+ }
+ }
+
+ return s
+}
+
+// Conntrack parameters and options:
+// -n, --src-nat ip source NAT ip
+// -g, --dst-nat ip destination NAT ip
+// -j, --any-nat ip source or destination NAT ip
+// -m, --mark mark Set mark
+// -c, --secmark secmark Set selinux secmark
+// -e, --event-mask eventmask Event mask, eg. NEW,DESTROY
+// -z, --zero Zero counters while listing
+// -o, --output type[,...] Output format, eg. xml
+// -l, --label label[,...] conntrack labels
+
+// Common parameters and options:
+// -s, --src, --orig-src ip Source address from original direction
+// -d, --dst, --orig-dst ip Destination address from original direction
+// -r, --reply-src ip Source addres from reply direction
+// -q, --reply-dst ip Destination address from reply direction
+// -p, --protonum proto Layer 4 Protocol, eg. 'tcp'
+// -f, --family proto Layer 3 Protocol, eg. 'ipv6'
+// -t, --timeout timeout Set timeout
+// -u, --status status Set status, eg. ASSURED
+// -w, --zone value Set conntrack zone
+// --orig-zone value Set zone for original direction
+// --reply-zone value Set zone for reply direction
+// -b, --buffer-size Netlink socket buffer size
+// --mask-src ip Source mask address
+// --mask-dst ip Destination mask address
+
+// Filter types
+type ConntrackFilterType uint8
+
+const (
+ ConntrackOrigSrcIP = iota // -orig-src ip Source address from original direction
+ ConntrackOrigDstIP // -orig-dst ip Destination address from original direction
+ ConntrackNatSrcIP // -src-nat ip Source NAT ip
+ ConntrackNatDstIP // -dst-nat ip Destination NAT ip
+ ConntrackNatAnyIP // -any-nat ip Source or destination NAT ip
+)
+
+type ConntrackFilter struct {
+ ipFilter map[ConntrackFilterType]net.IP
+}
+
+// AddIP adds an IP to the conntrack filter
+func (f *ConntrackFilter) AddIP(tp ConntrackFilterType, ip net.IP) error {
+ if f.ipFilter == nil {
+ f.ipFilter = make(map[ConntrackFilterType]net.IP)
+ }
+ if _, ok := f.ipFilter[tp]; ok {
+ return errors.New("Filter attribute already present")
+ }
+ f.ipFilter[tp] = ip
+ return nil
+}
+
+// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter
+// false otherwise
+func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
+ if len(f.ipFilter) == 0 {
+ // empty filter always not match
+ return false
+ }
+
+ match := true
+ // -orig-src ip Source address from original direction
+ if elem, found := f.ipFilter[ConntrackOrigSrcIP]; found {
+ match = match && elem.Equal(flow.Forward.SrcIP)
+ }
+
+ // -orig-dst ip Destination address from original direction
+ if elem, found := f.ipFilter[ConntrackOrigDstIP]; match && found {
+ match = match && elem.Equal(flow.Forward.DstIP)
+ }
+
+ // -src-nat ip Source NAT ip
+ if elem, found := f.ipFilter[ConntrackNatSrcIP]; match && found {
+ match = match && elem.Equal(flow.Reverse.SrcIP)
+ }
+
+ // -dst-nat ip Destination NAT ip
+ if elem, found := f.ipFilter[ConntrackNatDstIP]; match && found {
+ match = match && elem.Equal(flow.Reverse.DstIP)
+ }
+
+ // -any-nat ip Source or destination NAT ip
+ if elem, found := f.ipFilter[ConntrackNatAnyIP]; match && found {
+ match = match && (elem.Equal(flow.Reverse.SrcIP) || elem.Equal(flow.Reverse.DstIP))
+ }
+
+ return match
+}
--- /dev/null
+package netlink
+
+import (
+ "fmt"
+ "net"
+ "runtime"
+ "syscall"
+ "testing"
+
+ "github.com/vishvananda/netns"
+)
+
+func CheckErrorFail(t *testing.T, err error) {
+ if err != nil {
+ t.Fatalf("Fatal Error: %s", err)
+ }
+}
+func CheckError(t *testing.T, err error) {
+ if err != nil {
+ t.Errorf("Error: %s", err)
+ }
+}
+
+func udpFlowCreateProg(t *testing.T, flows, srcPort int, dstIP string, dstPort int) {
+ for i := 0; i < flows; i++ {
+ ServerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", dstIP, dstPort))
+ CheckError(t, err)
+
+ LocalAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", srcPort+i))
+ CheckError(t, err)
+
+ Conn, err := net.DialUDP("udp", LocalAddr, ServerAddr)
+ CheckError(t, err)
+
+ Conn.Write([]byte("Hello World"))
+ Conn.Close()
+ }
+}
+
+func nsCreateAndEnter(t *testing.T) (*netns.NsHandle, *netns.NsHandle, *Handle) {
+ // Lock the OS Thread so we don't accidentally switch namespaces
+ runtime.LockOSThread()
+
+ // Save the current network namespace
+ origns, _ := netns.Get()
+
+ ns, err := netns.New()
+ CheckErrorFail(t, err)
+
+ h, err := NewHandleAt(ns)
+ CheckErrorFail(t, err)
+
+ // Enter the new namespace
+ netns.Set(ns)
+
+ // Bing up loopback
+ link, _ := h.LinkByName("lo")
+ h.LinkSetUp(link)
+
+ return &origns, &ns, h
+}
+
+func applyFilter(flowList []ConntrackFlow, ipv4Filter *ConntrackFilter, ipv6Filter *ConntrackFilter) (ipv4Match, ipv6Match uint) {
+ for _, flow := range flowList {
+ if ipv4Filter.MatchConntrackFlow(&flow) == true {
+ ipv4Match++
+ }
+ if ipv6Filter.MatchConntrackFlow(&flow) == true {
+ ipv6Match++
+ }
+ }
+ return ipv4Match, ipv6Match
+}
+
+// TestConntrackSocket test the opening of a NETFILTER family socket
+func TestConntrackSocket(t *testing.T) {
+ skipUnlessRoot(t)
+
+ h, err := NewHandle(syscall.NETLINK_NETFILTER)
+ CheckErrorFail(t, err)
+
+ if h.SupportsNetlinkFamily(syscall.NETLINK_NETFILTER) != true {
+ t.Fatal("ERROR not supporting the NETFILTER family")
+ }
+}
+
+// TestConntrackTableList test the conntrack table list
+// Creates some flows and checks that they are correctly fetched from the conntrack table
+func TestConntrackTableList(t *testing.T) {
+ skipUnlessRoot(t)
+
+ // Creates a new namespace and bring up the loopback interface
+ origns, ns, h := nsCreateAndEnter(t)
+ defer netns.Set(*origns)
+ defer origns.Close()
+ defer ns.Close()
+ defer runtime.UnlockOSThread()
+
+ // Flush the table to start fresh
+ err := h.ConntrackTableFlush(ConntrackTable)
+ CheckErrorFail(t, err)
+
+ // Create 5 udp
+ udpFlowCreateProg(t, 5, 2000, "127.0.0.10", 3000)
+
+ // Fetch the conntrack table
+ flows, err := h.ConntrackTableList(ConntrackTable, syscall.AF_INET)
+ CheckErrorFail(t, err)
+
+ // Check that it is able to find the 5 flows created
+ var found int
+ for _, flow := range flows {
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) &&
+ flow.Forward.DstPort == 3000 &&
+ (flow.Forward.SrcPort >= 2000 && flow.Forward.SrcPort <= 2005) {
+ found++
+ }
+ }
+ if found != 5 {
+ t.Fatalf("Found only %d flows over 5", found)
+ }
+
+ // Give a try also to the IPv6 version
+ _, err = h.ConntrackTableList(ConntrackTable, syscall.AF_INET6)
+ CheckErrorFail(t, err)
+
+ // Switch back to the original namespace
+ netns.Set(*origns)
+}
+
+// TestConntrackTableFlush test the conntrack table flushing
+// Creates some flows and then call the table flush
+func TestConntrackTableFlush(t *testing.T) {
+ skipUnlessRoot(t)
+
+ // Creates a new namespace and bring up the loopback interface
+ origns, ns, h := nsCreateAndEnter(t)
+ defer netns.Set(*origns)
+ defer origns.Close()
+ defer ns.Close()
+ defer runtime.UnlockOSThread()
+
+ // Create 5 udp flows using netcat
+ udpFlowCreateProg(t, 5, 3000, "127.0.0.10", 4000)
+
+ // Fetch the conntrack table
+ flows, err := h.ConntrackTableList(ConntrackTable, syscall.AF_INET)
+ CheckErrorFail(t, err)
+
+ // Check that it is able to find the 5 flows created
+ var found int
+ for _, flow := range flows {
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) &&
+ flow.Forward.DstPort == 4000 &&
+ (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) {
+ found++
+ }
+ }
+ if found != 5 {
+ t.Fatalf("Found only %d flows over 5", found)
+ }
+
+ // Flush the table
+ err = h.ConntrackTableFlush(ConntrackTable)
+ CheckErrorFail(t, err)
+
+ // Fetch again the flows to validate the flush
+ flows, err = h.ConntrackTableList(ConntrackTable, syscall.AF_INET)
+ CheckErrorFail(t, err)
+
+ // Check if it is still able to find the 5 flows created
+ found = 0
+ for _, flow := range flows {
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) &&
+ flow.Forward.DstPort == 4000 &&
+ (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) {
+ found++
+ }
+ }
+ if found > 0 {
+ t.Fatalf("Found %d flows, they should had been flushed", found)
+ }
+
+ // Switch back to the original namespace
+ netns.Set(*origns)
+}
+
+// TestConntrackTableDelete tests the deletion with filter
+// Creates 2 group of flows then deletes only one group and validates the result
+func TestConntrackTableDelete(t *testing.T) {
+ skipUnlessRoot(t)
+
+ // Creates a new namespace and bring up the loopback interface
+ origns, ns, h := nsCreateAndEnter(t)
+ defer netns.Set(*origns)
+ defer origns.Close()
+ defer ns.Close()
+ defer runtime.UnlockOSThread()
+
+ // Create 10 udp flows
+ udpFlowCreateProg(t, 5, 5000, "127.0.0.10", 6000)
+ udpFlowCreateProg(t, 5, 7000, "127.0.0.20", 8000)
+
+ // Fetch the conntrack table
+ flows, err := h.ConntrackTableList(ConntrackTable, syscall.AF_INET)
+ CheckErrorFail(t, err)
+
+ // Check that it is able to find the 5 flows created for each group
+ var groupA int
+ var groupB int
+ for _, flow := range flows {
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) &&
+ flow.Forward.DstPort == 6000 &&
+ (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) {
+ groupA++
+ }
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) &&
+ flow.Forward.DstPort == 8000 &&
+ (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) {
+ groupB++
+ }
+ }
+ if groupA != 5 || groupB != 5 {
+ t.Fatalf("Flow creation issue groupA:%d, groupB:%d", groupA, groupB)
+ }
+
+ // Create a filter to erase groupB flows
+ filter := &ConntrackFilter{}
+ filter.AddIP(ConntrackOrigDstIP, net.ParseIP("127.0.0.20"))
+
+ // Flush entries of groupB
+ var deleted uint
+ if deleted, err = h.ConntrackDeleteFilter(ConntrackTable, syscall.AF_INET, filter); err != nil {
+ t.Fatalf("Error during the erase: %s", err)
+ }
+ if deleted != 5 {
+ t.Fatalf("Error deleted a wrong number of flows:%d instead of 5", deleted)
+ }
+
+ // Check again the table to verify that are gone
+ flows, err = h.ConntrackTableList(ConntrackTable, syscall.AF_INET)
+ CheckErrorFail(t, err)
+
+ // Check if it is able to find the 5 flows of groupA but none of groupB
+ groupA = 0
+ groupB = 0
+ for _, flow := range flows {
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) &&
+ flow.Forward.DstPort == 6000 &&
+ (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) {
+ groupA++
+ }
+ if flow.Forward.Protocol == 17 &&
+ flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) &&
+ flow.Forward.DstPort == 8000 &&
+ (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) {
+ groupB++
+ }
+ }
+ if groupA != 5 || groupB > 0 {
+ t.Fatalf("Error during the erase groupA:%d, groupB:%d", groupA, groupB)
+ }
+
+ // Switch back to the original namespace
+ netns.Set(*origns)
+}
+
+func TestConntrackFilter(t *testing.T) {
+ var flowList []ConntrackFlow
+ flowList = append(flowList, ConntrackFlow{
+ FamilyType: syscall.AF_INET,
+ Forward: ipTuple{
+ SrcIP: net.ParseIP("10.0.0.1"),
+ DstIP: net.ParseIP("20.0.0.1"),
+ SrcPort: 1000,
+ DstPort: 2000,
+ },
+ Reverse: ipTuple{
+ SrcIP: net.ParseIP("20.0.0.1"),
+ DstIP: net.ParseIP("192.168.1.1"),
+ SrcPort: 2000,
+ DstPort: 1000,
+ },
+ },
+ ConntrackFlow{
+ FamilyType: syscall.AF_INET,
+ Forward: ipTuple{
+ SrcIP: net.ParseIP("10.0.0.2"),
+ DstIP: net.ParseIP("20.0.0.2"),
+ SrcPort: 5000,
+ DstPort: 6000,
+ },
+ Reverse: ipTuple{
+ SrcIP: net.ParseIP("20.0.0.2"),
+ DstIP: net.ParseIP("192.168.1.1"),
+ SrcPort: 6000,
+ DstPort: 5000,
+ },
+ },
+ ConntrackFlow{
+ FamilyType: syscall.AF_INET6,
+ Forward: ipTuple{
+ SrcIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
+ DstIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
+ SrcPort: 1000,
+ DstPort: 2000,
+ },
+ Reverse: ipTuple{
+ SrcIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"),
+ DstIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"),
+ SrcPort: 2000,
+ DstPort: 1000,
+ },
+ })
+
+ // Empty filter
+ v4Match, v6Match := applyFilter(flowList, &ConntrackFilter{}, &ConntrackFilter{})
+ if v4Match > 0 || v6Match > 0 {
+ t.Fatalf("Error, empty filter cannot match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+
+ // SrcIP filter
+ filterV4 := &ConntrackFilter{}
+ filterV4.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1"))
+
+ filterV6 := &ConntrackFilter{}
+ filterV6.AddIP(ConntrackOrigSrcIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"))
+
+ v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
+ if v4Match != 1 || v6Match != 1 {
+ t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+
+ // DstIp filter
+ filterV4 = &ConntrackFilter{}
+ filterV4.AddIP(ConntrackOrigDstIP, net.ParseIP("20.0.0.1"))
+
+ filterV6 = &ConntrackFilter{}
+ filterV6.AddIP(ConntrackOrigDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"))
+
+ v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
+ if v4Match != 1 || v6Match != 1 {
+ t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+
+ // SrcIP for NAT
+ filterV4 = &ConntrackFilter{}
+ filterV4.AddIP(ConntrackNatSrcIP, net.ParseIP("20.0.0.1"))
+
+ filterV6 = &ConntrackFilter{}
+ filterV6.AddIP(ConntrackNatSrcIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"))
+
+ v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
+ if v4Match != 1 || v6Match != 1 {
+ t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+
+ // DstIP for NAT
+ filterV4 = &ConntrackFilter{}
+ filterV4.AddIP(ConntrackNatDstIP, net.ParseIP("192.168.1.1"))
+
+ filterV6 = &ConntrackFilter{}
+ filterV6.AddIP(ConntrackNatDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"))
+
+ v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
+ if v4Match != 2 || v6Match != 0 {
+ t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+
+ // AnyIp for Nat
+ filterV4 = &ConntrackFilter{}
+ filterV4.AddIP(ConntrackNatAnyIP, net.ParseIP("192.168.1.1"))
+
+ filterV6 = &ConntrackFilter{}
+ filterV6.AddIP(ConntrackNatAnyIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"))
+
+ v4Match, v6Match = applyFilter(flowList, filterV4, filterV6)
+ if v4Match != 2 || v6Match != 1 {
+ t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match)
+ }
+}
--- /dev/null
+// +build !linux
+
+package netlink
+
+// ConntrackTableType Conntrack table for the netlink operation
+type ConntrackTableType uint8
+
+// InetFamily Family type
+type InetFamily uint8
+
+// ConntrackFlow placeholder
+type ConntrackFlow struct{}
+
+// ConntrackFilter placeholder
+type ConntrackFilter struct{}
+
+// ConntrackTableList returns the flow list of a table of a specific family
+// conntrack -L [table] [options] List conntrack or expectation table
+func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
+ return nil, ErrNotImplemented
+}
+
+// ConntrackTableFlush flushes all the flows of a specified table
+// conntrack -F [table] Flush table
+// The flush operation applies to all the family types
+func ConntrackTableFlush(table ConntrackTableType) error {
+ return ErrNotImplemented
+}
+
+// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
+// conntrack -D [table] parameters Delete conntrack or expectation
+func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
+ return 0, ErrNotImplemented
+}
+
+// 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
+func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
+ return nil, ErrNotImplemented
+}
+
+// ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed
+// conntrack -F [table] Flush table
+// The flush operation applies to all the family types
+func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
+ return ErrNotImplemented
+}
+
+// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
+// conntrack -D [table] parameters Delete conntrack or expectation
+func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
+ return 0, ErrNotImplemented
+}
package netlink
-import "fmt"
+import (
+ "fmt"
+
+ "github.com/vishvananda/netlink/nl"
+)
type Filter interface {
Attrs() *FilterAttrs
}
}
+// Constants used in TcU32Sel.Flags.
+const (
+ TC_U32_TERMINAL = nl.TC_U32_TERMINAL
+ TC_U32_OFFSET = nl.TC_U32_OFFSET
+ TC_U32_VAROFFSET = nl.TC_U32_VAROFFSET
+ TC_U32_EAT = nl.TC_U32_EAT
+)
+
+// Sel of the U32 filters that contains multiple TcU32Key. This is the copy
+// and the frontend representation of nl.TcU32Sel. It is serialized into canonical
+// nl.TcU32Sel with the appropriate endianness.
+type TcU32Sel struct {
+ Flags uint8
+ Offshift uint8
+ Nkeys uint8
+ Pad uint8
+ Offmask uint16
+ Off uint16
+ Offoff int16
+ Hoff int16
+ Hmask uint32
+ Keys []TcU32Key
+}
+
+// TcU32Key contained of Sel in the U32 filters. This is the copy and the frontend
+// representation of nl.TcU32Key. It is serialized into chanonical nl.TcU32Sel
+// with the appropriate endianness.
+type TcU32Key struct {
+ Mask uint32
+ Val uint32
+ Off int32
+ OffMask int32
+}
+
// U32 filters on many packet related properties
type U32 struct {
FilterAttrs
ClassId uint32
RedirIndex int
+ Sel *TcU32Sel
Actions []Action
}
"errors"
"fmt"
"syscall"
+ "unsafe"
"github.com/vishvananda/netlink/nl"
)
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
if u32, ok := filter.(*U32); ok {
- // match all
- sel := nl.TcU32Sel{
- Nkeys: 1,
- Flags: nl.TC_U32_TERMINAL,
+ // Convert TcU32Sel into nl.TcU32Sel as it is without copy.
+ sel := (*nl.TcU32Sel)(unsafe.Pointer(u32.Sel))
+ if sel == nil {
+ // match all
+ sel = &nl.TcU32Sel{
+ Nkeys: 1,
+ Flags: nl.TC_U32_TERMINAL,
+ }
+ sel.Keys = append(sel.Keys, nl.TcU32Key{})
}
- sel.Keys = append(sel.Keys, nl.TcU32Key{})
+
+ if native != networkOrder {
+ // Copy Tcu32Sel.
+ cSel := sel
+ keys := make([]nl.TcU32Key, cap(sel.Keys))
+ copy(keys, sel.Keys)
+ cSel.Keys = keys
+ sel = cSel
+
+ // Handle the endianness of attributes
+ sel.Offmask = native.Uint16(htons(sel.Offmask))
+ sel.Hmask = native.Uint32(htonl(sel.Hmask))
+ for _, key := range sel.Keys {
+ key.Mask = native.Uint32(htonl(key.Mask))
+ key.Val = native.Uint32(htonl(key.Val))
+ }
+ }
+ sel.Nkeys = uint8(len(sel.Keys))
nl.NewRtAttrChild(options, nl.TCA_U32_SEL, sel.Serialize())
if u32.ClassId != 0 {
nl.NewRtAttrChild(options, nl.TCA_U32_CLASSID, nl.Uint32Attr(u32.ClassId))
case nl.TCA_U32_SEL:
detailed = true
sel := nl.DeserializeTcU32Sel(datum.Value)
- // only parse if we have a very basic redirect
- if sel.Flags&nl.TC_U32_TERMINAL == 0 || sel.Nkeys != 1 {
- return detailed, nil
+ u32.Sel = (*TcU32Sel)(unsafe.Pointer(sel))
+ if native != networkOrder {
+ // Handle the endianness of attributes
+ u32.Sel.Offmask = native.Uint16(htons(sel.Offmask))
+ u32.Sel.Hmask = native.Uint32(htonl(sel.Hmask))
+ for _, key := range u32.Sel.Keys {
+ key.Mask = native.Uint32(htonl(key.Mask))
+ key.Val = native.Uint32(htonl(key.Val))
+ }
}
case nl.TCA_U32_ACT:
tables, err := nl.ParseRouteAttr(datum.Value)
u32.RedirIndex = int(action.Ifindex)
}
}
+ case nl.TCA_U32_CLASSID:
+ u32.ClassId = native.Uint32(datum.Value)
}
}
return detailed, nil
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "syscall"
+ "testing"
+)
+
+func TestFilterAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ redir, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(redir); err != nil {
+ t.Fatal(err)
+ }
+ qdisc := &Ingress{
+ QdiscAttrs: QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_INGRESS,
+ },
+ }
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Ingress)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ classId := MakeHandle(1, 1)
+ filter := &U32{
+ FilterAttrs: FilterAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Priority: 1,
+ Protocol: syscall.ETH_P_IP,
+ },
+ RedirIndex: redir.Attrs().Index,
+ ClassId: classId,
+ }
+ if err := FilterAdd(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err := FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 1 {
+ t.Fatal("Failed to add filter")
+ }
+ u32, ok := filters[0].(*U32)
+ if !ok {
+ t.Fatal("Filter is the wrong type")
+ }
+ if u32.ClassId != classId {
+ t.Fatalf("ClassId of the filter is the wrong value")
+ }
+ if err := FilterDel(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err = FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 0 {
+ t.Fatal("Failed to remove filter")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestAdvancedFilterAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "baz"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("baz")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ index := link.Attrs().Index
+
+ qdiscHandle := MakeHandle(0x1, 0x0)
+ qdiscAttrs := QdiscAttrs{
+ LinkIndex: index,
+ Handle: qdiscHandle,
+ Parent: HANDLE_ROOT,
+ }
+
+ qdisc := NewHtb(qdiscAttrs)
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ classId := MakeHandle(0x1, 0x46cb)
+ classAttrs := ClassAttrs{
+ LinkIndex: index,
+ Parent: qdiscHandle,
+ Handle: classId,
+ }
+ htbClassAttrs := HtbClassAttrs{
+ Rate: 512 * 1024,
+ Buffer: 32 * 1024,
+ }
+ htbClass := NewHtbClass(classAttrs, htbClassAttrs)
+ if err = ClassReplace(htbClass); err != nil {
+ t.Fatalf("Failed to add a HTB class: %v", err)
+ }
+ classes, err := ClassList(link, qdiscHandle)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatal("Failed to add class")
+ }
+ _, ok = classes[0].(*HtbClass)
+ if !ok {
+ t.Fatal("Class is the wrong type")
+ }
+
+ u32SelKeys := []TcU32Key{
+ TcU32Key{
+ Mask: 0xff,
+ Val: 80,
+ Off: 20,
+ OffMask: 0,
+ },
+ TcU32Key{
+ Mask: 0xffff,
+ Val: 0x146ca,
+ Off: 32,
+ OffMask: 0,
+ },
+ }
+ filter := &U32{
+ FilterAttrs: FilterAttrs{
+ LinkIndex: index,
+ Parent: qdiscHandle,
+ Priority: 1,
+ Protocol: syscall.ETH_P_ALL,
+ },
+ Sel: &TcU32Sel{
+ Keys: u32SelKeys,
+ Flags: TC_U32_TERMINAL,
+ },
+ ClassId: classId,
+ Actions: []Action{},
+ }
+ if err := FilterAdd(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err := FilterList(link, qdiscHandle)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 1 {
+ t.Fatal("Failed to add filter")
+ }
+
+ u32, ok := filters[0].(*U32)
+ if !ok {
+ t.Fatal("Filter is the wrong type")
+ }
+ // Endianness checks
+ if u32.Sel.Offmask != filter.Sel.Offmask {
+ t.Fatal("The endianness of TcU32Key.Sel.Offmask is wrong")
+ }
+ if u32.Sel.Hmask != filter.Sel.Hmask {
+ t.Fatal("The endianness of TcU32Key.Sel.Hmask is wrong")
+ }
+ for i, key := range u32.Sel.Keys {
+ if key.Mask != filter.Sel.Keys[i].Mask {
+ t.Fatal("The endianness of TcU32Key.Mask is wrong")
+ }
+ if key.Val != filter.Sel.Keys[i].Val {
+ t.Fatal("The endianness of TcU32Key.Val is wrong")
+ }
+ }
+
+ if err := FilterDel(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err = FilterList(link, qdiscHandle)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 0 {
+ t.Fatal("Failed to remove filter")
+ }
+
+ if err = ClassDel(htbClass); err != nil {
+ t.Fatalf("Failed to delete a HTP class: %v", err)
+ }
+ classes, err = ClassList(link, qdiscHandle)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 0 {
+ t.Fatal("Failed to remove class")
+ }
+
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestFilterFwAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ redir, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(redir); err != nil {
+ t.Fatal(err)
+ }
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_ROOT,
+ }
+ qdisc := NewHtb(attrs)
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ classattrs := ClassAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Handle: MakeHandle(0xffff, 2),
+ }
+
+ htbclassattrs := HtbClassAttrs{
+ Rate: 1234000,
+ Cbuffer: 1690,
+ }
+ class := NewHtbClass(classattrs, htbclassattrs)
+ if err := ClassAdd(class); err != nil {
+ t.Fatal(err)
+ }
+ classes, err := ClassList(link, MakeHandle(0xffff, 2))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 1 {
+ t.Fatal("Failed to add class")
+ }
+
+ filterattrs := FilterAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Handle: MakeHandle(0, 0x6),
+ Priority: 1,
+ Protocol: syscall.ETH_P_IP,
+ }
+ fwattrs := FilterFwAttrs{
+ Buffer: 12345,
+ Rate: 1234,
+ PeakRate: 2345,
+ Action: TC_POLICE_SHOT,
+ ClassId: MakeHandle(0xffff, 2),
+ }
+
+ filter, err := NewFw(filterattrs, fwattrs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := FilterAdd(filter); err != nil {
+ t.Fatal(err)
+ }
+
+ filters, err := FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 1 {
+ t.Fatal("Failed to add filter")
+ }
+ fw, ok := filters[0].(*Fw)
+ if !ok {
+ t.Fatal("Filter is the wrong type")
+ }
+ if fw.Police.Rate.Rate != filter.Police.Rate.Rate {
+ t.Fatal("Police Rate doesn't match")
+ }
+ for i := range fw.Rtab {
+ if fw.Rtab[i] != filter.Rtab[i] {
+ t.Fatal("Rtab doesn't match")
+ }
+ if fw.Ptab[i] != filter.Ptab[i] {
+ t.Fatal("Ptab doesn't match")
+ }
+ }
+ if fw.ClassId != filter.ClassId {
+ t.Fatal("ClassId doesn't match")
+ }
+ if fw.InDev != filter.InDev {
+ t.Fatal("InDev doesn't match")
+ }
+ if fw.AvRate != filter.AvRate {
+ t.Fatal("AvRate doesn't match")
+ }
+
+ if err := FilterDel(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err = FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 0 {
+ t.Fatal("Failed to remove filter")
+ }
+ if err := ClassDel(class); err != nil {
+ t.Fatal(err)
+ }
+ classes, err = ClassList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(classes) != 0 {
+ t.Fatal("Failed to remove class")
+ }
+
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestFilterU32BpfAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ redir, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(redir); err != nil {
+ t.Fatal(err)
+ }
+ qdisc := &Ingress{
+ QdiscAttrs: QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_INGRESS,
+ },
+ }
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Ingress)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+
+ fd, err := loadSimpleBpf(BPF_PROG_TYPE_SCHED_ACT, 1)
+ if err != nil {
+ t.Skipf("Loading bpf program failed: %s", err)
+ }
+ classId := MakeHandle(1, 1)
+ filter := &U32{
+ FilterAttrs: FilterAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: MakeHandle(0xffff, 0),
+ Priority: 1,
+ Protocol: syscall.ETH_P_ALL,
+ },
+ ClassId: classId,
+ Actions: []Action{
+ &BpfAction{Fd: fd, Name: "simple"},
+ &MirredAction{
+ ActionAttrs: ActionAttrs{
+ Action: TC_ACT_STOLEN,
+ },
+ MirredAction: TCA_EGRESS_REDIR,
+ Ifindex: redir.Attrs().Index,
+ },
+ },
+ }
+
+ if err := FilterAdd(filter); err != nil {
+ t.Fatal(err)
+ }
+
+ filters, err := FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 1 {
+ t.Fatal("Failed to add filter")
+ }
+ u32, ok := filters[0].(*U32)
+ if !ok {
+ t.Fatal("Filter is the wrong type")
+ }
+
+ if len(u32.Actions) != 2 {
+ t.Fatalf("Too few Actions in filter")
+ }
+ if u32.ClassId != classId {
+ t.Fatalf("ClassId of the filter is the wrong value")
+ }
+ bpfAction, ok := u32.Actions[0].(*BpfAction)
+ if !ok {
+ t.Fatal("Action[0] is the wrong type")
+ }
+ if bpfAction.Fd != fd {
+ t.Fatal("Action Fd does not match")
+ }
+ if _, ok := u32.Actions[1].(*MirredAction); !ok {
+ t.Fatal("Action[1] is the wrong type")
+ }
+
+ if err := FilterDel(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err = FilterList(link, MakeHandle(0xffff, 0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 0 {
+ t.Fatal("Failed to remove filter")
+ }
+
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestFilterClsActBpfAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(0xffff, 0),
+ Parent: HANDLE_CLSACT,
+ }
+ qdisc := &GenericQdisc{
+ QdiscAttrs: attrs,
+ QdiscType: "clsact",
+ }
+ // This feature was added in kernel 4.5
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Skipf("Failed adding clsact qdisc, unsupported kernel")
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ if q, ok := qdiscs[0].(*GenericQdisc); !ok || q.Type() != "clsact" {
+ t.Fatal("qdisc is the wrong type")
+ }
+
+ filterattrs := FilterAttrs{
+ LinkIndex: link.Attrs().Index,
+ Parent: HANDLE_MIN_EGRESS,
+ Handle: MakeHandle(0, 1),
+ Protocol: syscall.ETH_P_ALL,
+ Priority: 1,
+ }
+ fd, err := loadSimpleBpf(BPF_PROG_TYPE_SCHED_CLS, 1)
+ if err != nil {
+ t.Skipf("Loading bpf program failed: %s", err)
+ }
+ filter := &BpfFilter{
+ FilterAttrs: filterattrs,
+ Fd: fd,
+ Name: "simple",
+ DirectAction: true,
+ }
+ if filter.Fd < 0 {
+ t.Skipf("Failed to load bpf program")
+ }
+
+ if err := FilterAdd(filter); err != nil {
+ t.Fatal(err)
+ }
+
+ filters, err := FilterList(link, HANDLE_MIN_EGRESS)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 1 {
+ t.Fatal("Failed to add filter")
+ }
+ bpf, ok := filters[0].(*BpfFilter)
+ if !ok {
+ t.Fatal("Filter is the wrong type")
+ }
+
+ if bpf.Fd != filter.Fd {
+ t.Fatal("Filter Fd does not match")
+ }
+ if bpf.DirectAction != filter.DirectAction {
+ t.Fatal("Filter DirectAction does not match")
+ }
+
+ if err := FilterDel(filter); err != nil {
+ t.Fatal(err)
+ }
+ filters, err = FilterList(link, HANDLE_MIN_EGRESS)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(filters) != 0 {
+ t.Fatal("Failed to remove filter")
+ }
+
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "net"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "testing"
+ "time"
+ "unsafe"
+
+ "github.com/vishvananda/netlink/nl"
+ "github.com/vishvananda/netns"
+)
+
+func TestHandleCreateDelete(t *testing.T) {
+ h, err := NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, f := range nl.SupportedNlFamilies {
+ sh, ok := h.sockets[f]
+ if !ok {
+ t.Fatalf("Handle socket(s) for family %d was not created", f)
+ }
+ if sh.Socket == nil {
+ t.Fatalf("Socket for family %d was not created", f)
+ }
+ }
+
+ h.Delete()
+ if h.sockets != nil {
+ t.Fatalf("Handle socket(s) were not destroyed")
+ }
+}
+
+func TestHandleCreateNetns(t *testing.T) {
+ skipUnlessRoot(t)
+
+ id := make([]byte, 4)
+ if _, err := io.ReadFull(rand.Reader, id); err != nil {
+ t.Fatal(err)
+ }
+ ifName := "dummy-" + hex.EncodeToString(id)
+
+ // Create an handle on the current netns
+ curNs, err := netns.Get()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer curNs.Close()
+
+ ch, err := NewHandleAt(curNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ch.Delete()
+
+ // Create an handle on a custom netns
+ newNs, err := netns.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer newNs.Close()
+
+ nh, err := NewHandleAt(newNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ // Create an interface using the current handle
+ err = ch.LinkAdd(&Dummy{LinkAttrs{Name: ifName}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ l, err := ch.LinkByName(ifName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if l.Type() != "dummy" {
+ t.Fatalf("Unexpected link type: %s", l.Type())
+ }
+
+ // Verify the new handle cannot find the interface
+ ll, err := nh.LinkByName(ifName)
+ if err == nil {
+ t.Fatalf("Unexpected link found on netns %s: %v", newNs, ll)
+ }
+
+ // Move the interface to the new netns
+ err = ch.LinkSetNsFd(l, int(newNs))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify new netns handle can find the interface while current cannot
+ ll, err = nh.LinkByName(ifName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ll.Type() != "dummy" {
+ t.Fatalf("Unexpected link type: %s", ll.Type())
+ }
+ ll, err = ch.LinkByName(ifName)
+ if err == nil {
+ t.Fatalf("Unexpected link found on netns %s: %v", curNs, ll)
+ }
+}
+
+func TestHandleTimeout(t *testing.T) {
+ h, err := NewHandle()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer h.Delete()
+
+ for _, sh := range h.sockets {
+ verifySockTimeVal(t, sh.Socket.GetFd(), syscall.Timeval{Sec: 0, Usec: 0})
+ }
+
+ h.SetSocketTimeout(2*time.Second + 8*time.Millisecond)
+
+ for _, sh := range h.sockets {
+ verifySockTimeVal(t, sh.Socket.GetFd(), syscall.Timeval{Sec: 2, Usec: 8000})
+ }
+}
+
+func verifySockTimeVal(t *testing.T, fd int, tv syscall.Timeval) {
+ var (
+ tr syscall.Timeval
+ v = uint32(0x10)
+ )
+ _, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(fd), syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, uintptr(unsafe.Pointer(&tr)), uintptr(unsafe.Pointer(&v)), 0)
+ if errno != 0 {
+ t.Fatal(errno)
+ }
+
+ if tr.Sec != tv.Sec || tr.Usec != tv.Usec {
+ t.Fatalf("Unexpected timeout value read: %v. Expected: %v", tr, tv)
+ }
+
+ _, _, errno = syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(fd), syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, uintptr(unsafe.Pointer(&tr)), uintptr(unsafe.Pointer(&v)), 0)
+ if errno != 0 {
+ t.Fatal(errno)
+ }
+
+ if tr.Sec != tv.Sec || tr.Usec != tv.Usec {
+ t.Fatalf("Unexpected timeout value read: %v. Expected: %v", tr, tv)
+ }
+}
+
+var (
+ iter = 10
+ numThread = uint32(4)
+ prefix = "iface"
+ handle1 *Handle
+ handle2 *Handle
+ ns1 netns.NsHandle
+ ns2 netns.NsHandle
+ done uint32
+ initError error
+ once sync.Once
+)
+
+func getXfrmState(thread int) *XfrmState {
+ return &XfrmState{
+ Src: net.IPv4(byte(192), byte(168), 1, byte(1+thread)),
+ Dst: net.IPv4(byte(192), byte(168), 2, byte(1+thread)),
+ Proto: XFRM_PROTO_AH,
+ Mode: XFRM_MODE_TUNNEL,
+ Spi: thread,
+ Auth: &XfrmStateAlgo{
+ Name: "hmac(sha256)",
+ Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"),
+ },
+ }
+}
+
+func getXfrmPolicy(thread int) *XfrmPolicy {
+ return &XfrmPolicy{
+ Src: &net.IPNet{IP: net.IPv4(byte(10), byte(10), byte(thread), 0), Mask: []byte{255, 255, 255, 0}},
+ Dst: &net.IPNet{IP: net.IPv4(byte(10), byte(10), byte(thread), 0), Mask: []byte{255, 255, 255, 0}},
+ Proto: 17,
+ DstPort: 1234,
+ SrcPort: 5678,
+ Dir: XFRM_DIR_OUT,
+ Tmpls: []XfrmPolicyTmpl{
+ {
+ Src: net.IPv4(byte(192), byte(168), 1, byte(thread)),
+ Dst: net.IPv4(byte(192), byte(168), 2, byte(thread)),
+ Proto: XFRM_PROTO_ESP,
+ Mode: XFRM_MODE_TUNNEL,
+ },
+ },
+ }
+}
+func initParallel() {
+ ns1, initError = netns.New()
+ if initError != nil {
+ return
+ }
+ handle1, initError = NewHandleAt(ns1)
+ if initError != nil {
+ return
+ }
+ ns2, initError = netns.New()
+ if initError != nil {
+ return
+ }
+ handle2, initError = NewHandleAt(ns2)
+ if initError != nil {
+ return
+ }
+}
+
+func parallelDone() {
+ atomic.AddUint32(&done, 1)
+ if done == numThread {
+ if ns1.IsOpen() {
+ ns1.Close()
+ }
+ if ns2.IsOpen() {
+ ns2.Close()
+ }
+ if handle1 != nil {
+ handle1.Delete()
+ }
+ if handle2 != nil {
+ handle2.Delete()
+ }
+ }
+}
+
+// Do few route and xfrm operation on the two handles in parallel
+func runParallelTests(t *testing.T, thread int) {
+ skipUnlessRoot(t)
+ defer parallelDone()
+
+ t.Parallel()
+
+ once.Do(initParallel)
+ if initError != nil {
+ t.Fatal(initError)
+ }
+
+ state := getXfrmState(thread)
+ policy := getXfrmPolicy(thread)
+ for i := 0; i < iter; i++ {
+ ifName := fmt.Sprintf("%s_%d_%d", prefix, thread, i)
+ link := &Dummy{LinkAttrs{Name: ifName}}
+ err := handle1.LinkAdd(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ l, err := handle1.LinkByName(ifName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.LinkSetUp(l)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handle1.LinkSetNsFd(l, int(ns2))
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.XfrmStateAdd(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.XfrmPolicyAdd(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle2.LinkSetDown(l)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle2.XfrmStateAdd(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle2.XfrmPolicyAdd(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = handle2.LinkByName(ifName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ handle2.LinkSetNsFd(l, int(ns1))
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.LinkSetUp(l)
+ if err != nil {
+ t.Fatal(err)
+ }
+ l, err = handle1.LinkByName(ifName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.XfrmPolicyDel(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle2.XfrmPolicyDel(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle1.XfrmStateDel(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = handle2.XfrmStateDel(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestHandleParallel1(t *testing.T) {
+ runParallelTests(t, 1)
+}
+
+func TestHandleParallel2(t *testing.T) {
+ runParallelTests(t, 2)
+}
+
+func TestHandleParallel3(t *testing.T) {
+ runParallelTests(t, 3)
+}
+
+func TestHandleParallel4(t *testing.T) {
+ runParallelTests(t, 4)
+}
}
}
+func (h *Handle) LinkSetARPOff(link Link) error {
+ base := link.Attrs()
+ h.ensureIndex(base)
+ req := h.newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
+
+ msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
+ msg.Change |= syscall.IFF_NOARP
+ msg.Flags |= syscall.IFF_NOARP
+ msg.Index = int32(base.Index)
+ req.AddData(msg)
+
+ _, err := req.Execute(syscall.NETLINK_ROUTE, 0)
+ return err
+}
+
+func LinkSetARPOff(link Link) error {
+ return pkgHandle.LinkSetARPOff(link)
+}
+
+func (h *Handle) LinkSetARPOn(link Link) error {
+ base := link.Attrs()
+ h.ensureIndex(base)
+ req := h.newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
+
+ msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
+ msg.Change |= syscall.IFF_NOARP
+ msg.Flags &= ^uint32(syscall.IFF_NOARP)
+ msg.Index = int32(base.Index)
+ req.AddData(msg)
+
+ _, err := req.Execute(syscall.NETLINK_ROUTE, 0)
+ return err
+}
+
+func LinkSetARPOn(link Link) error {
+ return pkgHandle.LinkSetARPOn(link)
+}
+
func (h *Handle) SetPromiscOn(link Link) error {
base := link.Attrs()
h.ensureIndex(base)
msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_PROMISC
- msg.Flags = syscall.IFF_UP
+ msg.Flags = syscall.IFF_PROMISC
msg.Index = int32(base.Index)
req.AddData(msg)
msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_PROMISC
- msg.Flags = 0 & ^syscall.IFF_UP
+ msg.Flags = 0 & ^syscall.IFF_PROMISC
msg.Index = int32(base.Index)
req.AddData(msg)
return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_UNICAST_FLOOD)
}
+func LinkSetBrProxyArp(link Link, mode bool) error {
+ return pkgHandle.LinkSetBrProxyArp(link, mode)
+}
+
+func (h *Handle) LinkSetBrProxyArp(link Link, mode bool) error {
+ return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_PROXYARP)
+}
+
+func LinkSetBrProxyArpWiFi(link Link, mode bool) error {
+ return pkgHandle.LinkSetBrProxyArpWiFi(link, mode)
+}
+
+func (h *Handle) LinkSetBrProxyArpWiFi(link Link, mode bool) error {
+ return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_PROXYARP_WIFI)
+}
+
func (h *Handle) setProtinfoAttr(link Link, mode bool, attr int) error {
base := link.Attrs()
h.ensureIndex(base)
}
func parseBondData(link Link, data []syscall.NetlinkRouteAttr) {
- bond := NewLinkBond(NewLinkAttrs())
+ bond := link.(*Bond)
for i := range data {
switch data[i].Attr.Type {
case nl.IFLA_BOND_MODE:
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "bytes"
+ "net"
+ "os"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/vishvananda/netns"
+)
+
+const (
+ testTxQLen int = 100
+ defaultTxQLen int = 1000
+)
+
+func testLinkAddDel(t *testing.T, link Link) {
+ links, err := LinkList()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkAdd(link); err != nil {
+ t.Fatal(err)
+ }
+
+ base := link.Attrs()
+
+ result, err := LinkByName(base.Name)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rBase := result.Attrs()
+
+ if vlan, ok := link.(*Vlan); ok {
+ other, ok := result.(*Vlan)
+ if !ok {
+ t.Fatal("Result of create is not a vlan")
+ }
+ if vlan.VlanId != other.VlanId {
+ t.Fatal("Link.VlanId id doesn't match")
+ }
+ }
+
+ if veth, ok := result.(*Veth); ok {
+ if rBase.TxQLen != base.TxQLen {
+ t.Fatalf("qlen is %d, should be %d", rBase.TxQLen, base.TxQLen)
+ }
+ if rBase.MTU != base.MTU {
+ t.Fatalf("MTU is %d, should be %d", rBase.MTU, base.MTU)
+ }
+
+ if original, ok := link.(*Veth); ok {
+ if original.PeerName != "" {
+ var peer *Veth
+ other, err := LinkByName(original.PeerName)
+ if err != nil {
+ t.Fatalf("Peer %s not created", veth.PeerName)
+ }
+ if peer, ok = other.(*Veth); !ok {
+ t.Fatalf("Peer %s is incorrect type", veth.PeerName)
+ }
+ if peer.TxQLen != testTxQLen {
+ t.Fatalf("TxQLen of peer is %d, should be %d", peer.TxQLen, testTxQLen)
+ }
+ }
+ }
+ } else {
+ // recent kernels set the parent index for veths in the response
+ if rBase.ParentIndex == 0 && base.ParentIndex != 0 {
+ t.Fatalf("Created link doesn't have parent %d but it should", base.ParentIndex)
+ } else if rBase.ParentIndex != 0 && base.ParentIndex == 0 {
+ t.Fatalf("Created link has parent %d but it shouldn't", rBase.ParentIndex)
+ } else if rBase.ParentIndex != 0 && base.ParentIndex != 0 {
+ if rBase.ParentIndex != base.ParentIndex {
+ t.Fatalf("Link.ParentIndex doesn't match %d != %d", rBase.ParentIndex, base.ParentIndex)
+ }
+ }
+ }
+
+ if vxlan, ok := link.(*Vxlan); ok {
+ other, ok := result.(*Vxlan)
+ if !ok {
+ t.Fatal("Result of create is not a vxlan")
+ }
+ compareVxlan(t, vxlan, other)
+ }
+
+ if ipv, ok := link.(*IPVlan); ok {
+ other, ok := result.(*IPVlan)
+ if !ok {
+ t.Fatal("Result of create is not a ipvlan")
+ }
+ if ipv.Mode != other.Mode {
+ t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, ipv.Mode)
+ }
+ }
+
+ if macv, ok := link.(*Macvlan); ok {
+ other, ok := result.(*Macvlan)
+ if !ok {
+ t.Fatal("Result of create is not a macvlan")
+ }
+ if macv.Mode != other.Mode {
+ t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, macv.Mode)
+ }
+ }
+
+ if macv, ok := link.(*Macvtap); ok {
+ other, ok := result.(*Macvtap)
+ if !ok {
+ t.Fatal("Result of create is not a macvtap")
+ }
+ if macv.Mode != other.Mode {
+ t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, macv.Mode)
+ }
+ }
+
+ if _, ok := link.(*Vti); ok {
+ _, ok := result.(*Vti)
+ if !ok {
+ t.Fatal("Result of create is not a vti")
+ }
+ }
+
+ if bond, ok := link.(*Bond); ok {
+ other, ok := result.(*Bond)
+ if !ok {
+ t.Fatal("Result of create is not a bond")
+ }
+ if bond.Mode != other.Mode {
+ t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, bond.Mode)
+ }
+ }
+
+ if _, ok := link.(*Iptun); ok {
+ _, ok := result.(*Iptun)
+ if !ok {
+ t.Fatal("Result of create is not a iptun")
+ }
+ }
+
+ if err = LinkDel(link); err != nil {
+ t.Fatal(err)
+ }
+
+ links, err = LinkList()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, l := range links {
+ if l.Attrs().Name == link.Attrs().Name {
+ t.Fatal("Link not removed properly")
+ }
+ }
+}
+
+func compareVxlan(t *testing.T, expected, actual *Vxlan) {
+
+ if actual.VxlanId != expected.VxlanId {
+ t.Fatal("Vxlan.VxlanId doesn't match")
+ }
+ if expected.SrcAddr != nil && !actual.SrcAddr.Equal(expected.SrcAddr) {
+ t.Fatal("Vxlan.SrcAddr doesn't match")
+ }
+ if expected.Group != nil && !actual.Group.Equal(expected.Group) {
+ t.Fatal("Vxlan.Group doesn't match")
+ }
+ if expected.TTL != -1 && actual.TTL != expected.TTL {
+ t.Fatal("Vxlan.TTL doesn't match")
+ }
+ if expected.TOS != -1 && actual.TOS != expected.TOS {
+ t.Fatal("Vxlan.TOS doesn't match")
+ }
+ if actual.Learning != expected.Learning {
+ t.Fatal("Vxlan.Learning doesn't match")
+ }
+ if actual.Proxy != expected.Proxy {
+ t.Fatal("Vxlan.Proxy doesn't match")
+ }
+ if actual.RSC != expected.RSC {
+ t.Fatal("Vxlan.RSC doesn't match")
+ }
+ if actual.L2miss != expected.L2miss {
+ t.Fatal("Vxlan.L2miss doesn't match")
+ }
+ if actual.L3miss != expected.L3miss {
+ t.Fatal("Vxlan.L3miss doesn't match")
+ }
+ if actual.GBP != expected.GBP {
+ t.Fatal("Vxlan.GBP doesn't match")
+ }
+ if expected.NoAge {
+ if !actual.NoAge {
+ t.Fatal("Vxlan.NoAge doesn't match")
+ }
+ } else if expected.Age > 0 && actual.Age != expected.Age {
+ t.Fatal("Vxlan.Age doesn't match")
+ }
+ if expected.Limit > 0 && actual.Limit != expected.Limit {
+ t.Fatal("Vxlan.Limit doesn't match")
+ }
+ if expected.Port > 0 && actual.Port != expected.Port {
+ t.Fatal("Vxlan.Port doesn't match")
+ }
+ if expected.PortLow > 0 || expected.PortHigh > 0 {
+ if actual.PortLow != expected.PortLow {
+ t.Fatal("Vxlan.PortLow doesn't match")
+ }
+ if actual.PortHigh != expected.PortHigh {
+ t.Fatal("Vxlan.PortHigh doesn't match")
+ }
+ }
+}
+
+func TestLinkAddDelDummy(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Dummy{LinkAttrs{Name: "foo"}})
+}
+
+func TestLinkAddDelIfb(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Ifb{LinkAttrs{Name: "foo"}})
+}
+
+func TestLinkAddDelBridge(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Bridge{LinkAttrs{Name: "foo", MTU: 1400}})
+}
+
+func TestLinkAddDelGretap(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Gretap{
+ LinkAttrs: LinkAttrs{Name: "foo"},
+ IKey: 0x101,
+ OKey: 0x101,
+ PMtuDisc: 1,
+ Local: net.IPv4(127, 0, 0, 1),
+ Remote: net.IPv4(127, 0, 0, 1)})
+}
+
+func TestLinkAddDelVlan(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ parent := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ testLinkAddDel(t, &Vlan{LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, 900})
+
+ if err := LinkDel(parent); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelMacvlan(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ parent := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ testLinkAddDel(t, &Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_PRIVATE,
+ })
+
+ testLinkAddDel(t, &Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_BRIDGE,
+ })
+
+ testLinkAddDel(t, &Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_VEPA,
+ })
+
+ if err := LinkDel(parent); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelMacvtap(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ parent := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ testLinkAddDel(t, &Macvtap{
+ Macvlan: Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_PRIVATE,
+ },
+ })
+
+ testLinkAddDel(t, &Macvtap{
+ Macvlan: Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_BRIDGE,
+ },
+ })
+
+ testLinkAddDel(t, &Macvtap{
+ Macvlan: Macvlan{
+ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index},
+ Mode: MACVLAN_MODE_VEPA,
+ },
+ })
+
+ if err := LinkDel(parent); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelVeth(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ veth := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1400}, PeerName: "bar"}
+ testLinkAddDel(t, veth)
+}
+
+func TestLinkAddDelBond(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ bond := NewLinkBond(LinkAttrs{Name: "foo"})
+ bond.Mode = StringToBondModeMap["802.3ad"]
+ testLinkAddDel(t, bond)
+}
+
+func TestLinkAddVethWithDefaultTxQLen(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ la := NewLinkAttrs()
+ la.Name = "foo"
+
+ veth := &Veth{LinkAttrs: la, PeerName: "bar"}
+ if err := LinkAdd(veth); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if veth, ok := link.(*Veth); !ok {
+ t.Fatalf("unexpected link type: %T", link)
+ } else {
+ if veth.TxQLen != defaultTxQLen {
+ t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, defaultTxQLen)
+ }
+ }
+ peer, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if veth, ok := peer.(*Veth); !ok {
+ t.Fatalf("unexpected link type: %T", link)
+ } else {
+ if veth.TxQLen != defaultTxQLen {
+ t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, defaultTxQLen)
+ }
+ }
+}
+
+func TestLinkAddVethWithZeroTxQLen(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ la := NewLinkAttrs()
+ la.Name = "foo"
+ la.TxQLen = 0
+
+ veth := &Veth{LinkAttrs: la, PeerName: "bar"}
+ if err := LinkAdd(veth); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if veth, ok := link.(*Veth); !ok {
+ t.Fatalf("unexpected link type: %T", link)
+ } else {
+ if veth.TxQLen != 0 {
+ t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, 0)
+ }
+ }
+ peer, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if veth, ok := peer.(*Veth); !ok {
+ t.Fatalf("unexpected link type: %T", link)
+ } else {
+ if veth.TxQLen != 0 {
+ t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, 0)
+ }
+ }
+}
+
+func TestLinkAddDummyWithTxQLen(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ la := NewLinkAttrs()
+ la.Name = "foo"
+ la.TxQLen = 1500
+
+ dummy := &Dummy{LinkAttrs: la}
+ if err := LinkAdd(dummy); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if dummy, ok := link.(*Dummy); !ok {
+ t.Fatalf("unexpected link type: %T", link)
+ } else {
+ if dummy.TxQLen != 1500 {
+ t.Fatalf("TxQLen is %d, should be %d", dummy.TxQLen, 1500)
+ }
+ }
+}
+
+func TestLinkAddDelBridgeMaster(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ master := &Bridge{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(master); err != nil {
+ t.Fatal(err)
+ }
+ testLinkAddDel(t, &Dummy{LinkAttrs{Name: "bar", MasterIndex: master.Attrs().Index}})
+
+ if err := LinkDel(master); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkSetUnsetResetMaster(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ master := &Bridge{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(master); err != nil {
+ t.Fatal(err)
+ }
+
+ newmaster := &Bridge{LinkAttrs{Name: "bar"}}
+ if err := LinkAdd(newmaster); err != nil {
+ t.Fatal(err)
+ }
+
+ slave := &Dummy{LinkAttrs{Name: "baz"}}
+ if err := LinkAdd(slave); err != nil {
+ t.Fatal(err)
+ }
+
+ nonexistsmaster := &Bridge{LinkAttrs{Name: "foobar"}}
+
+ if err := LinkSetMaster(slave, nonexistsmaster); err == nil {
+ t.Fatal("error expected")
+ }
+
+ if err := LinkSetMaster(slave, master); err != nil {
+ t.Fatal(err)
+ }
+
+ link, err := LinkByName("baz")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().MasterIndex != master.Attrs().Index {
+ t.Fatal("Master not set properly")
+ }
+
+ if err := LinkSetMaster(slave, newmaster); err != nil {
+ t.Fatal(err)
+ }
+
+ link, err = LinkByName("baz")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().MasterIndex != newmaster.Attrs().Index {
+ t.Fatal("Master not reset properly")
+ }
+
+ if err := LinkSetNoMaster(slave); err != nil {
+ t.Fatal(err)
+ }
+
+ link, err = LinkByName("baz")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().MasterIndex != 0 {
+ t.Fatal("Master not unset properly")
+ }
+ if err := LinkDel(slave); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkDel(newmaster); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkDel(master); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkSetNs(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ basens, err := netns.Get()
+ if err != nil {
+ t.Fatal("Failed to get basens")
+ }
+ defer basens.Close()
+
+ newns, err := netns.New()
+ if err != nil {
+ t.Fatal("Failed to create newns")
+ }
+ defer newns.Close()
+
+ link := &Veth{LinkAttrs{Name: "foo"}, "bar"}
+ if err := LinkAdd(link); err != nil {
+ t.Fatal(err)
+ }
+
+ peer, err := LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ LinkSetNsFd(peer, int(basens))
+ if err != nil {
+ t.Fatal("Failed to set newns for link")
+ }
+
+ _, err = LinkByName("bar")
+ if err == nil {
+ t.Fatal("Link bar is still in newns")
+ }
+
+ err = netns.Set(basens)
+ if err != nil {
+ t.Fatal("Failed to set basens")
+ }
+
+ peer, err = LinkByName("bar")
+ if err != nil {
+ t.Fatal("Link is not in basens")
+ }
+
+ if err := LinkDel(peer); err != nil {
+ t.Fatal(err)
+ }
+
+ err = netns.Set(newns)
+ if err != nil {
+ t.Fatal("Failed to set newns")
+ }
+
+ _, err = LinkByName("foo")
+ if err == nil {
+ t.Fatal("Other half of veth pair not deleted")
+ }
+
+}
+
+func TestLinkAddDelVxlan(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ parent := &Dummy{
+ LinkAttrs{Name: "foo"},
+ }
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ vxlan := Vxlan{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ },
+ VxlanId: 10,
+ VtepDevIndex: parent.Index,
+ Learning: true,
+ L2miss: true,
+ L3miss: true,
+ }
+
+ testLinkAddDel(t, &vxlan)
+ if err := LinkDel(parent); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelVxlanGbp(t *testing.T) {
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Kernel in travis is too old for this test")
+ }
+
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ parent := &Dummy{
+ LinkAttrs{Name: "foo"},
+ }
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ vxlan := Vxlan{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ },
+ VxlanId: 10,
+ VtepDevIndex: parent.Index,
+ Learning: true,
+ L2miss: true,
+ L3miss: true,
+ GBP: true,
+ }
+
+ testLinkAddDel(t, &vxlan)
+ if err := LinkDel(parent); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelIPVlanL2(t *testing.T) {
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Kernel in travis is too old for this test")
+ }
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ parent := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ ipv := IPVlan{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ ParentIndex: parent.Index,
+ },
+ Mode: IPVLAN_MODE_L2,
+ }
+
+ testLinkAddDel(t, &ipv)
+}
+
+func TestLinkAddDelIPVlanL3(t *testing.T) {
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Kernel in travis is too old for this test")
+ }
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ parent := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(parent); err != nil {
+ t.Fatal(err)
+ }
+
+ ipv := IPVlan{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ ParentIndex: parent.Index,
+ },
+ Mode: IPVLAN_MODE_L3,
+ }
+
+ testLinkAddDel(t, &ipv)
+}
+
+func TestLinkAddDelIPVlanNoParent(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ ipv := IPVlan{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ },
+ Mode: IPVLAN_MODE_L3,
+ }
+ err := LinkAdd(&ipv)
+ if err == nil {
+ t.Fatal("Add should fail if ipvlan creating without ParentIndex")
+ }
+ if err.Error() != "Can't create ipvlan link without ParentIndex" {
+ t.Fatalf("Error should be about missing ParentIndex, got %q", err)
+ }
+}
+
+func TestLinkByIndex(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ dummy := &Dummy{LinkAttrs{Name: "dummy"}}
+ if err := LinkAdd(dummy); err != nil {
+ t.Fatal(err)
+ }
+
+ found, err := LinkByIndex(dummy.Index)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if found.Attrs().Index != dummy.Attrs().Index {
+ t.Fatalf("Indices don't match: %v != %v", found.Attrs().Index, dummy.Attrs().Index)
+ }
+
+ LinkDel(dummy)
+
+ // test not found
+ _, err = LinkByIndex(dummy.Attrs().Index)
+ if err == nil {
+ t.Fatalf("LinkByIndex(%v) found deleted link", err)
+ }
+}
+
+func TestLinkSet(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ iface := &Dummy{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(iface); err != nil {
+ t.Fatal(err)
+ }
+
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = LinkSetName(link, "bar")
+ if err != nil {
+ t.Fatalf("Could not change interface name: %v", err)
+ }
+
+ link, err = LinkByName("bar")
+ if err != nil {
+ t.Fatalf("Interface name not changed: %v", err)
+ }
+
+ err = LinkSetMTU(link, 1400)
+ if err != nil {
+ t.Fatalf("Could not set MTU: %v", err)
+ }
+
+ link, err = LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().MTU != 1400 {
+ t.Fatal("MTU not changed!")
+ }
+
+ addr, err := net.ParseMAC("00:12:34:56:78:AB")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = LinkSetHardwareAddr(link, addr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link, err = LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(link.Attrs().HardwareAddr, addr) {
+ t.Fatalf("hardware address not changed!")
+ }
+
+ err = LinkSetAlias(link, "barAlias")
+ if err != nil {
+ t.Fatalf("Could not set alias: %v", err)
+ }
+
+ link, err = LinkByName("bar")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().Alias != "barAlias" {
+ t.Fatalf("alias not changed!")
+ }
+
+ link, err = LinkByAlias("barAlias")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkSetARP(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "banana"}
+ if err := LinkAdd(iface); err != nil {
+ t.Fatal(err)
+ }
+
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = LinkSetARPOff(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link, err = LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().RawFlags&syscall.IFF_NOARP != uint32(syscall.IFF_NOARP) {
+ t.Fatalf("NOARP was not set!")
+ }
+
+ err = LinkSetARPOn(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link, err = LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if link.Attrs().RawFlags&syscall.IFF_NOARP != 0 {
+ t.Fatalf("NOARP is still set!")
+ }
+}
+
+func expectLinkUpdate(ch <-chan LinkUpdate, ifaceName string, up bool) bool {
+ for {
+ timeout := time.After(time.Minute)
+ select {
+ case update := <-ch:
+ if ifaceName == update.Link.Attrs().Name && (update.IfInfomsg.Flags&syscall.IFF_UP != 0) == up {
+ return true
+ }
+ case <-timeout:
+ return false
+ }
+ }
+}
+
+func TestLinkSubscribe(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ ch := make(chan LinkUpdate)
+ done := make(chan struct{})
+ defer close(done)
+ if err := LinkSubscribe(ch, done); err != nil {
+ t.Fatal(err)
+ }
+
+ link := &Veth{LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1400}, "bar"}
+ if err := LinkAdd(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "foo", false) {
+ t.Fatal("Add update not received as expected")
+ }
+
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "foo", true) {
+ t.Fatal("Link Up update not received as expected")
+ }
+
+ if err := LinkDel(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "foo", false) {
+ t.Fatal("Del update not received as expected")
+ }
+}
+
+func TestLinkSubscribeAt(t *testing.T) {
+ skipUnlessRoot(t)
+
+ // Create an handle on a custom netns
+ newNs, err := netns.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer newNs.Close()
+
+ nh, err := NewHandleAt(newNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ // Subscribe for Link events on the custom netns
+ ch := make(chan LinkUpdate)
+ done := make(chan struct{})
+ defer close(done)
+ if err := LinkSubscribeAt(newNs, ch, done); err != nil {
+ t.Fatal(err)
+ }
+
+ link := &Veth{LinkAttrs{Name: "test", TxQLen: testTxQLen, MTU: 1400}, "bar"}
+ if err := nh.LinkAdd(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "test", false) {
+ t.Fatal("Add update not received as expected")
+ }
+
+ if err := nh.LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "test", true) {
+ t.Fatal("Link Up update not received as expected")
+ }
+
+ if err := nh.LinkDel(link); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectLinkUpdate(ch, "test", false) {
+ t.Fatal("Del update not received as expected")
+ }
+}
+
+func TestLinkStats(t *testing.T) {
+ defer setUpNetlinkTest(t)()
+
+ // Create a veth pair and verify the cross-stats once both
+ // ends are brought up and some ICMPv6 packets are exchanged
+ v0 := "v0"
+ v1 := "v1"
+
+ vethLink := &Veth{LinkAttrs: LinkAttrs{Name: v0}, PeerName: v1}
+ if err := LinkAdd(vethLink); err != nil {
+ t.Fatal(err)
+ }
+
+ veth0, err := LinkByName(v0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(veth0); err != nil {
+ t.Fatal(err)
+ }
+
+ veth1, err := LinkByName(v1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(veth1); err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(2 * time.Second)
+
+ // verify statistics
+ veth0, err = LinkByName(v0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ veth1, err = LinkByName(v1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ v0Stats := veth0.Attrs().Statistics
+ v1Stats := veth1.Attrs().Statistics
+ if v0Stats.RxPackets != v1Stats.TxPackets || v0Stats.TxPackets != v1Stats.RxPackets ||
+ v0Stats.RxBytes != v1Stats.TxBytes || v0Stats.TxBytes != v1Stats.RxBytes {
+ t.Fatalf("veth ends counters differ:\n%v\n%v", v0Stats, v1Stats)
+ }
+}
+
+func TestLinkXdp(t *testing.T) {
+ links, err := LinkList()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var testXdpLink Link
+ for _, link := range links {
+ if link.Attrs().Xdp != nil && !link.Attrs().Xdp.Attached {
+ testXdpLink = link
+ break
+ }
+ }
+ if testXdpLink == nil {
+ t.Skipf("No link supporting XDP found")
+ }
+ fd, err := loadSimpleBpf(BPF_PROG_TYPE_XDP, 2 /*XDP_PASS*/)
+ if err != nil {
+ t.Skipf("Loading bpf program failed: %s", err)
+ }
+ if err := LinkSetXdpFd(testXdpLink, fd); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetXdpFd(testXdpLink, -1); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestLinkAddDelIptun(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Iptun{
+ LinkAttrs: LinkAttrs{Name: "iptunfoo"},
+ PMtuDisc: 1,
+ Local: net.IPv4(127, 0, 0, 1),
+ Remote: net.IPv4(127, 0, 0, 1)})
+}
+
+func TestLinkAddDelVti(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ testLinkAddDel(t, &Vti{
+ LinkAttrs: LinkAttrs{Name: "vtifoo"},
+ IKey: 0x101,
+ OKey: 0x101,
+ Local: net.IPv4(127, 0, 0, 1),
+ Remote: net.IPv4(127, 0, 0, 1)})
+}
+
+func TestLinkSubscribeWithProtinfo(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ master := &Bridge{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(master); err != nil {
+ t.Fatal(err)
+ }
+
+ slave := &Veth{
+ LinkAttrs: LinkAttrs{
+ Name: "bar",
+ TxQLen: testTxQLen,
+ MTU: 1400,
+ MasterIndex: master.Attrs().Index,
+ },
+ PeerName: "bar-peer",
+ }
+ if err := LinkAdd(slave); err != nil {
+ t.Fatal(err)
+ }
+
+ ch := make(chan LinkUpdate)
+ done := make(chan struct{})
+ defer close(done)
+ if err := LinkSubscribe(ch, done); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkSetHairpin(slave, true); err != nil {
+ t.Fatal(err)
+ }
+
+ select {
+ case update := <-ch:
+ if !(update.Attrs().Name == "bar" && update.Attrs().Protinfo != nil &&
+ update.Attrs().Protinfo.Hairpin) {
+ t.Fatal("Hairpin update not received as expected")
+ }
+ case <-time.After(time.Minute):
+ t.Fatal("Hairpin update timed out")
+ }
+
+ if err := LinkDel(slave); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkDel(master); err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "net"
+ "testing"
+)
+
+type arpEntry struct {
+ ip net.IP
+ mac net.HardwareAddr
+}
+
+type proxyEntry struct {
+ ip net.IP
+ dev int
+}
+
+func parseMAC(s string) net.HardwareAddr {
+ m, err := net.ParseMAC(s)
+ if err != nil {
+ panic(err)
+ }
+ return m
+}
+
+func dumpContains(dump []Neigh, e arpEntry) bool {
+ for _, n := range dump {
+ if n.IP.Equal(e.ip) && (n.State&NUD_INCOMPLETE) == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func dumpContainsProxy(dump []Neigh, p proxyEntry) bool {
+ for _, n := range dump {
+ if n.IP.Equal(p.ip) && (n.LinkIndex == p.dev) && (n.Flags&NTF_PROXY) == NTF_PROXY {
+ return true
+ }
+ }
+ return false
+}
+
+func TestNeighAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ dummy := Dummy{LinkAttrs{Name: "neigh0"}}
+ if err := LinkAdd(&dummy); err != nil {
+ t.Fatal(err)
+ }
+
+ ensureIndex(dummy.Attrs())
+
+ arpTable := []arpEntry{
+ {net.ParseIP("10.99.0.1"), parseMAC("aa:bb:cc:dd:00:01")},
+ {net.ParseIP("10.99.0.2"), parseMAC("aa:bb:cc:dd:00:02")},
+ {net.ParseIP("10.99.0.3"), parseMAC("aa:bb:cc:dd:00:03")},
+ {net.ParseIP("10.99.0.4"), parseMAC("aa:bb:cc:dd:00:04")},
+ {net.ParseIP("10.99.0.5"), parseMAC("aa:bb:cc:dd:00:05")},
+ }
+
+ // Add the arpTable
+ for _, entry := range arpTable {
+ err := NeighAdd(&Neigh{
+ LinkIndex: dummy.Index,
+ State: NUD_REACHABLE,
+ IP: entry.ip,
+ HardwareAddr: entry.mac,
+ })
+
+ if err != nil {
+ t.Errorf("Failed to NeighAdd: %v", err)
+ }
+ }
+
+ // Dump and see that all added entries are there
+ dump, err := NeighList(dummy.Index, 0)
+ if err != nil {
+ t.Errorf("Failed to NeighList: %v", err)
+ }
+
+ for _, entry := range arpTable {
+ if !dumpContains(dump, entry) {
+ t.Errorf("Dump does not contain: %v", entry)
+ }
+ }
+
+ // Delete the arpTable
+ for _, entry := range arpTable {
+ err := NeighDel(&Neigh{
+ LinkIndex: dummy.Index,
+ IP: entry.ip,
+ HardwareAddr: entry.mac,
+ })
+
+ if err != nil {
+ t.Errorf("Failed to NeighDel: %v", err)
+ }
+ }
+
+ // TODO: seems not working because of cache
+ //// Dump and see that none of deleted entries are there
+ //dump, err = NeighList(dummy.Index, 0)
+ //if err != nil {
+ //t.Errorf("Failed to NeighList: %v", err)
+ //}
+
+ //for _, entry := range arpTable {
+ //if dumpContains(dump, entry) {
+ //t.Errorf("Dump contains: %v", entry)
+ //}
+ //}
+
+ if err := LinkDel(&dummy); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestNeighAddDelProxy(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ dummy := Dummy{LinkAttrs{Name: "neigh0"}}
+ if err := LinkAdd(&dummy); err != nil {
+ t.Fatal(err)
+ }
+
+ ensureIndex(dummy.Attrs())
+
+ proxyTable := []proxyEntry{
+ {net.ParseIP("10.99.0.1"), dummy.Index},
+ {net.ParseIP("10.99.0.2"), dummy.Index},
+ {net.ParseIP("10.99.0.3"), dummy.Index},
+ {net.ParseIP("10.99.0.4"), dummy.Index},
+ {net.ParseIP("10.99.0.5"), dummy.Index},
+ }
+
+ // Add the proxyTable
+ for _, entry := range proxyTable {
+ err := NeighAdd(&Neigh{
+ LinkIndex: dummy.Index,
+ Flags: NTF_PROXY,
+ IP: entry.ip,
+ })
+
+ if err != nil {
+ t.Errorf("Failed to NeighAdd: %v", err)
+ }
+ }
+
+ // Dump and see that all added entries are there
+ dump, err := NeighProxyList(dummy.Index, 0)
+ if err != nil {
+ t.Errorf("Failed to NeighList: %v", err)
+ }
+
+ for _, entry := range proxyTable {
+ if !dumpContainsProxy(dump, entry) {
+ t.Errorf("Dump does not contain: %v", entry)
+ }
+ }
+
+ // Delete the proxyTable
+ for _, entry := range proxyTable {
+ err := NeighDel(&Neigh{
+ LinkIndex: dummy.Index,
+ Flags: NTF_PROXY,
+ IP: entry.ip,
+ })
+
+ if err != nil {
+ t.Errorf("Failed to NeighDel: %v", err)
+ }
+ }
+
+ // Dump and see that none of deleted entries are there
+ dump, err = NeighProxyList(dummy.Index, 0)
+ if err != nil {
+ t.Errorf("Failed to NeighList: %v", err)
+ }
+
+ for _, entry := range proxyTable {
+ if dumpContainsProxy(dump, entry) {
+ t.Errorf("Dump contains: %v", entry)
+ }
+ }
+
+ if err := LinkDel(&dummy); err != nil {
+ t.Fatal(err)
+ }
+}
--- /dev/null
+package netlink
+
+import (
+ "log"
+ "os"
+ "runtime"
+ "testing"
+
+ "github.com/vishvananda/netns"
+)
+
+type tearDownNetlinkTest func()
+
+func skipUnlessRoot(t *testing.T) {
+ if os.Getuid() != 0 {
+ msg := "Skipped test because it requires root privileges."
+ log.Printf(msg)
+ t.Skip(msg)
+ }
+}
+
+func setUpNetlinkTest(t *testing.T) tearDownNetlinkTest {
+ skipUnlessRoot(t)
+
+ // new temporary namespace so we don't pollute the host
+ // lock thread since the namespace is thread local
+ runtime.LockOSThread()
+ var err error
+ ns, err := netns.New()
+ if err != nil {
+ t.Fatal("Failed to create newns", ns)
+ }
+
+ return func() {
+ ns.Close()
+ runtime.UnlockOSThread()
+ }
+}
+
+func setUpMPLSNetlinkTest(t *testing.T) tearDownNetlinkTest {
+ if _, err := os.Stat("/proc/sys/net/mpls/platform_labels"); err != nil {
+ msg := "Skipped test because it requires MPLS support."
+ log.Printf(msg)
+ t.Skip(msg)
+ }
+ f := setUpNetlinkTest(t)
+ setUpF := func(path, value string) {
+ file, err := os.Create(path)
+ defer file.Close()
+ if err != nil {
+ t.Fatalf("Failed to open %s: %s", path, err)
+ }
+ file.WriteString(value)
+ }
+ setUpF("/proc/sys/net/mpls/platform_labels", "1024")
+ setUpF("/proc/sys/net/mpls/conf/lo/input", "1")
+ return f
+}
return ErrNotImplemented
}
-func LinkSetMaster(link Link, master *Link) error {
+func LinkSetMaster(link Link, master *Bridge) error {
return ErrNotImplemented
}
return ErrNotImplemented
}
+func LinkSetARPOff(link Link) error {
+ return ErrNotImplemented
+}
+
+func LinkSetARPOn(link Link) error {
+ return ErrNotImplemented
+}
+
func LinkByName(name string) (Link, error) {
return nil, ErrNotImplemented
}
func (msg *IfAddrmsg) Len() int {
return syscall.SizeofIfAddrmsg
}
+
+// struct ifa_cacheinfo {
+// __u32 ifa_prefered;
+// __u32 ifa_valid;
+// __u32 cstamp; /* created timestamp, hundredths of seconds */
+// __u32 tstamp; /* updated timestamp, hundredths of seconds */
+// };
+
+const IFA_CACHEINFO = 6
+const SizeofIfaCacheInfo = 0x10
+
+type IfaCacheInfo struct {
+ IfaPrefered uint32
+ IfaValid uint32
+ Cstamp uint32
+ Tstamp uint32
+}
+
+func (msg *IfaCacheInfo) Len() int {
+ return SizeofIfaCacheInfo
+}
+
+func DeserializeIfaCacheInfo(b []byte) *IfaCacheInfo {
+ return (*IfaCacheInfo)(unsafe.Pointer(&b[0:SizeofIfaCacheInfo][0]))
+}
+
+func (msg *IfaCacheInfo) Serialize() []byte {
+ return (*(*[SizeofIfaCacheInfo]byte)(unsafe.Pointer(msg)))[:]
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "syscall"
+ "testing"
+)
+
+func (msg *IfAddrmsg) write(b []byte) {
+ native := NativeEndian()
+ b[0] = msg.Family
+ b[1] = msg.Prefixlen
+ b[2] = msg.Flags
+ b[3] = msg.Scope
+ native.PutUint32(b[4:8], msg.Index)
+}
+
+func (msg *IfAddrmsg) serializeSafe() []byte {
+ len := syscall.SizeofIfAddrmsg
+ b := make([]byte, len)
+ msg.write(b)
+ return b
+}
+
+func deserializeIfAddrmsgSafe(b []byte) *IfAddrmsg {
+ var msg = IfAddrmsg{}
+ binary.Read(bytes.NewReader(b[0:syscall.SizeofIfAddrmsg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestIfAddrmsgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, syscall.SizeofIfAddrmsg)
+ rand.Read(orig)
+ safemsg := deserializeIfAddrmsgSafe(orig)
+ msg := DeserializeIfAddrmsg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *IfaCacheInfo) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.IfaPrefered))
+ native.PutUint32(b[4:8], uint32(msg.IfaValid))
+ native.PutUint32(b[8:12], uint32(msg.Cstamp))
+ native.PutUint32(b[12:16], uint32(msg.Tstamp))
+}
+
+func (msg *IfaCacheInfo) serializeSafe() []byte {
+ length := SizeofIfaCacheInfo
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeIfaCacheInfoSafe(b []byte) *IfaCacheInfo {
+ var msg = IfaCacheInfo{}
+ binary.Read(bytes.NewReader(b[0:SizeofIfaCacheInfo]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestIfaCacheInfoDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofIfaCacheInfo)
+ rand.Read(orig)
+ safemsg := deserializeIfaCacheInfoSafe(orig)
+ msg := DeserializeIfaCacheInfo(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import "unsafe"
+
+// Track the message sizes for the correct serialization/deserialization
+const (
+ SizeofNfgenmsg = 4
+ SizeofNfattr = 4
+ SizeofNfConntrack = 376
+ SizeofNfctTupleHead = 52
+)
+
+var L4ProtoMap = map[uint8]string{
+ 6: "tcp",
+ 17: "udp",
+}
+
+// All the following constants are coming from:
+// https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+
+// enum cntl_msg_types {
+// IPCTNL_MSG_CT_NEW,
+// IPCTNL_MSG_CT_GET,
+// IPCTNL_MSG_CT_DELETE,
+// IPCTNL_MSG_CT_GET_CTRZERO,
+// IPCTNL_MSG_CT_GET_STATS_CPU,
+// IPCTNL_MSG_CT_GET_STATS,
+// IPCTNL_MSG_CT_GET_DYING,
+// IPCTNL_MSG_CT_GET_UNCONFIRMED,
+//
+// IPCTNL_MSG_MAX
+// };
+const (
+ IPCTNL_MSG_CT_GET = 1
+ IPCTNL_MSG_CT_DELETE = 2
+)
+
+// #define NFNETLINK_V0 0
+const (
+ NFNETLINK_V0 = 0
+)
+
+// #define NLA_F_NESTED (1 << 15)
+const (
+ NLA_F_NESTED = (1 << 15)
+)
+
+// enum ctattr_type {
+// CTA_UNSPEC,
+// CTA_TUPLE_ORIG,
+// CTA_TUPLE_REPLY,
+// CTA_STATUS,
+// CTA_PROTOINFO,
+// CTA_HELP,
+// CTA_NAT_SRC,
+// #define CTA_NAT CTA_NAT_SRC /* backwards compatibility */
+// CTA_TIMEOUT,
+// CTA_MARK,
+// CTA_COUNTERS_ORIG,
+// CTA_COUNTERS_REPLY,
+// CTA_USE,
+// CTA_ID,
+// CTA_NAT_DST,
+// CTA_TUPLE_MASTER,
+// CTA_SEQ_ADJ_ORIG,
+// CTA_NAT_SEQ_ADJ_ORIG = CTA_SEQ_ADJ_ORIG,
+// CTA_SEQ_ADJ_REPLY,
+// CTA_NAT_SEQ_ADJ_REPLY = CTA_SEQ_ADJ_REPLY,
+// CTA_SECMARK, /* obsolete */
+// CTA_ZONE,
+// CTA_SECCTX,
+// CTA_TIMESTAMP,
+// CTA_MARK_MASK,
+// CTA_LABELS,
+// CTA_LABELS_MASK,
+// __CTA_MAX
+// };
+const (
+ CTA_TUPLE_ORIG = 1
+ CTA_TUPLE_REPLY = 2
+ CTA_STATUS = 3
+ CTA_TIMEOUT = 8
+ CTA_MARK = 9
+ CTA_PROTOINFO = 4
+)
+
+// enum ctattr_tuple {
+// CTA_TUPLE_UNSPEC,
+// CTA_TUPLE_IP,
+// CTA_TUPLE_PROTO,
+// CTA_TUPLE_ZONE,
+// __CTA_TUPLE_MAX
+// };
+// #define CTA_TUPLE_MAX (__CTA_TUPLE_MAX - 1)
+const (
+ CTA_TUPLE_IP = 1
+ CTA_TUPLE_PROTO = 2
+)
+
+// enum ctattr_ip {
+// CTA_IP_UNSPEC,
+// CTA_IP_V4_SRC,
+// CTA_IP_V4_DST,
+// CTA_IP_V6_SRC,
+// CTA_IP_V6_DST,
+// __CTA_IP_MAX
+// };
+// #define CTA_IP_MAX (__CTA_IP_MAX - 1)
+const (
+ CTA_IP_V4_SRC = 1
+ CTA_IP_V4_DST = 2
+ CTA_IP_V6_SRC = 3
+ CTA_IP_V6_DST = 4
+)
+
+// enum ctattr_l4proto {
+// CTA_PROTO_UNSPEC,
+// CTA_PROTO_NUM,
+// CTA_PROTO_SRC_PORT,
+// CTA_PROTO_DST_PORT,
+// CTA_PROTO_ICMP_ID,
+// CTA_PROTO_ICMP_TYPE,
+// CTA_PROTO_ICMP_CODE,
+// CTA_PROTO_ICMPV6_ID,
+// CTA_PROTO_ICMPV6_TYPE,
+// CTA_PROTO_ICMPV6_CODE,
+// __CTA_PROTO_MAX
+// };
+// #define CTA_PROTO_MAX (__CTA_PROTO_MAX - 1)
+const (
+ CTA_PROTO_NUM = 1
+ CTA_PROTO_SRC_PORT = 2
+ CTA_PROTO_DST_PORT = 3
+)
+
+// enum ctattr_protoinfo {
+// CTA_PROTOINFO_UNSPEC,
+// CTA_PROTOINFO_TCP,
+// CTA_PROTOINFO_DCCP,
+// CTA_PROTOINFO_SCTP,
+// __CTA_PROTOINFO_MAX
+// };
+// #define CTA_PROTOINFO_MAX (__CTA_PROTOINFO_MAX - 1)
+const (
+ CTA_PROTOINFO_TCP = 1
+)
+
+// enum ctattr_protoinfo_tcp {
+// CTA_PROTOINFO_TCP_UNSPEC,
+// CTA_PROTOINFO_TCP_STATE,
+// CTA_PROTOINFO_TCP_WSCALE_ORIGINAL,
+// CTA_PROTOINFO_TCP_WSCALE_REPLY,
+// CTA_PROTOINFO_TCP_FLAGS_ORIGINAL,
+// CTA_PROTOINFO_TCP_FLAGS_REPLY,
+// __CTA_PROTOINFO_TCP_MAX
+// };
+// #define CTA_PROTOINFO_TCP_MAX (__CTA_PROTOINFO_TCP_MAX - 1)
+const (
+ CTA_PROTOINFO_TCP_STATE = 1
+ CTA_PROTOINFO_TCP_WSCALE_ORIGINAL = 2
+ CTA_PROTOINFO_TCP_WSCALE_REPLY = 3
+ CTA_PROTOINFO_TCP_FLAGS_ORIGINAL = 4
+ CTA_PROTOINFO_TCP_FLAGS_REPLY = 5
+)
+
+// /* General form of address family dependent message.
+// */
+// struct nfgenmsg {
+// __u8 nfgen_family; /* AF_xxx */
+// __u8 version; /* nfnetlink version */
+// __be16 res_id; /* resource id */
+// };
+type Nfgenmsg struct {
+ NfgenFamily uint8
+ Version uint8
+ ResId uint16 // big endian
+}
+
+func (msg *Nfgenmsg) Len() int {
+ return SizeofNfgenmsg
+}
+
+func DeserializeNfgenmsg(b []byte) *Nfgenmsg {
+ return (*Nfgenmsg)(unsafe.Pointer(&b[0:SizeofNfgenmsg][0]))
+}
+
+func (msg *Nfgenmsg) Serialize() []byte {
+ return (*(*[SizeofNfgenmsg]byte)(unsafe.Pointer(msg)))[:]
+}
IFLA_BRPORT_FAST_LEAVE
IFLA_BRPORT_LEARNING
IFLA_BRPORT_UNICAST_FLOOD
- IFLA_BRPORT_MAX = IFLA_BRPORT_UNICAST_FLOOD
+ IFLA_BRPORT_PROXYARP
+ IFLA_BRPORT_LEARNING_SYNC
+ IFLA_BRPORT_PROXYARP_WIFI
+ IFLA_BRPORT_MAX = IFLA_BRPORT_PROXYARP_WIFI
)
const (
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+func (msg *VfMac) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ copy(b[4:36], msg.Mac[:])
+}
+
+func (msg *VfMac) serializeSafe() []byte {
+ length := SizeofVfMac
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfMacSafe(b []byte) *VfMac {
+ var msg = VfMac{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfMac]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfMacDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfMac)
+ rand.Read(orig)
+ safemsg := deserializeVfMacSafe(orig)
+ msg := DeserializeVfMac(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfVlan) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.Vlan))
+ native.PutUint32(b[8:12], uint32(msg.Qos))
+}
+
+func (msg *VfVlan) serializeSafe() []byte {
+ length := SizeofVfVlan
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfVlanSafe(b []byte) *VfVlan {
+ var msg = VfVlan{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfVlan]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfVlanDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfVlan)
+ rand.Read(orig)
+ safemsg := deserializeVfVlanSafe(orig)
+ msg := DeserializeVfVlan(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfTxRate) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.Rate))
+}
+
+func (msg *VfTxRate) serializeSafe() []byte {
+ length := SizeofVfTxRate
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfTxRateSafe(b []byte) *VfTxRate {
+ var msg = VfTxRate{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfTxRate]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfTxRateDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfTxRate)
+ rand.Read(orig)
+ safemsg := deserializeVfTxRateSafe(orig)
+ msg := DeserializeVfTxRate(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfRate) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.MinTxRate))
+ native.PutUint32(b[8:12], uint32(msg.MaxTxRate))
+}
+
+func (msg *VfRate) serializeSafe() []byte {
+ length := SizeofVfRate
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfRateSafe(b []byte) *VfRate {
+ var msg = VfRate{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfRate]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfRateDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfRate)
+ rand.Read(orig)
+ safemsg := deserializeVfRateSafe(orig)
+ msg := DeserializeVfRate(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfSpoofchk) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.Setting))
+}
+
+func (msg *VfSpoofchk) serializeSafe() []byte {
+ length := SizeofVfSpoofchk
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfSpoofchkSafe(b []byte) *VfSpoofchk {
+ var msg = VfSpoofchk{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfSpoofchk]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfSpoofchkDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfSpoofchk)
+ rand.Read(orig)
+ safemsg := deserializeVfSpoofchkSafe(orig)
+ msg := DeserializeVfSpoofchk(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfLinkState) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.LinkState))
+}
+
+func (msg *VfLinkState) serializeSafe() []byte {
+ length := SizeofVfLinkState
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfLinkStateSafe(b []byte) *VfLinkState {
+ var msg = VfLinkState{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfLinkState]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfLinkStateDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfLinkState)
+ rand.Read(orig)
+ safemsg := deserializeVfLinkStateSafe(orig)
+ msg := DeserializeVfLinkState(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *VfRssQueryEn) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], uint32(msg.Vf))
+ native.PutUint32(b[4:8], uint32(msg.Setting))
+}
+
+func (msg *VfRssQueryEn) serializeSafe() []byte {
+ length := SizeofVfRssQueryEn
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeVfRssQueryEnSafe(b []byte) *VfRssQueryEn {
+ var msg = VfRssQueryEn{}
+ binary.Read(bytes.NewReader(b[0:SizeofVfRssQueryEn]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestVfRssQueryEnDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofVfRssQueryEn)
+ rand.Read(orig)
+ safemsg := deserializeVfRssQueryEnSafe(orig)
+ msg := DeserializeVfRssQueryEn(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
)
// SupportedNlFamilies contains the list of netlink families this netlink package supports
-var SupportedNlFamilies = []int{syscall.NETLINK_ROUTE, syscall.NETLINK_XFRM}
+var SupportedNlFamilies = []int{syscall.NETLINK_ROUTE, syscall.NETLINK_XFRM, syscall.NETLINK_NETFILTER}
var nextSeqNr uint32
type NetlinkRequest struct {
syscall.NlMsghdr
Data []NetlinkRequestData
+ RawData []byte
Sockets map[int]*SocketHandle
}
dataBytes[i] = data.Serialize()
length = length + len(dataBytes[i])
}
+ length += len(req.RawData)
+
req.Len = uint32(length)
b := make([]byte, length)
hdr := (*(*[syscall.SizeofNlMsghdr]byte)(unsafe.Pointer(req)))[:]
next = next + 1
}
}
+ // Add the raw data if any
+ if len(req.RawData) > 0 {
+ copy(b[next:length], req.RawData)
+ }
return b
}
}
}
+// AddRawData adds raw bytes to the end of the NetlinkRequest object during serialization
+func (req *NetlinkRequest) AddRawData(data []byte) {
+ if data != nil {
+ req.RawData = append(req.RawData, data...)
+ }
+}
+
// Execute the request against a the given sockType.
// Returns a list of netlink messages in serialized format, optionally filtered
// by resType.
}
func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
- fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
+ fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, protocol)
if err != nil {
return nil, err
}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "reflect"
+ "syscall"
+ "testing"
+)
+
+type testSerializer interface {
+ serializeSafe() []byte
+ Serialize() []byte
+}
+
+func testDeserializeSerialize(t *testing.T, orig []byte, safemsg testSerializer, msg testSerializer) {
+ if !reflect.DeepEqual(safemsg, msg) {
+ t.Fatal("Deserialization failed.\n", safemsg, "\n", msg)
+ }
+ safe := msg.serializeSafe()
+ if !bytes.Equal(safe, orig) {
+ t.Fatal("Safe serialization failed.\n", safe, "\n", orig)
+ }
+ b := msg.Serialize()
+ if !bytes.Equal(b, safe) {
+ t.Fatal("Serialization failed.\n", b, "\n", safe)
+ }
+}
+
+func (msg *IfInfomsg) write(b []byte) {
+ native := NativeEndian()
+ b[0] = msg.Family
+ // pad byte is skipped because it is not exported on linux/s390x
+ native.PutUint16(b[2:4], msg.Type)
+ native.PutUint32(b[4:8], uint32(msg.Index))
+ native.PutUint32(b[8:12], msg.Flags)
+ native.PutUint32(b[12:16], msg.Change)
+}
+
+func (msg *IfInfomsg) serializeSafe() []byte {
+ length := syscall.SizeofIfInfomsg
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeIfInfomsgSafe(b []byte) *IfInfomsg {
+ var msg = IfInfomsg{}
+ binary.Read(bytes.NewReader(b[0:syscall.SizeofIfInfomsg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestIfInfomsgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, syscall.SizeofIfInfomsg)
+ rand.Read(orig)
+ // zero out the pad byte
+ orig[1] = 0
+ safemsg := deserializeIfInfomsgSafe(orig)
+ msg := DeserializeIfInfomsg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "syscall"
+ "testing"
+)
+
+func (msg *RtMsg) write(b []byte) {
+ native := NativeEndian()
+ b[0] = msg.Family
+ b[1] = msg.Dst_len
+ b[2] = msg.Src_len
+ b[3] = msg.Tos
+ b[4] = msg.Table
+ b[5] = msg.Protocol
+ b[6] = msg.Scope
+ b[7] = msg.Type
+ native.PutUint32(b[8:12], msg.Flags)
+}
+
+func (msg *RtMsg) serializeSafe() []byte {
+ len := syscall.SizeofRtMsg
+ b := make([]byte, len)
+ msg.write(b)
+ return b
+}
+
+func deserializeRtMsgSafe(b []byte) *RtMsg {
+ var msg = RtMsg{}
+ binary.Read(bytes.NewReader(b[0:syscall.SizeofRtMsg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestRtMsgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, syscall.SizeofRtMsg)
+ rand.Read(orig)
+ safemsg := deserializeRtMsgSafe(orig)
+ msg := DeserializeRtMsg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+/* TcMsg */
+func (msg *TcMsg) write(b []byte) {
+ native := NativeEndian()
+ b[0] = msg.Family
+ copy(b[1:4], msg.Pad[:])
+ native.PutUint32(b[4:8], uint32(msg.Ifindex))
+ native.PutUint32(b[8:12], msg.Handle)
+ native.PutUint32(b[12:16], msg.Parent)
+ native.PutUint32(b[16:20], msg.Info)
+}
+
+func (msg *TcMsg) serializeSafe() []byte {
+ length := SizeofTcMsg
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeTcMsgSafe(b []byte) *TcMsg {
+ var msg = TcMsg{}
+ binary.Read(bytes.NewReader(b[0:SizeofTcMsg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestTcMsgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofTcMsg)
+ rand.Read(orig)
+ safemsg := deserializeTcMsgSafe(orig)
+ msg := DeserializeTcMsg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+/* TcActionMsg */
+func (msg *TcActionMsg) write(b []byte) {
+ b[0] = msg.Family
+ copy(b[1:4], msg.Pad[:])
+}
+
+func (msg *TcActionMsg) serializeSafe() []byte {
+ length := SizeofTcActionMsg
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeTcActionMsgSafe(b []byte) *TcActionMsg {
+ var msg = TcActionMsg{}
+ binary.Read(bytes.NewReader(b[0:SizeofTcActionMsg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestTcActionMsgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofTcActionMsg)
+ rand.Read(orig)
+ safemsg := deserializeTcActionMsgSafe(orig)
+ msg := DeserializeTcActionMsg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+/* TcRateSpec */
+func (msg *TcRateSpec) write(b []byte) {
+ native := NativeEndian()
+ b[0] = msg.CellLog
+ b[1] = msg.Linklayer
+ native.PutUint16(b[2:4], msg.Overhead)
+ native.PutUint16(b[4:6], uint16(msg.CellAlign))
+ native.PutUint16(b[6:8], msg.Mpu)
+ native.PutUint32(b[8:12], msg.Rate)
+}
+
+func (msg *TcRateSpec) serializeSafe() []byte {
+ length := SizeofTcRateSpec
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeTcRateSpecSafe(b []byte) *TcRateSpec {
+ var msg = TcRateSpec{}
+ binary.Read(bytes.NewReader(b[0:SizeofTcRateSpec]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestTcRateSpecDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofTcRateSpec)
+ rand.Read(orig)
+ safemsg := deserializeTcRateSpecSafe(orig)
+ msg := DeserializeTcRateSpec(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+/* TcTbfQopt */
+func (msg *TcTbfQopt) write(b []byte) {
+ native := NativeEndian()
+ msg.Rate.write(b[0:SizeofTcRateSpec])
+ start := SizeofTcRateSpec
+ msg.Peakrate.write(b[start : start+SizeofTcRateSpec])
+ start += SizeofTcRateSpec
+ native.PutUint32(b[start:start+4], msg.Limit)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Buffer)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Mtu)
+}
+
+func (msg *TcTbfQopt) serializeSafe() []byte {
+ length := SizeofTcTbfQopt
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeTcTbfQoptSafe(b []byte) *TcTbfQopt {
+ var msg = TcTbfQopt{}
+ binary.Read(bytes.NewReader(b[0:SizeofTcTbfQopt]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestTcTbfQoptDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofTcTbfQopt)
+ rand.Read(orig)
+ safemsg := deserializeTcTbfQoptSafe(orig)
+ msg := DeserializeTcTbfQopt(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+/* TcHtbCopt */
+func (msg *TcHtbCopt) write(b []byte) {
+ native := NativeEndian()
+ msg.Rate.write(b[0:SizeofTcRateSpec])
+ start := SizeofTcRateSpec
+ msg.Ceil.write(b[start : start+SizeofTcRateSpec])
+ start += SizeofTcRateSpec
+ native.PutUint32(b[start:start+4], msg.Buffer)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Cbuffer)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Quantum)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Level)
+ start += 4
+ native.PutUint32(b[start:start+4], msg.Prio)
+}
+
+func (msg *TcHtbCopt) serializeSafe() []byte {
+ length := SizeofTcHtbCopt
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeTcHtbCoptSafe(b []byte) *TcHtbCopt {
+ var msg = TcHtbCopt{}
+ binary.Read(bytes.NewReader(b[0:SizeofTcHtbCopt]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestTcHtbCoptDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofTcHtbCopt)
+ rand.Read(orig)
+ safemsg := deserializeTcHtbCoptSafe(orig)
+ msg := DeserializeTcHtbCopt(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+func (msg *XfrmAddress) write(b []byte) {
+ copy(b[0:SizeofXfrmAddress], msg[:])
+}
+
+func (msg *XfrmAddress) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmAddress)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmAddressSafe(b []byte) *XfrmAddress {
+ var msg = XfrmAddress{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmAddress]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmAddressDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmAddress)
+ rand.Read(orig)
+ safemsg := deserializeXfrmAddressSafe(orig)
+ msg := DeserializeXfrmAddress(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmSelector) write(b []byte) {
+ const AddrEnd = SizeofXfrmAddress * 2
+ native := NativeEndian()
+ msg.Daddr.write(b[0:SizeofXfrmAddress])
+ msg.Saddr.write(b[SizeofXfrmAddress:AddrEnd])
+ native.PutUint16(b[AddrEnd:AddrEnd+2], msg.Dport)
+ native.PutUint16(b[AddrEnd+2:AddrEnd+4], msg.DportMask)
+ native.PutUint16(b[AddrEnd+4:AddrEnd+6], msg.Sport)
+ native.PutUint16(b[AddrEnd+6:AddrEnd+8], msg.SportMask)
+ native.PutUint16(b[AddrEnd+8:AddrEnd+10], msg.Family)
+ b[AddrEnd+10] = msg.PrefixlenD
+ b[AddrEnd+11] = msg.PrefixlenS
+ b[AddrEnd+12] = msg.Proto
+ copy(b[AddrEnd+13:AddrEnd+16], msg.Pad[:])
+ native.PutUint32(b[AddrEnd+16:AddrEnd+20], uint32(msg.Ifindex))
+ native.PutUint32(b[AddrEnd+20:AddrEnd+24], msg.User)
+}
+
+func (msg *XfrmSelector) serializeSafe() []byte {
+ length := SizeofXfrmSelector
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmSelectorSafe(b []byte) *XfrmSelector {
+ var msg = XfrmSelector{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmSelector]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmSelectorDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmSelector)
+ rand.Read(orig)
+ safemsg := deserializeXfrmSelectorSafe(orig)
+ msg := DeserializeXfrmSelector(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmLifetimeCfg) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint64(b[0:8], msg.SoftByteLimit)
+ native.PutUint64(b[8:16], msg.HardByteLimit)
+ native.PutUint64(b[16:24], msg.SoftPacketLimit)
+ native.PutUint64(b[24:32], msg.HardPacketLimit)
+ native.PutUint64(b[32:40], msg.SoftAddExpiresSeconds)
+ native.PutUint64(b[40:48], msg.HardAddExpiresSeconds)
+ native.PutUint64(b[48:56], msg.SoftUseExpiresSeconds)
+ native.PutUint64(b[56:64], msg.HardUseExpiresSeconds)
+}
+
+func (msg *XfrmLifetimeCfg) serializeSafe() []byte {
+ length := SizeofXfrmLifetimeCfg
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmLifetimeCfgSafe(b []byte) *XfrmLifetimeCfg {
+ var msg = XfrmLifetimeCfg{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCfg]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmLifetimeCfgDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmLifetimeCfg)
+ rand.Read(orig)
+ safemsg := deserializeXfrmLifetimeCfgSafe(orig)
+ msg := DeserializeXfrmLifetimeCfg(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmLifetimeCur) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint64(b[0:8], msg.Bytes)
+ native.PutUint64(b[8:16], msg.Packets)
+ native.PutUint64(b[16:24], msg.AddTime)
+ native.PutUint64(b[24:32], msg.UseTime)
+}
+
+func (msg *XfrmLifetimeCur) serializeSafe() []byte {
+ length := SizeofXfrmLifetimeCur
+ b := make([]byte, length)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmLifetimeCurSafe(b []byte) *XfrmLifetimeCur {
+ var msg = XfrmLifetimeCur{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCur]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmLifetimeCurDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmLifetimeCur)
+ rand.Read(orig)
+ safemsg := deserializeXfrmLifetimeCurSafe(orig)
+ msg := DeserializeXfrmLifetimeCur(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmId) write(b []byte) {
+ native := NativeEndian()
+ msg.Daddr.write(b[0:SizeofXfrmAddress])
+ native.PutUint32(b[SizeofXfrmAddress:SizeofXfrmAddress+4], msg.Spi)
+ b[SizeofXfrmAddress+4] = msg.Proto
+ copy(b[SizeofXfrmAddress+5:SizeofXfrmAddress+8], msg.Pad[:])
+}
+
+func (msg *XfrmId) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmId)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmIdSafe(b []byte) *XfrmId {
+ var msg = XfrmId{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmId]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmIdDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmId)
+ rand.Read(orig)
+ safemsg := deserializeXfrmIdSafe(orig)
+ msg := DeserializeXfrmId(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+func (msg *XfrmUserExpire) write(b []byte) {
+ msg.XfrmUsersaInfo.write(b[0:SizeofXfrmUsersaInfo])
+ b[SizeofXfrmUsersaInfo] = msg.Hard
+ copy(b[SizeofXfrmUsersaInfo+1:SizeofXfrmUserExpire], msg.Pad[:])
+}
+
+func (msg *XfrmUserExpire) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUserExpire)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUserExpireSafe(b []byte) *XfrmUserExpire {
+ var msg = XfrmUserExpire{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUserExpire]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUserExpireDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUserExpire)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUserExpireSafe(orig)
+ msg := DeserializeXfrmUserExpire(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+func (msg *XfrmUserpolicyId) write(b []byte) {
+ native := NativeEndian()
+ msg.Sel.write(b[0:SizeofXfrmSelector])
+ native.PutUint32(b[SizeofXfrmSelector:SizeofXfrmSelector+4], msg.Index)
+ b[SizeofXfrmSelector+4] = msg.Dir
+ copy(b[SizeofXfrmSelector+5:SizeofXfrmSelector+8], msg.Pad[:])
+}
+
+func (msg *XfrmUserpolicyId) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUserpolicyId)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUserpolicyIdSafe(b []byte) *XfrmUserpolicyId {
+ var msg = XfrmUserpolicyId{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyId]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUserpolicyIdDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUserpolicyId)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUserpolicyIdSafe(orig)
+ msg := DeserializeXfrmUserpolicyId(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmUserpolicyInfo) write(b []byte) {
+ const CfgEnd = SizeofXfrmSelector + SizeofXfrmLifetimeCfg
+ const CurEnd = CfgEnd + SizeofXfrmLifetimeCur
+ native := NativeEndian()
+ msg.Sel.write(b[0:SizeofXfrmSelector])
+ msg.Lft.write(b[SizeofXfrmSelector:CfgEnd])
+ msg.Curlft.write(b[CfgEnd:CurEnd])
+ native.PutUint32(b[CurEnd:CurEnd+4], msg.Priority)
+ native.PutUint32(b[CurEnd+4:CurEnd+8], msg.Index)
+ b[CurEnd+8] = msg.Dir
+ b[CurEnd+9] = msg.Action
+ b[CurEnd+10] = msg.Flags
+ b[CurEnd+11] = msg.Share
+ copy(b[CurEnd+12:CurEnd+16], msg.Pad[:])
+}
+
+func (msg *XfrmUserpolicyInfo) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUserpolicyInfo)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUserpolicyInfoSafe(b []byte) *XfrmUserpolicyInfo {
+ var msg = XfrmUserpolicyInfo{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyInfo]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUserpolicyInfoDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUserpolicyInfo)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUserpolicyInfoSafe(orig)
+ msg := DeserializeXfrmUserpolicyInfo(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmUserTmpl) write(b []byte) {
+ const AddrEnd = SizeofXfrmId + 4 + SizeofXfrmAddress
+ native := NativeEndian()
+ msg.XfrmId.write(b[0:SizeofXfrmId])
+ native.PutUint16(b[SizeofXfrmId:SizeofXfrmId+2], msg.Family)
+ copy(b[SizeofXfrmId+2:SizeofXfrmId+4], msg.Pad1[:])
+ msg.Saddr.write(b[SizeofXfrmId+4 : AddrEnd])
+ native.PutUint32(b[AddrEnd:AddrEnd+4], msg.Reqid)
+ b[AddrEnd+4] = msg.Mode
+ b[AddrEnd+5] = msg.Share
+ b[AddrEnd+6] = msg.Optional
+ b[AddrEnd+7] = msg.Pad2
+ native.PutUint32(b[AddrEnd+8:AddrEnd+12], msg.Aalgos)
+ native.PutUint32(b[AddrEnd+12:AddrEnd+16], msg.Ealgos)
+ native.PutUint32(b[AddrEnd+16:AddrEnd+20], msg.Calgos)
+}
+
+func (msg *XfrmUserTmpl) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUserTmpl)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUserTmplSafe(b []byte) *XfrmUserTmpl {
+ var msg = XfrmUserTmpl{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUserTmpl]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUserTmplDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUserTmpl)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUserTmplSafe(orig)
+ msg := DeserializeXfrmUserTmpl(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
--- /dev/null
+package nl
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+)
+
+func (msg *XfrmUsersaId) write(b []byte) {
+ native := NativeEndian()
+ msg.Daddr.write(b[0:SizeofXfrmAddress])
+ native.PutUint32(b[SizeofXfrmAddress:SizeofXfrmAddress+4], msg.Spi)
+ native.PutUint16(b[SizeofXfrmAddress+4:SizeofXfrmAddress+6], msg.Family)
+ b[SizeofXfrmAddress+6] = msg.Proto
+ b[SizeofXfrmAddress+7] = msg.Pad
+}
+
+func (msg *XfrmUsersaId) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUsersaId)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUsersaIdSafe(b []byte) *XfrmUsersaId {
+ var msg = XfrmUsersaId{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUsersaId]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUsersaIdDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUsersaId)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUsersaIdSafe(orig)
+ msg := DeserializeXfrmUsersaId(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmStats) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], msg.ReplayWindow)
+ native.PutUint32(b[4:8], msg.Replay)
+ native.PutUint32(b[8:12], msg.IntegrityFailed)
+}
+
+func (msg *XfrmStats) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmStats)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmStatsSafe(b []byte) *XfrmStats {
+ var msg = XfrmStats{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmStats]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmStatsDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmStats)
+ rand.Read(orig)
+ safemsg := deserializeXfrmStatsSafe(orig)
+ msg := DeserializeXfrmStats(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmUsersaInfo) write(b []byte) {
+ const IdEnd = SizeofXfrmSelector + SizeofXfrmId
+ const AddressEnd = IdEnd + SizeofXfrmAddress
+ const CfgEnd = AddressEnd + SizeofXfrmLifetimeCfg
+ const CurEnd = CfgEnd + SizeofXfrmLifetimeCur
+ const StatsEnd = CurEnd + SizeofXfrmStats
+ native := NativeEndian()
+ msg.Sel.write(b[0:SizeofXfrmSelector])
+ msg.Id.write(b[SizeofXfrmSelector:IdEnd])
+ msg.Saddr.write(b[IdEnd:AddressEnd])
+ msg.Lft.write(b[AddressEnd:CfgEnd])
+ msg.Curlft.write(b[CfgEnd:CurEnd])
+ msg.Stats.write(b[CurEnd:StatsEnd])
+ native.PutUint32(b[StatsEnd:StatsEnd+4], msg.Seq)
+ native.PutUint32(b[StatsEnd+4:StatsEnd+8], msg.Reqid)
+ native.PutUint16(b[StatsEnd+8:StatsEnd+10], msg.Family)
+ b[StatsEnd+10] = msg.Mode
+ b[StatsEnd+11] = msg.ReplayWindow
+ b[StatsEnd+12] = msg.Flags
+ copy(b[StatsEnd+13:StatsEnd+20], msg.Pad[:])
+}
+
+func (msg *XfrmUsersaInfo) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUsersaInfo)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUsersaInfoSafe(b []byte) *XfrmUsersaInfo {
+ var msg = XfrmUsersaInfo{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUsersaInfo]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUsersaInfoDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUsersaInfo)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUsersaInfoSafe(orig)
+ msg := DeserializeXfrmUsersaInfo(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmAlgo) write(b []byte) {
+ native := NativeEndian()
+ copy(b[0:64], msg.AlgName[:])
+ native.PutUint32(b[64:68], msg.AlgKeyLen)
+ copy(b[68:msg.Len()], msg.AlgKey[:])
+}
+
+func (msg *XfrmAlgo) serializeSafe() []byte {
+ b := make([]byte, msg.Len())
+ msg.write(b)
+ return b
+}
+
+func (msg *XfrmUserSpiInfo) write(b []byte) {
+ native := NativeEndian()
+ msg.XfrmUsersaInfo.write(b[0:SizeofXfrmUsersaInfo])
+ native.PutUint32(b[SizeofXfrmUsersaInfo:SizeofXfrmUsersaInfo+4], msg.Min)
+ native.PutUint32(b[SizeofXfrmUsersaInfo+4:SizeofXfrmUsersaInfo+8], msg.Max)
+}
+
+func (msg *XfrmUserSpiInfo) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmUserSpiInfo)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmUserSpiInfoSafe(b []byte) *XfrmUserSpiInfo {
+ var msg = XfrmUserSpiInfo{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmUserSpiInfo]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmUserSpiInfoDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmUserSpiInfo)
+ rand.Read(orig)
+ safemsg := deserializeXfrmUserSpiInfoSafe(orig)
+ msg := DeserializeXfrmUserSpiInfo(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func deserializeXfrmAlgoSafe(b []byte) *XfrmAlgo {
+ var msg = XfrmAlgo{}
+ copy(msg.AlgName[:], b[0:64])
+ binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen)
+ msg.AlgKey = b[68:msg.Len()]
+ return &msg
+}
+
+func TestXfrmAlgoDeserializeSerialize(t *testing.T) {
+ native := NativeEndian()
+ // use a 32 byte key len
+ var orig = make([]byte, SizeofXfrmAlgo+32)
+ rand.Read(orig)
+ // set the key len to 256 bits
+ var KeyLen uint32 = 0x00000100
+ // Little Endian Big Endian
+ // orig[64] = 0 orig[64] = 0
+ // orig[65] = 1 orig[65] = 0
+ // orig[66] = 0 orig[66] = 1
+ // orig[67] = 0 orig[67] = 0
+ native.PutUint32(orig[64:68], KeyLen)
+ safemsg := deserializeXfrmAlgoSafe(orig)
+ msg := DeserializeXfrmAlgo(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmAlgoAuth) write(b []byte) {
+ native := NativeEndian()
+ copy(b[0:64], msg.AlgName[:])
+ native.PutUint32(b[64:68], msg.AlgKeyLen)
+ native.PutUint32(b[68:72], msg.AlgTruncLen)
+ copy(b[72:msg.Len()], msg.AlgKey[:])
+}
+
+func (msg *XfrmAlgoAuth) serializeSafe() []byte {
+ b := make([]byte, msg.Len())
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmAlgoAuthSafe(b []byte) *XfrmAlgoAuth {
+ var msg = XfrmAlgoAuth{}
+ copy(msg.AlgName[:], b[0:64])
+ binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen)
+ binary.Read(bytes.NewReader(b[68:72]), NativeEndian(), &msg.AlgTruncLen)
+ msg.AlgKey = b[72:msg.Len()]
+ return &msg
+}
+
+func TestXfrmAlgoAuthDeserializeSerialize(t *testing.T) {
+ native := NativeEndian()
+ // use a 32 byte key len
+ var orig = make([]byte, SizeofXfrmAlgoAuth+32)
+ rand.Read(orig)
+ // set the key len to 256 bits
+ var KeyLen uint32 = 0x00000100
+ // Little Endian Big Endian
+ // orig[64] = 0 orig[64] = 0
+ // orig[65] = 1 orig[65] = 0
+ // orig[66] = 0 orig[66] = 1
+ // orig[67] = 0 orig[67] = 0
+ native.PutUint32(orig[64:68], KeyLen)
+ safemsg := deserializeXfrmAlgoAuthSafe(orig)
+ msg := DeserializeXfrmAlgoAuth(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmEncapTmpl) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint16(b[0:2], msg.EncapType)
+ native.PutUint16(b[2:4], msg.EncapSport)
+ native.PutUint16(b[4:6], msg.EncapDport)
+ copy(b[6:8], msg.Pad[:])
+ msg.EncapOa.write(b[8:SizeofXfrmAddress])
+}
+
+func (msg *XfrmEncapTmpl) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmEncapTmpl)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmEncapTmplSafe(b []byte) *XfrmEncapTmpl {
+ var msg = XfrmEncapTmpl{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmEncapTmpl]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmEncapTmplDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmEncapTmpl)
+ rand.Read(orig)
+ safemsg := deserializeXfrmEncapTmplSafe(orig)
+ msg := DeserializeXfrmEncapTmpl(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmMark) write(b []byte) {
+ native := NativeEndian()
+ native.PutUint32(b[0:4], msg.Value)
+ native.PutUint32(b[4:8], msg.Mask)
+}
+
+func (msg *XfrmMark) serializeSafe() []byte {
+ b := make([]byte, SizeofXfrmMark)
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmMarkSafe(b []byte) *XfrmMark {
+ var msg = XfrmMark{}
+ binary.Read(bytes.NewReader(b[0:SizeofXfrmMark]), NativeEndian(), &msg)
+ return &msg
+}
+
+func TestXfrmMarkDeserializeSerialize(t *testing.T) {
+ var orig = make([]byte, SizeofXfrmMark)
+ rand.Read(orig)
+ safemsg := deserializeXfrmMarkSafe(orig)
+ msg := DeserializeXfrmMark(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
+
+func (msg *XfrmAlgoAEAD) write(b []byte) {
+ native := NativeEndian()
+ copy(b[0:64], msg.AlgName[:])
+ native.PutUint32(b[64:68], msg.AlgKeyLen)
+ native.PutUint32(b[68:72], msg.AlgICVLen)
+ copy(b[72:msg.Len()], msg.AlgKey[:])
+}
+
+func (msg *XfrmAlgoAEAD) serializeSafe() []byte {
+ b := make([]byte, msg.Len())
+ msg.write(b)
+ return b
+}
+
+func deserializeXfrmAlgoAEADSafe(b []byte) *XfrmAlgoAEAD {
+ var msg = XfrmAlgoAEAD{}
+ copy(msg.AlgName[:], b[0:64])
+ binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen)
+ binary.Read(bytes.NewReader(b[68:72]), NativeEndian(), &msg.AlgICVLen)
+ msg.AlgKey = b[72:msg.Len()]
+ return &msg
+}
+
+func TestXfrmXfrmAlgoAeadDeserializeSerialize(t *testing.T) {
+ native := NativeEndian()
+ // use a 32 byte key len
+ var orig = make([]byte, SizeofXfrmAlgoAEAD+36)
+ rand.Read(orig)
+ // set the key len to (256 + 32) bits
+ var KeyLen uint32 = 0x00000120
+ native.PutUint32(orig[64:68], KeyLen)
+ safemsg := deserializeXfrmAlgoAEADSafe(orig)
+ msg := DeserializeXfrmAlgoAEAD(orig)
+ testDeserializeSerialize(t, orig, safemsg, msg)
+}
// Protinfo represents bridge flags from netlink.
type Protinfo struct {
- Hairpin bool
- Guard bool
- FastLeave bool
- RootBlock bool
- Learning bool
- Flood bool
+ Hairpin bool
+ Guard bool
+ FastLeave bool
+ RootBlock bool
+ Learning bool
+ Flood bool
+ ProxyArp bool
+ ProxyArpWiFi bool
}
// String returns a list of enabled flags
if prot.Flood {
boolStrings = append(boolStrings, "Flood")
}
+ if prot.ProxyArp {
+ boolStrings = append(boolStrings, "ProxyArp")
+ }
+ if prot.ProxyArpWiFi {
+ boolStrings = append(boolStrings, "ProxyArpWiFi")
+ }
return strings.Join(boolStrings, " ")
}
pi.Learning = byteToBool(info.Value[0])
case nl.IFLA_BRPORT_UNICAST_FLOOD:
pi.Flood = byteToBool(info.Value[0])
+ case nl.IFLA_BRPORT_PROXYARP:
+ pi.ProxyArp = byteToBool(info.Value[0])
+ case nl.IFLA_BRPORT_PROXYARP_WIFI:
+ pi.ProxyArpWiFi = byteToBool(info.Value[0])
}
}
return &pi
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "os"
+ "testing"
+)
+
+func TestProtinfo(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ master := &Bridge{LinkAttrs{Name: "foo"}}
+ if err := LinkAdd(master); err != nil {
+ t.Fatal(err)
+ }
+ iface1 := &Dummy{LinkAttrs{Name: "bar1", MasterIndex: master.Index}}
+ iface2 := &Dummy{LinkAttrs{Name: "bar2", MasterIndex: master.Index}}
+ iface3 := &Dummy{LinkAttrs{Name: "bar3"}}
+ iface4 := &Dummy{LinkAttrs{Name: "bar4", MasterIndex: master.Index}}
+
+ if err := LinkAdd(iface1); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(iface2); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(iface3); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkAdd(iface4); err != nil {
+ t.Fatal(err)
+ }
+
+ oldpi1, err := LinkGetProtinfo(iface1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ oldpi2, err := LinkGetProtinfo(iface2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ oldpi4, err := LinkGetProtinfo(iface4)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkSetHairpin(iface1, true); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkSetRootBlock(iface1, true); err != nil {
+ t.Fatal(err)
+ }
+
+ pi1, err := LinkGetProtinfo(iface1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !pi1.Hairpin {
+ t.Fatalf("Hairpin mode is not enabled for %s, but should", iface1.Name)
+ }
+ if !pi1.RootBlock {
+ t.Fatalf("RootBlock is not enabled for %s, but should", iface1.Name)
+ }
+ if pi1.ProxyArp != oldpi1.ProxyArp {
+ t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface1.Name)
+ }
+ if pi1.ProxyArpWiFi != oldpi1.ProxyArp {
+ t.Fatalf("ProxyArpWiFi ProxyArp field was changed for %s but shouldn't", iface1.Name)
+ }
+ if pi1.Guard != oldpi1.Guard {
+ t.Fatalf("Guard field was changed for %s but shouldn't", iface1.Name)
+ }
+ if pi1.FastLeave != oldpi1.FastLeave {
+ t.Fatalf("FastLeave field was changed for %s but shouldn't", iface1.Name)
+ }
+ if pi1.Learning != oldpi1.Learning {
+ t.Fatalf("Learning field was changed for %s but shouldn't", iface1.Name)
+ }
+ if pi1.Flood != oldpi1.Flood {
+ t.Fatalf("Flood field was changed for %s but shouldn't", iface1.Name)
+ }
+
+ if err := LinkSetGuard(iface2, true); err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetLearning(iface2, false); err != nil {
+ t.Fatal(err)
+ }
+ pi2, err := LinkGetProtinfo(iface2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if pi2.Hairpin {
+ t.Fatalf("Hairpin mode is enabled for %s, but shouldn't", iface2.Name)
+ }
+ if !pi2.Guard {
+ t.Fatalf("Guard is not enabled for %s, but should", iface2.Name)
+ }
+ if pi2.ProxyArp != oldpi2.ProxyArp {
+ t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface2.Name)
+ }
+ if pi2.ProxyArpWiFi != oldpi2.ProxyArpWiFi {
+ t.Fatalf("ProxyArpWiFi field was changed for %s but shouldn't", iface2.Name)
+ }
+ if pi2.Learning {
+ t.Fatalf("Learning is enabled for %s, but shouldn't", iface2.Name)
+ }
+ if pi2.RootBlock != oldpi2.RootBlock {
+ t.Fatalf("RootBlock field was changed for %s but shouldn't", iface2.Name)
+ }
+ if pi2.FastLeave != oldpi2.FastLeave {
+ t.Fatalf("FastLeave field was changed for %s but shouldn't", iface2.Name)
+ }
+ if pi2.Flood != oldpi2.Flood {
+ t.Fatalf("Flood field was changed for %s but shouldn't", iface2.Name)
+ }
+
+ if err := LinkSetHairpin(iface3, true); err == nil || err.Error() != "operation not supported" {
+ t.Fatalf("Set protinfo attrs for link without master is not supported, but err: %s", err)
+ }
+
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Skipped some tests because travis kernel is to old to support BR_PROXYARP.")
+ }
+
+ if err := LinkSetBrProxyArp(iface4, true); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := LinkSetBrProxyArpWiFi(iface4, true); err != nil {
+ t.Fatal(err)
+ }
+ pi4, err := LinkGetProtinfo(iface4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if pi4.Hairpin != oldpi4.Hairpin {
+ t.Fatalf("Hairpin field was changed for %s but shouldn't", iface4.Name)
+ }
+ if pi4.Guard != oldpi4.Guard {
+ t.Fatalf("Guard field was changed for %s but shouldn't", iface4.Name)
+ }
+ if pi4.Learning != oldpi4.Learning {
+ t.Fatalf("Learning field was changed for %s but shouldn't", iface4.Name)
+ }
+ if !pi4.ProxyArp {
+ t.Fatalf("ProxyArp is not enabled for %s, but should", iface4.Name)
+ }
+ if !pi4.ProxyArpWiFi {
+ t.Fatalf("ProxyArpWiFi is not enabled for %s, but should", iface4.Name)
+ }
+ if pi4.RootBlock != oldpi4.RootBlock {
+ t.Fatalf("RootBlock field was changed for %s but shouldn't", iface4.Name)
+ }
+ if pi4.FastLeave != oldpi4.FastLeave {
+ t.Fatalf("FastLeave field was changed for %s but shouldn't", iface4.Name)
+ }
+ if pi4.Flood != oldpi4.Flood {
+ t.Fatalf("Flood field was changed for %s but shouldn't", iface4.Name)
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "testing"
+)
+
+func TestTbfAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ qdisc := &Tbf{
+ QdiscAttrs: QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(1, 0),
+ Parent: HANDLE_ROOT,
+ },
+ Rate: 131072,
+ Limit: 1220703,
+ Buffer: 16793,
+ }
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ tbf, ok := qdiscs[0].(*Tbf)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if tbf.Rate != qdisc.Rate {
+ t.Fatal("Rate doesn't match")
+ }
+ if tbf.Limit != qdisc.Limit {
+ t.Fatal("Limit doesn't match")
+ }
+ if tbf.Buffer != qdisc.Buffer {
+ t.Fatal("Buffer doesn't match")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestHtbAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(1, 0),
+ Parent: HANDLE_ROOT,
+ }
+
+ qdisc := NewHtb(attrs)
+ qdisc.Rate2Quantum = 5
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ htb, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if htb.Defcls != qdisc.Defcls {
+ t.Fatal("Defcls doesn't match")
+ }
+ if htb.Rate2Quantum != qdisc.Rate2Quantum {
+ t.Fatal("Rate2Quantum doesn't match")
+ }
+ if htb.Debug != qdisc.Debug {
+ t.Fatal("Debug doesn't match")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestPrioAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+ qdisc := NewPrio(QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(1, 0),
+ Parent: HANDLE_ROOT,
+ })
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ _, ok := qdiscs[0].(*Prio)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestTbfAddHtbReplaceDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // Add
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(1, 0),
+ Parent: HANDLE_ROOT,
+ }
+ qdisc := &Tbf{
+ QdiscAttrs: attrs,
+ Rate: 131072,
+ Limit: 1220703,
+ Buffer: 16793,
+ }
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ tbf, ok := qdiscs[0].(*Tbf)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if tbf.Rate != qdisc.Rate {
+ t.Fatal("Rate doesn't match")
+ }
+ if tbf.Limit != qdisc.Limit {
+ t.Fatal("Limit doesn't match")
+ }
+ if tbf.Buffer != qdisc.Buffer {
+ t.Fatal("Buffer doesn't match")
+ }
+ // Replace
+ // For replace to work, the handle MUST be different that the running one
+ attrs.Handle = MakeHandle(2, 0)
+ qdisc2 := NewHtb(attrs)
+ qdisc2.Rate2Quantum = 5
+ if err := QdiscReplace(qdisc2); err != nil {
+ t.Fatal(err)
+ }
+
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ htb, ok := qdiscs[0].(*Htb)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if htb.Defcls != qdisc2.Defcls {
+ t.Fatal("Defcls doesn't match")
+ }
+ if htb.Rate2Quantum != qdisc2.Rate2Quantum {
+ t.Fatal("Rate2Quantum doesn't match")
+ }
+ if htb.Debug != qdisc2.Debug {
+ t.Fatal("Debug doesn't match")
+ }
+
+ if err := QdiscDel(qdisc2); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
+
+func TestTbfAddTbfChangeDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
+ t.Fatal(err)
+ }
+ link, err := LinkByName("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // Add
+ attrs := QdiscAttrs{
+ LinkIndex: link.Attrs().Index,
+ Handle: MakeHandle(1, 0),
+ Parent: HANDLE_ROOT,
+ }
+ qdisc := &Tbf{
+ QdiscAttrs: attrs,
+ Rate: 131072,
+ Limit: 1220703,
+ Buffer: 16793,
+ }
+ if err := QdiscAdd(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err := QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ tbf, ok := qdiscs[0].(*Tbf)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if tbf.Rate != qdisc.Rate {
+ t.Fatal("Rate doesn't match")
+ }
+ if tbf.Limit != qdisc.Limit {
+ t.Fatal("Limit doesn't match")
+ }
+ if tbf.Buffer != qdisc.Buffer {
+ t.Fatal("Buffer doesn't match")
+ }
+ // Change
+ // For change to work, the handle MUST not change
+ qdisc.Rate = 23456
+ if err := QdiscChange(qdisc); err != nil {
+ t.Fatal(err)
+ }
+
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 1 {
+ t.Fatal("Failed to add qdisc")
+ }
+ tbf, ok = qdiscs[0].(*Tbf)
+ if !ok {
+ t.Fatal("Qdisc is the wrong type")
+ }
+ if tbf.Rate != qdisc.Rate {
+ t.Fatal("Rate doesn't match")
+ }
+ if tbf.Limit != qdisc.Limit {
+ t.Fatal("Limit doesn't match")
+ }
+ if tbf.Buffer != qdisc.Buffer {
+ t.Fatal("Buffer doesn't match")
+ }
+
+ if err := QdiscDel(qdisc); err != nil {
+ t.Fatal(err)
+ }
+ qdiscs, err = QdiscList(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(qdiscs) != 0 {
+ t.Fatal("Failed to remove qdisc")
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "net"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/vishvananda/netns"
+)
+
+func TestRouteAddDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 168, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ ip := net.IPv4(127, 1, 1, 1)
+ route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err := RouteList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("Route not added properly")
+ }
+
+ dstIP := net.IPv4(192, 168, 0, 42)
+ routeToDstIP, err := RouteGet(dstIP)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(routeToDstIP) == 0 {
+ t.Fatal("Default route not present")
+ }
+ if err := RouteDel(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err = RouteList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 0 {
+ t.Fatal("Route not removed properly")
+ }
+
+}
+
+func TestRouteReplace(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 168, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ ip := net.IPv4(127, 1, 1, 1)
+ route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err := RouteList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("Route not added properly")
+ }
+
+ ip = net.IPv4(127, 1, 1, 2)
+ route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
+ if err := RouteReplace(&route); err != nil {
+ t.Fatal(err)
+ }
+
+ routes, err = RouteList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(routes) != 1 || !routes[0].Src.Equal(ip) {
+ t.Fatal("Route not replaced properly")
+ }
+
+ if err := RouteDel(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err = RouteList(link, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 0 {
+ t.Fatal("Route not removed properly")
+ }
+
+}
+
+func TestRouteAddIncomplete(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err = LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ route := Route{LinkIndex: link.Attrs().Index}
+ if err := RouteAdd(&route); err == nil {
+ t.Fatal("Adding incomplete route should fail")
+ }
+}
+
+func expectRouteUpdate(ch <-chan RouteUpdate, t uint16, dst net.IP) bool {
+ for {
+ timeout := time.After(time.Minute)
+ select {
+ case update := <-ch:
+ if update.Type == t && update.Route.Dst.IP.Equal(dst) {
+ return true
+ }
+ case <-timeout:
+ return false
+ }
+ }
+}
+
+func TestRouteSubscribe(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ ch := make(chan RouteUpdate)
+ done := make(chan struct{})
+ defer close(done)
+ if err := RouteSubscribe(ch, done); err != nil {
+ t.Fatal(err)
+ }
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err = LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 168, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ ip := net.IPv4(127, 1, 1, 1)
+ route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectRouteUpdate(ch, syscall.RTM_NEWROUTE, dst.IP) {
+ t.Fatal("Add update not received as expected")
+ }
+ if err := RouteDel(&route); err != nil {
+ t.Fatal(err)
+ }
+ if !expectRouteUpdate(ch, syscall.RTM_DELROUTE, dst.IP) {
+ t.Fatal("Del update not received as expected")
+ }
+}
+
+func TestRouteSubscribeAt(t *testing.T) {
+ skipUnlessRoot(t)
+
+ // Create an handle on a custom netns
+ newNs, err := netns.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer newNs.Close()
+
+ nh, err := NewHandleAt(newNs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nh.Delete()
+
+ // Subscribe for Route events on the custom netns
+ ch := make(chan RouteUpdate)
+ done := make(chan struct{})
+ defer close(done)
+ if err := RouteSubscribeAt(newNs, ch, done); err != nil {
+ t.Fatal(err)
+ }
+
+ // get loopback interface
+ link, err := nh.LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err = nh.LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 169, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ ip := net.IPv4(127, 100, 1, 1)
+ route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
+ if err := nh.RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+
+ if !expectRouteUpdate(ch, syscall.RTM_NEWROUTE, dst.IP) {
+ t.Fatal("Add update not received as expected")
+ }
+ if err := nh.RouteDel(&route); err != nil {
+ t.Fatal(err)
+ }
+ if !expectRouteUpdate(ch, syscall.RTM_DELROUTE, dst.IP) {
+ t.Fatal("Del update not received as expected")
+ }
+}
+
+func TestRouteExtraFields(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // bring the interface up
+ if err = LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(1, 1, 1, 1),
+ Mask: net.CIDRMask(32, 32),
+ }
+
+ src := net.IPv4(127, 3, 3, 3)
+ route := Route{
+ LinkIndex: link.Attrs().Index,
+ Dst: dst,
+ Src: src,
+ Scope: syscall.RT_SCOPE_LINK,
+ Priority: 13,
+ Table: syscall.RT_TABLE_MAIN,
+ Type: syscall.RTN_UNICAST,
+ Tos: 14,
+ }
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err := RouteListFiltered(FAMILY_V4, &Route{
+ Dst: dst,
+ Src: src,
+ Scope: syscall.RT_SCOPE_LINK,
+ Table: syscall.RT_TABLE_MAIN,
+ Type: syscall.RTN_UNICAST,
+ Tos: 14,
+ }, RT_FILTER_DST|RT_FILTER_SRC|RT_FILTER_SCOPE|RT_FILTER_TABLE|RT_FILTER_TYPE|RT_FILTER_TOS)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("Route not added properly")
+ }
+
+ if routes[0].Scope != syscall.RT_SCOPE_LINK {
+ t.Fatal("Invalid Scope. Route not added properly")
+ }
+ if routes[0].Priority != 13 {
+ t.Fatal("Invalid Priority. Route not added properly")
+ }
+ if routes[0].Table != syscall.RT_TABLE_MAIN {
+ t.Fatal("Invalid Scope. Route not added properly")
+ }
+ if routes[0].Type != syscall.RTN_UNICAST {
+ t.Fatal("Invalid Type. Route not added properly")
+ }
+ if routes[0].Tos != 14 {
+ t.Fatal("Invalid Tos. Route not added properly")
+ }
+}
+
+func TestRouteMultiPath(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // bring the interface up
+ if err = LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ // add a gateway route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 168, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ idx := link.Attrs().Index
+ route := Route{Dst: dst, MultiPath: []*NexthopInfo{&NexthopInfo{LinkIndex: idx}, &NexthopInfo{LinkIndex: idx}}}
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err := RouteList(nil, FAMILY_V4)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("MultiPath Route not added properly")
+ }
+ if len(routes[0].MultiPath) != 2 {
+ t.Fatal("MultiPath Route not added properly")
+ }
+}
+
+func TestFilterDefaultRoute(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // bring the interface up
+ if err = LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ address := &Addr{
+ IPNet: &net.IPNet{
+ IP: net.IPv4(127, 0, 0, 2),
+ Mask: net.CIDRMask(24, 32),
+ },
+ }
+ if err = AddrAdd(link, address); err != nil {
+ t.Fatal(err)
+ }
+
+ // Add default route
+ gw := net.IPv4(127, 0, 0, 2)
+
+ defaultRoute := Route{
+ Dst: nil,
+ Gw: gw,
+ }
+
+ if err := RouteAdd(&defaultRoute); err != nil {
+ t.Fatal(err)
+ }
+
+ // add an extra route
+ dst := &net.IPNet{
+ IP: net.IPv4(192, 168, 0, 0),
+ Mask: net.CIDRMask(24, 32),
+ }
+
+ extraRoute := Route{
+ Dst: dst,
+ Gw: gw,
+ }
+
+ if err := RouteAdd(&extraRoute); err != nil {
+ t.Fatal(err)
+ }
+ var filterTests = []struct {
+ filter *Route
+ mask uint64
+ expected net.IP
+ }{
+ {
+ &Route{Dst: nil},
+ RT_FILTER_DST,
+ gw,
+ },
+ {
+ &Route{Dst: dst},
+ RT_FILTER_DST,
+ gw,
+ },
+ }
+
+ for _, f := range filterTests {
+ routes, err := RouteListFiltered(FAMILY_V4, f.filter, f.mask)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("Route not filtered properly")
+ }
+ if !routes[0].Gw.Equal(gw) {
+ t.Fatal("Unexpected Gateway")
+ }
+ }
+
+}
+
+func TestMPLSRouteAddDel(t *testing.T) {
+ tearDown := setUpMPLSNetlinkTest(t)
+ defer tearDown()
+
+ // get loopback interface
+ link, err := LinkByName("lo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // bring the interface up
+ if err := LinkSetUp(link); err != nil {
+ t.Fatal(err)
+ }
+
+ mplsDst := 100
+ route := Route{
+ LinkIndex: link.Attrs().Index,
+ MPLSDst: &mplsDst,
+ NewDst: &MPLSDestination{
+ Labels: []int{200, 300},
+ },
+ }
+ if err := RouteAdd(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err := RouteList(link, FAMILY_MPLS)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 1 {
+ t.Fatal("Route not added properly")
+ }
+
+ if err := RouteDel(&route); err != nil {
+ t.Fatal(err)
+ }
+ routes, err = RouteList(link, FAMILY_MPLS)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(routes) != 0 {
+ t.Fatal("Route not removed properly")
+ }
+
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "net"
+ "syscall"
+ "testing"
+)
+
+func TestRuleAddDel(t *testing.T) {
+ skipUnlessRoot(t)
+
+ srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)}
+ dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)}
+
+ rulesBegin, err := RuleList(syscall.AF_INET)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rule := NewRule()
+ rule.Table = syscall.RT_TABLE_MAIN
+ rule.Src = srcNet
+ rule.Dst = dstNet
+ rule.Priority = 5
+ rule.OifName = "lo"
+ rule.IifName = "lo"
+ if err := RuleAdd(rule); err != nil {
+ t.Fatal(err)
+ }
+
+ rules, err := RuleList(syscall.AF_INET)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(rules) != len(rulesBegin)+1 {
+ t.Fatal("Rule not added properly")
+ }
+
+ // find this rule
+ var found bool
+ for i := range rules {
+ if rules[i].Table == rule.Table &&
+ rules[i].Src != nil && rules[i].Src.String() == srcNet.String() &&
+ rules[i].Dst != nil && rules[i].Dst.String() == dstNet.String() &&
+ rules[i].OifName == rule.OifName &&
+ rules[i].Priority == rule.Priority &&
+ rules[i].IifName == rule.IifName {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatal("Rule has diffrent options than one added")
+ }
+
+ if err := RuleDel(rule); err != nil {
+ t.Fatal(err)
+ }
+
+ rulesEnd, err := RuleList(syscall.AF_INET)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(rulesEnd) != len(rulesBegin) {
+ t.Fatal("Rule not removed properly")
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "log"
+ "net"
+ "os"
+ "os/user"
+ "strconv"
+ "testing"
+)
+
+func TestSocketGet(t *testing.T) {
+ if os.Getenv("TRAVIS_BUILD_DIR") != "" {
+ t.Skipf("Goroutines + network namespaces == inconsistent results")
+ }
+ addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
+ if err != nil {
+ log.Fatal(err)
+ }
+ l, err := net.ListenTCP("tcp", addr)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ conn, err := net.Dial(l.Addr().Network(), l.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer conn.Close()
+
+ localAddr := conn.LocalAddr().(*net.TCPAddr)
+ remoteAddr := conn.RemoteAddr().(*net.TCPAddr)
+ socket, err := SocketGet(localAddr, remoteAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if got, want := socket.ID.Source, localAddr.IP; !got.Equal(want) {
+ t.Fatalf("local ip = %v, want %v", got, want)
+ }
+ if got, want := socket.ID.Destination, remoteAddr.IP; !got.Equal(want) {
+ t.Fatalf("remote ip = %v, want %v", got, want)
+ }
+ if got, want := int(socket.ID.SourcePort), localAddr.Port; got != want {
+ t.Fatalf("local port = %d, want %d", got, want)
+ }
+ if got, want := int(socket.ID.DestinationPort), remoteAddr.Port; got != want {
+ t.Fatalf("remote port = %d, want %d", got, want)
+ }
+ u, err := user.Current()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := strconv.Itoa(int(socket.UID)), u.Uid; got != want {
+ t.Fatalf("UID = %s, want %s", got, want)
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "testing"
+
+ "github.com/vishvananda/netlink/nl"
+)
+
+func TestXfrmMonitorExpire(t *testing.T) {
+ setUpNetlinkTest(t)()
+
+ ch := make(chan XfrmMsg)
+ done := make(chan struct{})
+ defer close(done)
+ errChan := make(chan error)
+ if err := XfrmMonitor(ch, nil, errChan, nl.XFRM_MSG_EXPIRE); err != nil {
+ t.Fatal(err)
+ }
+
+ // Program state with limits
+ state := getBaseState()
+ state.Limits.TimeHard = 2
+ state.Limits.TimeSoft = 1
+ if err := XfrmStateAdd(state); err != nil {
+ t.Fatal(err)
+ }
+
+ msg := (<-ch).(*XfrmMsgExpire)
+ if msg.XfrmState.Spi != state.Spi || msg.Hard {
+ t.Fatal("Received unexpected msg")
+ }
+
+ msg = (<-ch).(*XfrmMsgExpire)
+ if msg.XfrmState.Spi != state.Spi || !msg.Hard {
+ t.Fatal("Received unexpected msg")
+ }
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "bytes"
+ "net"
+ "testing"
+)
+
+const zeroCIDR = "0.0.0.0/0"
+
+func TestXfrmPolicyAddUpdateDel(t *testing.T) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+
+ policy := getPolicy()
+ if err := XfrmPolicyAdd(policy); err != nil {
+ t.Fatal(err)
+ }
+ policies, err := XfrmPolicyList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(policies) != 1 {
+ t.Fatal("Policy not added properly")
+ }
+
+ if !comparePolicies(policy, &policies[0]) {
+ t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", policy, policies[0])
+ }
+
+ // Look for a specific policy
+ sp, err := XfrmPolicyGet(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !comparePolicies(policy, sp) {
+ t.Fatalf("unexpected policy returned")
+ }
+
+ // Modify the policy
+ policy.Priority = 100
+ if err := XfrmPolicyUpdate(policy); err != nil {
+ t.Fatal(err)
+ }
+ sp, err = XfrmPolicyGet(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if sp.Priority != 100 {
+ t.Fatalf("failed to modify the policy")
+ }
+
+ if err = XfrmPolicyDel(policy); err != nil {
+ t.Fatal(err)
+ }
+
+ policies, err = XfrmPolicyList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(policies) != 0 {
+ t.Fatal("Policy not removed properly")
+ }
+
+ // Src and dst are not mandatory field. Creation should succeed
+ policy.Src = nil
+ policy.Dst = nil
+ if err = XfrmPolicyAdd(policy); err != nil {
+ t.Fatal(err)
+ }
+
+ sp, err = XfrmPolicyGet(policy)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !comparePolicies(policy, sp) {
+ t.Fatalf("unexpected policy returned")
+ }
+
+ if err = XfrmPolicyDel(policy); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := XfrmPolicyGet(policy); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+}
+
+func TestXfrmPolicyFlush(t *testing.T) {
+ setUpNetlinkTest(t)()
+
+ p1 := getPolicy()
+ if err := XfrmPolicyAdd(p1); err != nil {
+ t.Fatal(err)
+ }
+
+ p1.Dir = XFRM_DIR_IN
+ s := p1.Src
+ p1.Src = p1.Dst
+ p1.Dst = s
+ if err := XfrmPolicyAdd(p1); err != nil {
+ t.Fatal(err)
+ }
+
+ policies, err := XfrmPolicyList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(policies) != 2 {
+ t.Fatalf("unexpected number of policies: %d", len(policies))
+ }
+
+ if err := XfrmPolicyFlush(); err != nil {
+ t.Fatal(err)
+ }
+
+ policies, err = XfrmPolicyList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(policies) != 0 {
+ t.Fatalf("unexpected number of policies: %d", len(policies))
+ }
+
+}
+
+func comparePolicies(a, b *XfrmPolicy) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ // Do not check Index which is assigned by kernel
+ return a.Dir == b.Dir && a.Priority == b.Priority &&
+ compareIPNet(a.Src, b.Src) && compareIPNet(a.Dst, b.Dst) &&
+ a.Mark.Value == b.Mark.Value && a.Mark.Mask == b.Mark.Mask &&
+ compareTemplates(a.Tmpls, b.Tmpls)
+}
+
+func compareTemplates(a, b []XfrmPolicyTmpl) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, ta := range a {
+ tb := b[i]
+ if !ta.Dst.Equal(tb.Dst) || !ta.Src.Equal(tb.Src) || ta.Spi != tb.Spi ||
+ ta.Mode != tb.Mode || ta.Reqid != tb.Reqid || ta.Proto != tb.Proto {
+ return false
+ }
+ }
+ return true
+}
+
+func compareIPNet(a, b *net.IPNet) bool {
+ if a == b {
+ return true
+ }
+ // For unspecified src/dst parseXfrmPolicy would set the zero address cidr
+ if (a == nil && b.String() == zeroCIDR) || (b == nil && a.String() == zeroCIDR) {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
+}
+
+func getPolicy() *XfrmPolicy {
+ src, _ := ParseIPNet("127.1.1.1/32")
+ dst, _ := ParseIPNet("127.1.1.2/32")
+ policy := &XfrmPolicy{
+ Src: src,
+ Dst: dst,
+ Proto: 17,
+ DstPort: 1234,
+ SrcPort: 5678,
+ Dir: XFRM_DIR_OUT,
+ Mark: &XfrmMark{
+ Value: 0xabff22,
+ Mask: 0xffffffff,
+ },
+ Priority: 10,
+ }
+ tmpl := XfrmPolicyTmpl{
+ Src: net.ParseIP("127.0.0.1"),
+ Dst: net.ParseIP("127.0.0.2"),
+ Proto: XFRM_PROTO_ESP,
+ Mode: XFRM_MODE_TUNNEL,
+ Spi: 0xabcdef99,
+ }
+ policy.Tmpls = append(policy.Tmpls, tmpl)
+ return policy
+}
--- /dev/null
+// +build linux
+
+package netlink
+
+import (
+ "bytes"
+ "encoding/hex"
+ "net"
+ "testing"
+)
+
+func TestXfrmStateAddGetDel(t *testing.T) {
+ for _, s := range []*XfrmState{getBaseState(), getAeadState()} {
+ testXfrmStateAddGetDel(t, s)
+ }
+}
+
+func testXfrmStateAddGetDel(t *testing.T, state *XfrmState) {
+ tearDown := setUpNetlinkTest(t)
+ defer tearDown()
+ if err := XfrmStateAdd(state); err != nil {
+ t.Fatal(err)
+ }
+ states, err := XfrmStateList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(states) != 1 {
+ t.Fatal("State not added properly")
+ }
+
+ if !compareStates(state, &states[0]) {
+ t.Fatalf("unexpected states returned")
+ }
+
+ // Get specific state
+ sa, err := XfrmStateGet(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !compareStates(state, sa) {
+ t.Fatalf("unexpected state returned")
+ }
+
+ if err = XfrmStateDel(state); err != nil {
+ t.Fatal(err)
+ }
+
+ states, err = XfrmStateList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(states) != 0 {
+ t.Fatal("State not removed properly")
+ }
+
+ if _, err := XfrmStateGet(state); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+}
+
+func TestXfrmStateAllocSpi(t *testing.T) {
+ setUpNetlinkTest(t)()
+
+ state := getBaseState()
+ state.Spi = 0
+ state.Auth = nil
+ state.Crypt = nil
+ rstate, err := XfrmStateAllocSpi(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if rstate.Spi == 0 {
+ t.Fatalf("SPI is not allocated")
+ }
+ rstate.Spi = 0
+ if !compareStates(state, rstate) {
+ t.Fatalf("State not properly allocated")
+ }
+}
+
+func TestXfrmStateFlush(t *testing.T) {
+ setUpNetlinkTest(t)()
+
+ state1 := getBaseState()
+ state2 := getBaseState()
+ state2.Src = net.ParseIP("127.1.0.1")
+ state2.Dst = net.ParseIP("127.1.0.2")
+ state2.Proto = XFRM_PROTO_AH
+ state2.Mode = XFRM_MODE_TUNNEL
+ state2.Spi = 20
+ state2.Mark = nil
+ state2.Crypt = nil
+
+ if err := XfrmStateAdd(state1); err != nil {
+ t.Fatal(err)
+ }
+ if err := XfrmStateAdd(state2); err != nil {
+ t.Fatal(err)
+ }
+
+ // flushing proto for which no state is present should return silently
+ if err := XfrmStateFlush(XFRM_PROTO_COMP); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := XfrmStateFlush(XFRM_PROTO_AH); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := XfrmStateGet(state2); err == nil {
+ t.Fatalf("Unexpected success")
+ }
+
+ if err := XfrmStateAdd(state2); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := XfrmStateFlush(0); err != nil {
+ t.Fatal(err)
+ }
+
+ states, err := XfrmStateList(FAMILY_ALL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(states) != 0 {
+ t.Fatal("State not flushed properly")
+ }
+
+}
+
+func TestXfrmStateUpdateLimits(t *testing.T) {
+ setUpNetlinkTest(t)()
+
+ // Program state with limits
+ state := getBaseState()
+ state.Limits.TimeHard = 3600
+ state.Limits.TimeSoft = 60
+ state.Limits.PacketHard = 1000
+ state.Limits.PacketSoft = 50
+ state.Limits.ByteHard = 1000000
+ state.Limits.ByteSoft = 50000
+ state.Limits.TimeUseHard = 3000
+ state.Limits.TimeUseSoft = 1500
+ if err := XfrmStateAdd(state); err != nil {
+ t.Fatal(err)
+ }
+ // Verify limits
+ s, err := XfrmStateGet(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !compareLimits(state, s) {
+ t.Fatalf("Incorrect time hard/soft retrieved: %s", s.Print(true))
+ }
+
+ // Update limits
+ state.Limits.TimeHard = 1800
+ state.Limits.TimeSoft = 30
+ state.Limits.PacketHard = 500
+ state.Limits.PacketSoft = 25
+ state.Limits.ByteHard = 500000
+ state.Limits.ByteSoft = 25000
+ state.Limits.TimeUseHard = 2000
+ state.Limits.TimeUseSoft = 1000
+ if err := XfrmStateUpdate(state); err != nil {
+ t.Fatal(err)
+ }
+
+ // Verify new limits
+ s, err = XfrmStateGet(state)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s.Limits.TimeHard != 1800 || s.Limits.TimeSoft != 30 {
+ t.Fatalf("Incorrect time hard retrieved: (%d, %d)", s.Limits.TimeHard, s.Limits.TimeSoft)
+ }
+}
+
+func getBaseState() *XfrmState {
+ return &XfrmState{
+ // Force 4 byte notation for the IPv4 addresses
+ Src: net.ParseIP("127.0.0.1").To4(),
+ Dst: net.ParseIP("127.0.0.2").To4(),
+ Proto: XFRM_PROTO_ESP,
+ Mode: XFRM_MODE_TUNNEL,
+ Spi: 1,
+ Auth: &XfrmStateAlgo{
+ Name: "hmac(sha256)",
+ Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"),
+ },
+ Crypt: &XfrmStateAlgo{
+ Name: "cbc(aes)",
+ Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"),
+ },
+ Mark: &XfrmMark{
+ Value: 0x12340000,
+ Mask: 0xffff0000,
+ },
+ }
+}
+
+func getAeadState() *XfrmState {
+ // 128 key bits + 32 salt bits
+ k, _ := hex.DecodeString("d0562776bf0e75830ba3f7f8eb6c09b555aa1177")
+ return &XfrmState{
+ // Leave IPv4 addresses in Ipv4 in IPv6 notation
+ Src: net.ParseIP("192.168.1.1"),
+ Dst: net.ParseIP("192.168.2.2"),
+ Proto: XFRM_PROTO_ESP,
+ Mode: XFRM_MODE_TUNNEL,
+ Spi: 2,
+ Aead: &XfrmStateAlgo{
+ Name: "rfc4106(gcm(aes))",
+ Key: k,
+ ICVLen: 64,
+ },
+ }
+}
+
+func compareStates(a, b *XfrmState) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.Src.Equal(b.Src) && a.Dst.Equal(b.Dst) &&
+ a.Mode == b.Mode && a.Spi == b.Spi && a.Proto == b.Proto &&
+ compareAlgo(a.Auth, b.Auth) &&
+ compareAlgo(a.Crypt, b.Crypt) &&
+ compareAlgo(a.Aead, b.Aead) &&
+ compareMarks(a.Mark, b.Mark)
+}
+
+func compareLimits(a, b *XfrmState) bool {
+ return a.Limits.TimeHard == b.Limits.TimeHard &&
+ a.Limits.TimeSoft == b.Limits.TimeSoft &&
+ a.Limits.PacketHard == b.Limits.PacketHard &&
+ a.Limits.PacketSoft == b.Limits.PacketSoft &&
+ a.Limits.ByteHard == b.Limits.ByteHard &&
+ a.Limits.ByteSoft == b.Limits.ByteSoft &&
+ a.Limits.TimeUseHard == b.Limits.TimeUseHard &&
+ a.Limits.TimeUseSoft == b.Limits.TimeUseSoft
+}
+
+func compareAlgo(a, b *XfrmStateAlgo) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.Name == b.Name && bytes.Equal(a.Key, b.Key) &&
+ (a.TruncateLen == 0 || a.TruncateLen == b.TruncateLen) &&
+ (a.ICVLen == 0 || a.ICVLen == b.ICVLen)
+}
+
+func compareMarks(a, b *XfrmMark) bool {
+ if a == b {
+ return true
+ }
+ if a == nil || b == nil {
+ return false
+ }
+ return a.Value == b.Value && a.Mask == b.Mask
+}