├── .github ├── scripts │ └── modprobe.sh └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── addr.go ├── addr_linux.go ├── addr_test.go ├── bpf_linux.go ├── bridge_linux.go ├── bridge_linux_test.go ├── chain.go ├── chain_linux.go ├── chain_test.go ├── class.go ├── class_linux.go ├── class_test.go ├── cmd └── ipset-test │ └── main.go ├── conntrack_linux.go ├── conntrack_test.go ├── conntrack_unspecified.go ├── devlink_linux.go ├── devlink_test.go ├── filter.go ├── filter_linux.go ├── filter_test.go ├── fou.go ├── fou_linux.go ├── fou_test.go ├── fou_unspecified.go ├── genetlink_linux.go ├── genetlink_unspecified.go ├── go.mod ├── go.sum ├── gtp_linux.go ├── gtp_test.go ├── handle_linux.go ├── handle_linux_test.go ├── handle_test.go ├── handle_unspecified.go ├── inet_diag.go ├── ioctl_linux.go ├── ipset_linux.go ├── ipset_linux_test.go ├── link.go ├── link_linux.go ├── link_test.go ├── link_tuntap_linux.go ├── neigh.go ├── neigh_linux.go ├── neigh_test.go ├── netlink.go ├── netlink_linux.go ├── netlink_test.go ├── netlink_unspecified.go ├── netns_linux.go ├── netns_test.go ├── netns_unspecified.go ├── nl ├── addr_linux.go ├── addr_linux_test.go ├── bridge_linux.go ├── bridge_linux_test.go ├── conntrack_linux.go ├── devlink_linux.go ├── genetlink_linux.go ├── ip6tnl_linux.go ├── ipset_linux.go ├── link_linux.go ├── link_linux_test.go ├── lwt_linux.go ├── mpls_linux.go ├── nl_linux.go ├── nl_linux_test.go ├── nl_unspecified.go ├── parse_attr_linux.go ├── rdma_link_linux.go ├── route_linux.go ├── route_linux_test.go ├── seg6_linux.go ├── seg6local_linux.go ├── syscall.go ├── tc_linux.go ├── tc_linux_test.go ├── vdpa_linux.go ├── xfrm_linux.go ├── xfrm_linux_test.go ├── xfrm_monitor_linux.go ├── xfrm_monitor_linux_test.go ├── xfrm_policy_linux.go ├── xfrm_policy_linux_test.go ├── xfrm_state_linux.go └── xfrm_state_linux_test.go ├── order.go ├── proc_event_linux.go ├── proc_event_test.go ├── protinfo.go ├── protinfo_linux.go ├── protinfo_test.go ├── qdisc.go ├── qdisc_linux.go ├── qdisc_test.go ├── rdma_link_linux.go ├── rdma_link_test.go ├── route.go ├── route_linux.go ├── route_test.go ├── route_unspecified.go ├── rule.go ├── rule_linux.go ├── rule_nonlinux.go ├── rule_test.go ├── socket.go ├── socket_linux.go ├── socket_linux_test.go ├── socket_test.go ├── socket_xdp_linux.go ├── socket_xdp_linux_test.go ├── tcp.go ├── tcp_linux.go ├── tcp_linux_test.go ├── testdata ├── ipset_list_result └── ipset_protocol_result ├── unix_diag.go ├── vdpa_linux.go ├── vdpa_linux_test.go ├── virtio.go ├── xdp_diag.go ├── xdp_linux.go ├── xfrm_linux.go ├── xfrm_monitor_linux.go ├── xfrm_monitor_linux_test.go ├── xfrm_policy_linux.go ├── xfrm_policy_linux_test.go ├── xfrm_state_linux.go ├── xfrm_state_linux_test.go └── xfrm_unspecified.go /.github/scripts/modprobe.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo modprobe ip_gre 3 | sudo modprobe nf_conntrack 4 | sudo modprobe nf_conntrack_netlink 5 | # these modules not available 6 | # sudo modprobe nf_conntrack_ipv4 7 | # sudo modprobe nf_conntrack_ipv6 8 | sudo modprobe sch_hfsc 9 | sudo modprobe sch_sfq 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.22 20 | 21 | - name: Kernel Modules 22 | run: ./.github/scripts/modprobe.sh 23 | shell: bash 24 | 25 | - name: Test 26 | run: sudo -E env PATH=$PATH go test -v ./ ./nl 27 | 28 | build-macos: 29 | # netlink is Linux-only, but this ensures that netlink builds without error 30 | # on macOS, which helps catch missing build tags. 31 | runs-on: macos-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - name: Set up Go 36 | uses: actions/setup-go@v2 37 | with: 38 | go-version: 1.22 39 | 40 | - name: Build 41 | run: go build ./... 42 | 43 | - name: Test 44 | run: go test ./... 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 (2018-03-15) 4 | 5 | Initial release tagging -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIRS := \ 2 | . \ 3 | nl 4 | 5 | DEPS = \ 6 | github.com/vishvananda/netns \ 7 | golang.org/x/sys/unix 8 | 9 | uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) 10 | testdirs = $(call uniq,$(foreach d,$(1),$(dir $(wildcard $(d)/*_test.go)))) 11 | goroot = $(addprefix ../../../,$(1)) 12 | unroot = $(subst ../../../,,$(1)) 13 | fmt = $(addprefix fmt-,$(1)) 14 | 15 | all: test 16 | 17 | $(call goroot,$(DEPS)): 18 | go get $(call unroot,$@) 19 | 20 | .PHONY: $(call testdirs,$(DIRS)) 21 | $(call testdirs,$(DIRS)): 22 | go test -test.exec sudo -test.parallel 4 -timeout 60s -test.v github.com/vishvananda/netlink/$@ 23 | 24 | $(call fmt,$(call testdirs,$(DIRS))): 25 | ! gofmt -l $(subst fmt-,,$@)/*.go | grep -q . 26 | 27 | .PHONY: fmt 28 | fmt: $(call fmt,$(call testdirs,$(DIRS))) 29 | 30 | test: fmt $(call goroot,$(DEPS)) $(call testdirs,$(DIRS)) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netlink - netlink library for go # 2 | 3 | ![Build Status](https://github.com/vishvananda/netlink/actions/workflows/main.yml/badge.svg) [![GoDoc](https://godoc.org/github.com/vishvananda/netlink?status.svg)](https://godoc.org/github.com/vishvananda/netlink) 4 | 5 | The netlink package provides a simple netlink library for go. Netlink 6 | is the interface a user-space program in linux uses to communicate with 7 | the kernel. It can be used to add and remove interfaces, set ip addresses 8 | and routes, and configure ipsec. Netlink communication requires elevated 9 | privileges, so in most cases this code needs to be run as root. Since 10 | low-level netlink messages are inscrutable at best, the library attempts 11 | to provide an api that is loosely modeled on the CLI provided by iproute2. 12 | Actions like `ip link add` will be accomplished via a similarly named 13 | function like AddLink(). This library began its life as a fork of the 14 | netlink functionality in 15 | [docker/libcontainer](https://github.com/docker/libcontainer) but was 16 | heavily rewritten to improve testability, performance, and to add new 17 | functionality like ipsec xfrm handling. 18 | 19 | ## Local Build and Test ## 20 | 21 | You can use go get command: 22 | 23 | go get github.com/vishvananda/netlink 24 | 25 | Testing dependencies: 26 | 27 | go get github.com/vishvananda/netns 28 | 29 | Testing (requires root): 30 | 31 | sudo -E go test github.com/vishvananda/netlink 32 | 33 | ## Examples ## 34 | 35 | Add a new bridge and add eth1 into it: 36 | 37 | ```go 38 | package main 39 | 40 | import ( 41 | "fmt" 42 | "github.com/vishvananda/netlink" 43 | ) 44 | 45 | func main() { 46 | la := netlink.NewLinkAttrs() 47 | la.Name = "foo" 48 | mybridge := &netlink.Bridge{LinkAttrs: la} 49 | err := netlink.LinkAdd(mybridge) 50 | if err != nil { 51 | fmt.Printf("could not add %s: %v\n", la.Name, err) 52 | } 53 | eth1, _ := netlink.LinkByName("eth1") 54 | netlink.LinkSetMaster(eth1, mybridge) 55 | } 56 | 57 | ``` 58 | Note `NewLinkAttrs` constructor, it sets default values in structure. For now 59 | it sets only `TxQLen` to `-1`, so kernel will set default by itself. If you're 60 | using simple initialization(`LinkAttrs{Name: "foo"}`) `TxQLen` will be set to 61 | `0` unless you specify it like `LinkAttrs{Name: "foo", TxQLen: 1000}`. 62 | 63 | Add a new ip address to loopback: 64 | 65 | ```go 66 | package main 67 | 68 | import ( 69 | "github.com/vishvananda/netlink" 70 | ) 71 | 72 | func main() { 73 | lo, _ := netlink.LinkByName("lo") 74 | addr, _ := netlink.ParseAddr("169.254.169.254/32") 75 | netlink.AddrAdd(lo, addr) 76 | } 77 | 78 | ``` 79 | 80 | ## Future Work ## 81 | 82 | Many pieces of netlink are not yet fully supported in the high-level 83 | interface. Aspects of virtually all of the high-level objects don't exist. 84 | Many of the underlying primitives are there, so its a matter of putting 85 | the right fields into the high-level objects and making sure that they 86 | are serialized and deserialized correctly in the Add and List methods. 87 | 88 | There are also a few pieces of low level netlink functionality that still 89 | need to be implemented. Routing rules are not in place and some of the 90 | more advanced link types. Hopefully there is decent structure and testing 91 | in place to make these fairly straightforward to add. 92 | 93 | -------------------------------------------------------------------------------- /addr.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | // Addr represents an IP address from netlink. Netlink ip addresses 10 | // include a mask, so it stores the address as a net.IPNet. 11 | type Addr struct { 12 | *net.IPNet 13 | Label string 14 | Flags int 15 | Scope int 16 | Peer *net.IPNet 17 | Broadcast net.IP 18 | PreferedLft int 19 | ValidLft int 20 | LinkIndex int 21 | } 22 | 23 | // String returns $ip/$netmask $label 24 | func (a Addr) String() string { 25 | return strings.TrimSpace(fmt.Sprintf("%s %s", a.IPNet, a.Label)) 26 | } 27 | 28 | // ParseAddr parses the string representation of an address in the 29 | // form $ip/$netmask $label. The label portion is optional 30 | func ParseAddr(s string) (*Addr, error) { 31 | label := "" 32 | parts := strings.Split(s, " ") 33 | if len(parts) > 1 { 34 | s = parts[0] 35 | label = parts[1] 36 | } 37 | m, err := ParseIPNet(s) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return &Addr{IPNet: m, Label: label}, nil 42 | } 43 | 44 | // Equal returns true if both Addrs have the same net.IPNet value. 45 | func (a Addr) Equal(x Addr) bool { 46 | sizea, _ := a.Mask.Size() 47 | sizeb, _ := x.Mask.Size() 48 | // ignore label for comparison 49 | return a.IP.Equal(x.IP) && sizea == sizeb 50 | } 51 | 52 | func (a Addr) PeerEqual(x Addr) bool { 53 | sizea, _ := a.Peer.Mask.Size() 54 | sizeb, _ := x.Peer.Mask.Size() 55 | // ignore label for comparison 56 | return a.Peer.IP.Equal(x.Peer.IP) && sizea == sizeb 57 | } 58 | -------------------------------------------------------------------------------- /bpf_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | type BpfProgType uint32 10 | 11 | const ( 12 | BPF_PROG_TYPE_UNSPEC BpfProgType = iota 13 | BPF_PROG_TYPE_SOCKET_FILTER 14 | BPF_PROG_TYPE_KPROBE 15 | BPF_PROG_TYPE_SCHED_CLS 16 | BPF_PROG_TYPE_SCHED_ACT 17 | BPF_PROG_TYPE_TRACEPOINT 18 | BPF_PROG_TYPE_XDP 19 | BPF_PROG_TYPE_PERF_EVENT 20 | BPF_PROG_TYPE_CGROUP_SKB 21 | BPF_PROG_TYPE_CGROUP_SOCK 22 | BPF_PROG_TYPE_LWT_IN 23 | BPF_PROG_TYPE_LWT_OUT 24 | BPF_PROG_TYPE_LWT_XMIT 25 | BPF_PROG_TYPE_SOCK_OPS 26 | BPF_PROG_TYPE_SK_SKB 27 | BPF_PROG_TYPE_CGROUP_DEVICE 28 | BPF_PROG_TYPE_SK_MSG 29 | BPF_PROG_TYPE_RAW_TRACEPOINT 30 | BPF_PROG_TYPE_CGROUP_SOCK_ADDR 31 | BPF_PROG_TYPE_LWT_SEG6LOCAL 32 | BPF_PROG_TYPE_LIRC_MODE2 33 | BPF_PROG_TYPE_SK_REUSEPORT 34 | BPF_PROG_TYPE_FLOW_DISSECTOR 35 | BPF_PROG_TYPE_CGROUP_SYSCTL 36 | BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE 37 | BPF_PROG_TYPE_CGROUP_SOCKOPT 38 | BPF_PROG_TYPE_TRACING 39 | BPF_PROG_TYPE_STRUCT_OPS 40 | BPF_PROG_TYPE_EXT 41 | BPF_PROG_TYPE_LSM 42 | BPF_PROG_TYPE_SK_LOOKUP 43 | ) 44 | 45 | type BPFAttr struct { 46 | ProgType uint32 47 | InsnCnt uint32 48 | Insns uintptr 49 | License uintptr 50 | LogLevel uint32 51 | LogSize uint32 52 | LogBuf uintptr 53 | KernVersion uint32 54 | } 55 | 56 | // loadSimpleBpf loads a trivial bpf program for testing purposes. 57 | func loadSimpleBpf(progType BpfProgType, ret uint32) (int, error) { 58 | insns := []uint64{ 59 | 0x00000000000000b7 | (uint64(ret) << 32), 60 | 0x0000000000000095, 61 | } 62 | license := []byte{'A', 'S', 'L', '2', '\x00'} 63 | attr := BPFAttr{ 64 | ProgType: uint32(progType), 65 | InsnCnt: uint32(len(insns)), 66 | Insns: uintptr(unsafe.Pointer(&insns[0])), 67 | License: uintptr(unsafe.Pointer(&license[0])), 68 | } 69 | fd, _, errno := unix.Syscall(unix.SYS_BPF, 70 | 5, /* bpf cmd */ 71 | uintptr(unsafe.Pointer(&attr)), 72 | unsafe.Sizeof(attr)) 73 | if errno != 0 { 74 | return 0, errno 75 | } 76 | return int(fd), nil 77 | } 78 | -------------------------------------------------------------------------------- /bridge_linux_test.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestBridgeVlan(t *testing.T) { 10 | minKernelRequired(t, 3, 10) 11 | 12 | tearDown := setUpNetlinkTest(t) 13 | defer tearDown() 14 | if err := remountSysfs(); err != nil { 15 | t.Fatal(err) 16 | } 17 | bridgeName := "foo" 18 | bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} 19 | if err := LinkAdd(bridge); err != nil { 20 | t.Fatal(err) 21 | } 22 | if err := ioutil.WriteFile(fmt.Sprintf("/sys/devices/virtual/net/%s/bridge/vlan_filtering", bridgeName), []byte("1"), 0644); err != nil { 23 | t.Fatal(err) 24 | } 25 | if vlanMap, err := BridgeVlanList(); err != nil { 26 | t.Fatal(err) 27 | } else { 28 | if len(vlanMap) != 1 { 29 | t.Fatal() 30 | } 31 | if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok { 32 | t.Fatal("vlanMap should include foo port vlan info") 33 | } else { 34 | if len(vInfo) != 1 { 35 | t.Fatal() 36 | } else { 37 | if !vInfo[0].EngressUntag() || !vInfo[0].PortVID() || vInfo[0].Vid != 1 { 38 | t.Fatalf("bridge vlan show get wrong return %s", vInfo[0].String()) 39 | } 40 | } 41 | } 42 | } 43 | dummy := &Dummy{LinkAttrs: LinkAttrs{Name: "dum1"}} 44 | if err := LinkAdd(dummy); err != nil { 45 | t.Fatal(err) 46 | } 47 | if err := LinkSetMaster(dummy, bridge); err != nil { 48 | t.Fatal(err) 49 | } 50 | if err := BridgeVlanAdd(dummy, 2, false, false, false, false); err != nil { 51 | t.Fatal(err) 52 | } 53 | if err := BridgeVlanAdd(dummy, 3, true, true, false, false); err != nil { 54 | t.Fatal(err) 55 | } 56 | if err := BridgeVlanAddRange(dummy, 4, 6, false, false, false, false); err != nil { 57 | t.Fatal(err) 58 | } 59 | if vlanMap, err := BridgeVlanList(); err != nil { 60 | t.Fatal(err) 61 | } else { 62 | if len(vlanMap) != 2 { 63 | t.Fatal() 64 | } 65 | if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok { 66 | t.Fatal("vlanMap should include foo port vlan info") 67 | } else { 68 | if fmt.Sprintf("%v", vInfo) != "[{Flags:6 Vid:1}]" { 69 | t.Fatalf("unexpected result %v", vInfo) 70 | } 71 | } 72 | if vInfo, ok := vlanMap[int32(dummy.Index)]; !ok { 73 | t.Fatal("vlanMap should include dum1 port vlan info") 74 | } else { 75 | if fmt.Sprintf("%v", vInfo) != "[{Flags:4 Vid:1} {Flags:0 Vid:2} {Flags:6 Vid:3} {Flags:0 Vid:4} {Flags:0 Vid:5} {Flags:0 Vid:6}]" { 76 | t.Fatalf("unexpected result %v", vInfo) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func TestBridgeVlanTunnelInfo(t *testing.T) { 83 | minKernelRequired(t, 4, 11) 84 | tearDown := setUpNetlinkTest(t) 85 | defer tearDown() 86 | 87 | if err := remountSysfs(); err != nil { 88 | t.Fatal(err) 89 | } 90 | bridgeName := "br0" 91 | vxlanName := "vxlan0" 92 | 93 | // ip link add br0 type bridge 94 | bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} 95 | if err := LinkAdd(bridge); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | // ip link add vxlan0 type vxlan dstport 4789 nolearning external local 10.0.1.1 100 | vxlan := &Vxlan{ 101 | // local 102 | SrcAddr: []byte("10.0.1.1"), 103 | Learning: false, 104 | // external 105 | FlowBased: true, 106 | // dstport 107 | Port: 4789, 108 | LinkAttrs: LinkAttrs{Name: vxlanName}, 109 | } 110 | if err := LinkAdd(vxlan); err != nil { 111 | t.Fatal(err) 112 | } 113 | 114 | // ip link set dev vxlan0 master br0 115 | if err := LinkSetMaster(vxlan, bridge); err != nil { 116 | t.Fatal(err) 117 | } 118 | 119 | // ip link set br0 type bridge vlan_filtering 1 120 | if err := BridgeSetVlanFiltering(bridge, true); err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | // bridge link set dev vxlan0 vlan_tunnel on 125 | if err := LinkSetVlanTunnel(vxlan, true); err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | p, err := LinkGetProtinfo(vxlan) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | if !p.VlanTunnel { 134 | t.Fatal("vlan tunnel should be enabled on vxlan device") 135 | } 136 | 137 | // bridge vlan add vid 10 dev vxlan0 138 | if err := BridgeVlanAdd(vxlan, 10, false, false, false, false); err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | // bridge vlan add vid 11 dev vxlan0 143 | if err := BridgeVlanAdd(vxlan, 11, false, false, false, false); err != nil { 144 | t.Fatal(err) 145 | } 146 | 147 | // bridge vlan add dev vxlan0 vid 10 tunnel_info id 20 148 | if err := BridgeVlanAddTunnelInfo(vxlan, 10, 20, false, false); err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | tis, err := BridgeVlanTunnelShow() 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | 157 | if len(tis) != 1 { 158 | t.Fatal("only one tunnel info") 159 | } 160 | ti := tis[0] 161 | if ti.TunId != 20 || ti.Vid != 10 { 162 | t.Fatal("unexpected result") 163 | } 164 | 165 | // bridge vlan del dev vxlan0 vid 10 tunnel_info id 20 166 | if err := BridgeVlanDelTunnelInfo(vxlan, 10, 20, false, false); err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | tis, err = BridgeVlanTunnelShow() 171 | if err != nil { 172 | t.Fatal(err) 173 | } 174 | 175 | if len(tis) != 0 { 176 | t.Fatal("tunnel info should have been deleted") 177 | } 178 | 179 | // bridge vlan add dev vxlan0 vid 10-11 tunnel_info id 20-21 180 | if err := BridgeVlanAddRangeTunnelInfoRange(vxlan, 10, 11, 20, 21, false, false); err != nil { 181 | t.Fatal(err) 182 | } 183 | 184 | tis, err = BridgeVlanTunnelShow() 185 | if err != nil { 186 | t.Fatal(err) 187 | } 188 | if len(tis) != 2 { 189 | t.Fatal("two tunnel info") 190 | } 191 | 192 | // bridge vlan del dev vxlan0 vid 10-11 tunnel_info id 20-21 193 | if err := BridgeVlanDelRangeTunnelInfoRange(vxlan, 10, 11, 20, 21, false, false); err != nil { 194 | t.Fatal(err) 195 | } 196 | 197 | tis, err = BridgeVlanTunnelShow() 198 | if err != nil { 199 | t.Fatal(err) 200 | } 201 | 202 | if len(tis) != 0 { 203 | t.Fatal("tunnel info should have been deleted") 204 | } 205 | } 206 | 207 | func TestBridgeGroupFwdMask(t *testing.T) { 208 | minKernelRequired(t, 4, 15) //minimal release for per-port group_fwd_mask 209 | tearDown := setUpNetlinkTest(t) 210 | defer tearDown() 211 | if err := remountSysfs(); err != nil { 212 | t.Fatal(err) 213 | } 214 | bridgeName := "foo" 215 | var mask uint16 = 0xfff0 216 | bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}, GroupFwdMask: &mask} 217 | if err := LinkAdd(bridge); err != nil { 218 | t.Fatal(err) 219 | } 220 | brlink, err := LinkByName(bridgeName) 221 | if err != nil { 222 | t.Fatal(err) 223 | } 224 | if *(brlink.(*Bridge).GroupFwdMask) != mask { 225 | t.Fatalf("created bridge has group_fwd_mask value %x, different from expected %x", 226 | *(brlink.(*Bridge).GroupFwdMask), mask) 227 | } 228 | dummyName := "dm1" 229 | dummy := &Dummy{LinkAttrs: LinkAttrs{Name: dummyName, MasterIndex: brlink.Attrs().Index}} 230 | if err := LinkAdd(dummy); err != nil { 231 | t.Fatal(err) 232 | } 233 | dmLink, err := LinkByName(dummyName) 234 | if err != nil { 235 | t.Fatal(err) 236 | } 237 | if err = LinkSetBRSlaveGroupFwdMask(dmLink, mask); err != nil { 238 | t.Fatal(err) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /chain.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Chain contains the attributes of a Chain 8 | type Chain struct { 9 | Parent uint32 10 | Chain uint32 11 | } 12 | 13 | func (c Chain) String() string { 14 | return fmt.Sprintf("{Parent: %d, Chain: %d}", c.Parent, c.Chain) 15 | } 16 | 17 | func NewChain(parent uint32, chain uint32) Chain { 18 | return Chain{ 19 | Parent: parent, 20 | Chain: chain, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chain_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/vishvananda/netlink/nl" 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // ChainDel will delete a chain from the system. 11 | func ChainDel(link Link, chain Chain) error { 12 | // Equivalent to: `tc chain del $chain` 13 | return pkgHandle.ChainDel(link, chain) 14 | } 15 | 16 | // ChainDel will delete a chain from the system. 17 | // Equivalent to: `tc chain del $chain` 18 | func (h *Handle) ChainDel(link Link, chain Chain) error { 19 | return h.chainModify(unix.RTM_DELCHAIN, 0, link, chain) 20 | } 21 | 22 | // ChainAdd will add a chain to the system. 23 | // Equivalent to: `tc chain add` 24 | func ChainAdd(link Link, chain Chain) error { 25 | return pkgHandle.ChainAdd(link, chain) 26 | } 27 | 28 | // ChainAdd will add a chain to the system. 29 | // Equivalent to: `tc chain add` 30 | func (h *Handle) ChainAdd(link Link, chain Chain) error { 31 | return h.chainModify( 32 | unix.RTM_NEWCHAIN, 33 | unix.NLM_F_CREATE|unix.NLM_F_EXCL, 34 | link, 35 | chain) 36 | } 37 | 38 | func (h *Handle) chainModify(cmd, flags int, link Link, chain Chain) error { 39 | req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) 40 | index := int32(0) 41 | if link != nil { 42 | base := link.Attrs() 43 | h.ensureIndex(base) 44 | index = int32(base.Index) 45 | } 46 | msg := &nl.TcMsg{ 47 | Family: nl.FAMILY_ALL, 48 | Ifindex: index, 49 | Parent: chain.Parent, 50 | } 51 | req.AddData(msg) 52 | req.AddData(nl.NewRtAttr(nl.TCA_CHAIN, nl.Uint32Attr(chain.Chain))) 53 | 54 | _, err := req.Execute(unix.NETLINK_ROUTE, 0) 55 | return err 56 | } 57 | 58 | // ChainList gets a list of chains in the system. 59 | // Equivalent to: `tc chain list`. 60 | // The list can be filtered by link. 61 | // 62 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 63 | // or incomplete. 64 | func ChainList(link Link, parent uint32) ([]Chain, error) { 65 | return pkgHandle.ChainList(link, parent) 66 | } 67 | 68 | // ChainList gets a list of chains in the system. 69 | // Equivalent to: `tc chain list`. 70 | // The list can be filtered by link. 71 | // 72 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 73 | // or incomplete. 74 | func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { 75 | req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP) 76 | index := int32(0) 77 | if link != nil { 78 | base := link.Attrs() 79 | h.ensureIndex(base) 80 | index = int32(base.Index) 81 | } 82 | msg := &nl.TcMsg{ 83 | Family: nl.FAMILY_ALL, 84 | Ifindex: index, 85 | Parent: parent, 86 | } 87 | req.AddData(msg) 88 | 89 | msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN) 90 | if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { 91 | return nil, executeErr 92 | } 93 | 94 | var res []Chain 95 | for _, m := range msgs { 96 | msg := nl.DeserializeTcMsg(m) 97 | 98 | attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | // skip chains from other interfaces 104 | if link != nil && msg.Ifindex != index { 105 | continue 106 | } 107 | 108 | var chain Chain 109 | for _, attr := range attrs { 110 | switch attr.Attr.Type { 111 | case nl.TCA_CHAIN: 112 | chain.Chain = native.Uint32(attr.Value) 113 | chain.Parent = parent 114 | } 115 | } 116 | res = append(res, chain) 117 | } 118 | 119 | return res, executeErr 120 | } 121 | -------------------------------------------------------------------------------- /chain_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestChainAddDel(t *testing.T) { 11 | tearDown := setUpNetlinkTest(t) 12 | defer tearDown() 13 | if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { 14 | t.Fatal(err) 15 | } 16 | if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil { 17 | t.Fatal(err) 18 | } 19 | link, err := LinkByName("foo") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if err := LinkSetUp(link); err != nil { 24 | t.Fatal(err) 25 | } 26 | qdisc := &Ingress{ 27 | QdiscAttrs: QdiscAttrs{ 28 | LinkIndex: link.Attrs().Index, 29 | Handle: MakeHandle(0xffff, 0), 30 | Parent: HANDLE_INGRESS, 31 | }, 32 | } 33 | if err := QdiscAdd(qdisc); err != nil { 34 | t.Fatal(err) 35 | } 36 | qdiscs, err := SafeQdiscList(link) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if len(qdiscs) != 1 { 41 | t.Fatal("Failed to add qdisc") 42 | } 43 | _, ok := qdiscs[0].(*Ingress) 44 | if !ok { 45 | t.Fatal("Qdisc is the wrong type") 46 | } 47 | chainVal := new(uint32) 48 | *chainVal = 20 49 | chain := NewChain(HANDLE_INGRESS, *chainVal) 50 | err = ChainAdd(link, chain) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | chains, err := ChainList(link, HANDLE_INGRESS) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(chains) != 1 { 59 | t.Fatal("Failed to add chain") 60 | } 61 | if chains[0].Chain != *chainVal { 62 | t.Fatal("Incorrect chain added") 63 | } 64 | if chains[0].Parent != HANDLE_INGRESS { 65 | t.Fatal("Incorrect chain parent") 66 | } 67 | if err := ChainDel(link, chain); err != nil { 68 | t.Fatal(err) 69 | } 70 | chains, err = ChainList(link, HANDLE_INGRESS) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | if len(chains) != 0 { 75 | t.Fatal("Failed to remove chain") 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cmd/ipset-test/main.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "log" 10 | "net" 11 | "os" 12 | "sort" 13 | 14 | "github.com/vishvananda/netlink" 15 | ) 16 | 17 | type command struct { 18 | Function func([]string) 19 | Description string 20 | ArgCount int 21 | } 22 | 23 | var ( 24 | commands = map[string]command{ 25 | "protocol": {cmdProtocol, "prints the protocol version", 0}, 26 | "create": {cmdCreate, "creates a new ipset", 2}, 27 | "destroy": {cmdDestroy, "creates a new ipset", 1}, 28 | "list": {cmdList, "list specific ipset", 1}, 29 | "listall": {cmdListAll, "list all ipsets", 0}, 30 | "add": {cmdAddDel(netlink.IpsetAdd), "add entry", 2}, 31 | "del": {cmdAddDel(netlink.IpsetDel), "delete entry", 2}, 32 | "test": {cmdTest, "test whether an entry is in a set or not", 2}, 33 | } 34 | 35 | timeoutVal *uint32 36 | timeout = flag.Int("timeout", -1, "timeout, negative means omit the argument") 37 | comment = flag.String("comment", "", "comment") 38 | withComments = flag.Bool("with-comments", false, "create set with comment support") 39 | withCounters = flag.Bool("with-counters", false, "create set with counters support") 40 | withSkbinfo = flag.Bool("with-skbinfo", false, "create set with skbinfo support") 41 | replace = flag.Bool("replace", false, "replace existing set/entry") 42 | ) 43 | 44 | func main() { 45 | flag.Parse() 46 | args := flag.Args() 47 | 48 | if len(args) < 1 { 49 | printUsage() 50 | os.Exit(1) 51 | } 52 | 53 | if *timeout >= 0 { 54 | v := uint32(*timeout) 55 | timeoutVal = &v 56 | } 57 | 58 | log.SetFlags(log.Lshortfile) 59 | 60 | cmdName := args[0] 61 | args = args[1:] 62 | 63 | cmd, exist := commands[cmdName] 64 | if !exist { 65 | fmt.Printf("Unknown command '%s'\n\n", cmdName) 66 | printUsage() 67 | os.Exit(1) 68 | } 69 | 70 | if cmd.ArgCount != len(args) { 71 | fmt.Printf("Invalid number of arguments. expected=%d given=%d\n", cmd.ArgCount, len(args)) 72 | os.Exit(1) 73 | } 74 | 75 | cmd.Function(args) 76 | } 77 | 78 | func printUsage() { 79 | fmt.Printf("Usage: %s COMMAND [args] [-flags]\n\n", os.Args[0]) 80 | names := make([]string, 0, len(commands)) 81 | for name := range commands { 82 | names = append(names, name) 83 | } 84 | sort.Strings(names) 85 | fmt.Println("Available commands:") 86 | for _, name := range names { 87 | fmt.Printf(" %-15v %s\n", name, commands[name].Description) 88 | } 89 | fmt.Println("\nAvailable flags:") 90 | flag.PrintDefaults() 91 | } 92 | 93 | func cmdProtocol(_ []string) { 94 | protocol, minProto, err := netlink.IpsetProtocol() 95 | check(err) 96 | log.Println("Protocol:", protocol, "min:", minProto) 97 | } 98 | 99 | func cmdCreate(args []string) { 100 | err := netlink.IpsetCreate(args[0], args[1], netlink.IpsetCreateOptions{ 101 | Replace: *replace, 102 | Timeout: timeoutVal, 103 | Comments: *withComments, 104 | Counters: *withCounters, 105 | Skbinfo: *withSkbinfo, 106 | }) 107 | check(err) 108 | } 109 | 110 | func cmdDestroy(args []string) { 111 | check(netlink.IpsetDestroy(args[0])) 112 | } 113 | 114 | func cmdList(args []string) { 115 | result, err := netlink.IpsetList(args[0]) 116 | check(err) 117 | log.Printf("%+v", result) 118 | } 119 | 120 | func cmdListAll(args []string) { 121 | result, err := netlink.IpsetListAll() 122 | check(err) 123 | for _, ipset := range result { 124 | log.Printf("%+v", ipset) 125 | } 126 | } 127 | 128 | func cmdAddDel(f func(string, *netlink.IPSetEntry) error) func([]string) { 129 | return func(args []string) { 130 | setName := args[0] 131 | element := args[1] 132 | 133 | mac, _ := net.ParseMAC(element) 134 | entry := netlink.IPSetEntry{ 135 | Timeout: timeoutVal, 136 | MAC: mac, 137 | Comment: *comment, 138 | Replace: *replace, 139 | } 140 | 141 | check(f(setName, &entry)) 142 | } 143 | } 144 | 145 | func cmdTest(args []string) { 146 | setName := args[0] 147 | element := args[1] 148 | ip := net.ParseIP(element) 149 | entry := &netlink.IPSetEntry{ 150 | Timeout: timeoutVal, 151 | IP: ip, 152 | Comment: *comment, 153 | Replace: *replace, 154 | } 155 | exist, err := netlink.IpsetTest(setName, entry) 156 | check(err) 157 | log.Printf("existence: %t\n", exist) 158 | } 159 | 160 | // panic on error 161 | func check(err error) { 162 | if err != nil { 163 | panic(err) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /conntrack_unspecified.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package netlink 4 | 5 | // ConntrackTableType Conntrack table for the netlink operation 6 | type ConntrackTableType uint8 7 | 8 | // InetFamily Family type 9 | type InetFamily uint8 10 | 11 | // ConntrackFlow placeholder 12 | type ConntrackFlow struct{} 13 | 14 | // CustomConntrackFilter placeholder 15 | type CustomConntrackFilter struct{} 16 | 17 | // ConntrackFilter placeholder 18 | type ConntrackFilter struct{} 19 | 20 | // ConntrackTableList returns the flow list of a table of a specific family 21 | // conntrack -L [table] [options] List conntrack or expectation table 22 | func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { 23 | return nil, ErrNotImplemented 24 | } 25 | 26 | // ConntrackTableFlush flushes all the flows of a specified table 27 | // conntrack -F [table] Flush table 28 | // The flush operation applies to all the family types 29 | func ConntrackTableFlush(table ConntrackTableType) error { 30 | return ErrNotImplemented 31 | } 32 | 33 | // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter 34 | // conntrack -D [table] parameters Delete conntrack or expectation 35 | // 36 | // Deprecated: use [ConntrackDeleteFilters] instead. 37 | func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) { 38 | return 0, ErrNotImplemented 39 | } 40 | 41 | // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters 42 | // conntrack -D [table] parameters Delete conntrack or expectation 43 | func ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { 44 | return 0, ErrNotImplemented 45 | } 46 | 47 | // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed 48 | // conntrack -L [table] [options] List conntrack or expectation table 49 | func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { 50 | return nil, ErrNotImplemented 51 | } 52 | 53 | // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed 54 | // conntrack -F [table] Flush table 55 | // The flush operation applies to all the family types 56 | func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error { 57 | return ErrNotImplemented 58 | } 59 | 60 | // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed 61 | // conntrack -D [table] parameters Delete conntrack or expectation 62 | // 63 | // Deprecated: use [Handle.ConntrackDeleteFilters] instead. 64 | func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) { 65 | return 0, ErrNotImplemented 66 | } 67 | 68 | // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters using the netlink handle passed 69 | // conntrack -D [table] parameters Delete conntrack or expectation 70 | func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { 71 | return 0, ErrNotImplemented 72 | } 73 | -------------------------------------------------------------------------------- /fou.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type Fou struct { 8 | Family int 9 | Port int 10 | Protocol int 11 | EncapType int 12 | Local net.IP 13 | Peer net.IP 14 | PeerPort int 15 | IfIndex int 16 | } 17 | -------------------------------------------------------------------------------- /fou_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "encoding/binary" 8 | "errors" 9 | "log" 10 | "net" 11 | 12 | "github.com/vishvananda/netlink/nl" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | FOU_GENL_NAME = "fou" 18 | ) 19 | 20 | const ( 21 | FOU_CMD_UNSPEC uint8 = iota 22 | FOU_CMD_ADD 23 | FOU_CMD_DEL 24 | FOU_CMD_GET 25 | FOU_CMD_MAX = FOU_CMD_GET 26 | ) 27 | 28 | const ( 29 | FOU_ATTR_UNSPEC = iota 30 | FOU_ATTR_PORT 31 | FOU_ATTR_AF 32 | FOU_ATTR_IPPROTO 33 | FOU_ATTR_TYPE 34 | FOU_ATTR_REMCSUM_NOPARTIAL 35 | FOU_ATTR_LOCAL_V4 36 | FOU_ATTR_LOCAL_V6 37 | FOU_ATTR_PEER_V4 38 | FOU_ATTR_PEER_V6 39 | FOU_ATTR_PEER_PORT 40 | FOU_ATTR_IFINDEX 41 | FOU_ATTR_MAX = FOU_ATTR_REMCSUM_NOPARTIAL 42 | ) 43 | 44 | const ( 45 | FOU_ENCAP_UNSPEC = iota 46 | FOU_ENCAP_DIRECT 47 | FOU_ENCAP_GUE 48 | FOU_ENCAP_MAX = FOU_ENCAP_GUE 49 | ) 50 | 51 | var fouFamilyId int 52 | 53 | func FouFamilyId() (int, error) { 54 | if fouFamilyId != 0 { 55 | return fouFamilyId, nil 56 | } 57 | 58 | fam, err := GenlFamilyGet(FOU_GENL_NAME) 59 | if err != nil { 60 | return -1, err 61 | } 62 | 63 | fouFamilyId = int(fam.ID) 64 | return fouFamilyId, nil 65 | } 66 | 67 | func FouAdd(f Fou) error { 68 | return pkgHandle.FouAdd(f) 69 | } 70 | 71 | func (h *Handle) FouAdd(f Fou) error { 72 | fam_id, err := FouFamilyId() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | // setting ip protocol conflicts with encapsulation type GUE 78 | if f.EncapType == FOU_ENCAP_GUE && f.Protocol != 0 { 79 | return errors.New("GUE encapsulation doesn't specify an IP protocol") 80 | } 81 | 82 | req := h.newNetlinkRequest(fam_id, unix.NLM_F_ACK) 83 | 84 | // int to byte for port 85 | bp := make([]byte, 2) 86 | binary.BigEndian.PutUint16(bp[0:2], uint16(f.Port)) 87 | 88 | attrs := []*nl.RtAttr{ 89 | nl.NewRtAttr(FOU_ATTR_PORT, bp), 90 | nl.NewRtAttr(FOU_ATTR_TYPE, []byte{uint8(f.EncapType)}), 91 | nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(f.Family)}), 92 | nl.NewRtAttr(FOU_ATTR_IPPROTO, []byte{uint8(f.Protocol)}), 93 | } 94 | raw := []byte{FOU_CMD_ADD, 1, 0, 0} 95 | for _, a := range attrs { 96 | raw = append(raw, a.Serialize()...) 97 | } 98 | 99 | req.AddRawData(raw) 100 | 101 | _, err = req.Execute(unix.NETLINK_GENERIC, 0) 102 | return err 103 | } 104 | 105 | func FouDel(f Fou) error { 106 | return pkgHandle.FouDel(f) 107 | } 108 | 109 | func (h *Handle) FouDel(f Fou) error { 110 | fam_id, err := FouFamilyId() 111 | if err != nil { 112 | return err 113 | } 114 | 115 | req := h.newNetlinkRequest(fam_id, unix.NLM_F_ACK) 116 | 117 | // int to byte for port 118 | bp := make([]byte, 2) 119 | binary.BigEndian.PutUint16(bp[0:2], uint16(f.Port)) 120 | 121 | attrs := []*nl.RtAttr{ 122 | nl.NewRtAttr(FOU_ATTR_PORT, bp), 123 | nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(f.Family)}), 124 | } 125 | raw := []byte{FOU_CMD_DEL, 1, 0, 0} 126 | for _, a := range attrs { 127 | raw = append(raw, a.Serialize()...) 128 | } 129 | 130 | req.AddRawData(raw) 131 | 132 | _, err = req.Execute(unix.NETLINK_GENERIC, 0) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | return nil 138 | } 139 | 140 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 141 | // or incomplete. 142 | func FouList(fam int) ([]Fou, error) { 143 | return pkgHandle.FouList(fam) 144 | } 145 | 146 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 147 | // or incomplete. 148 | func (h *Handle) FouList(fam int) ([]Fou, error) { 149 | fam_id, err := FouFamilyId() 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | req := h.newNetlinkRequest(fam_id, unix.NLM_F_DUMP) 155 | 156 | attrs := []*nl.RtAttr{ 157 | nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(fam)}), 158 | } 159 | raw := []byte{FOU_CMD_GET, 1, 0, 0} 160 | for _, a := range attrs { 161 | raw = append(raw, a.Serialize()...) 162 | } 163 | 164 | req.AddRawData(raw) 165 | 166 | msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) 167 | if executeErr != nil && !errors.Is(err, ErrDumpInterrupted) { 168 | return nil, executeErr 169 | } 170 | 171 | fous := make([]Fou, 0, len(msgs)) 172 | for _, m := range msgs { 173 | f, err := deserializeFouMsg(m) 174 | if err != nil { 175 | return fous, err 176 | } 177 | 178 | fous = append(fous, f) 179 | } 180 | 181 | return fous, executeErr 182 | } 183 | 184 | func deserializeFouMsg(msg []byte) (Fou, error) { 185 | fou := Fou{} 186 | 187 | for attr := range nl.ParseAttributes(msg[4:]) { 188 | switch attr.Type { 189 | case FOU_ATTR_AF: 190 | fou.Family = int(attr.Value[0]) 191 | case FOU_ATTR_PORT: 192 | fou.Port = int(networkOrder.Uint16(attr.Value)) 193 | case FOU_ATTR_IPPROTO: 194 | fou.Protocol = int(attr.Value[0]) 195 | case FOU_ATTR_TYPE: 196 | fou.EncapType = int(attr.Value[0]) 197 | case FOU_ATTR_LOCAL_V4, FOU_ATTR_LOCAL_V6: 198 | fou.Local = net.IP(attr.Value) 199 | case FOU_ATTR_PEER_V4, FOU_ATTR_PEER_V6: 200 | fou.Peer = net.IP(attr.Value) 201 | case FOU_ATTR_PEER_PORT: 202 | fou.PeerPort = int(networkOrder.Uint16(attr.Value)) 203 | case FOU_ATTR_IFINDEX: 204 | fou.IfIndex = int(native.Uint16(attr.Value)) 205 | default: 206 | log.Printf("unknown fou attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK) 207 | } 208 | } 209 | 210 | return fou, nil 211 | } 212 | -------------------------------------------------------------------------------- /fou_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "net" 8 | "testing" 9 | ) 10 | 11 | func TestFouDeserializeMsg(t *testing.T) { 12 | var msg []byte 13 | 14 | // deserialize a valid message 15 | msg = []byte{3, 1, 0, 0, 5, 0, 2, 0, 2, 0, 0, 0, 6, 0, 1, 0, 21, 179, 0, 0, 5, 0, 3, 0, 4, 0, 0, 0, 5, 0, 4, 0, 1, 0, 0, 0} 16 | if fou, err := deserializeFouMsg(msg); err != nil { 17 | t.Error(err.Error()) 18 | } else { 19 | 20 | // check if message was deserialized correctly 21 | if fou.Family != FAMILY_V4 { 22 | t.Errorf("expected family %d, got %d", FAMILY_V4, fou.Family) 23 | } 24 | 25 | if fou.Port != 5555 { 26 | t.Errorf("expected port 5555, got %d", fou.Port) 27 | } 28 | 29 | if fou.Protocol != 4 { // ipip 30 | t.Errorf("expected protocol 4, got %d", fou.Protocol) 31 | } 32 | 33 | if fou.EncapType != FOU_ENCAP_DIRECT { 34 | t.Errorf("expected encap type %d, got %d", FOU_ENCAP_DIRECT, fou.EncapType) 35 | } 36 | } 37 | 38 | // deserialize a valid message(kernel >= 5.2) 39 | msg = []byte{3, 1, 0, 0, 5, 0, 2, 0, 2, 0, 0, 0, 6, 0, 1, 0, 43, 103, 0, 0, 6, 0, 10, 0, 86, 206, 0, 0, 5, 0, 3, 0, 0, 0, 0, 0, 5, 0, 4, 0, 2, 0, 0, 0, 8, 0, 11, 0, 0, 0, 0, 0, 8, 0, 6, 0, 1, 2, 3, 4, 8, 0, 8, 0, 5, 6, 7, 8} 40 | if fou, err := deserializeFouMsg(msg); err != nil { 41 | t.Error(err.Error()) 42 | } else { 43 | if fou.Family != FAMILY_V4 { 44 | t.Errorf("expected family %d, got %d", FAMILY_V4, fou.Family) 45 | } 46 | 47 | if fou.Port != 11111 { 48 | t.Errorf("expected port 5555, got %d", fou.Port) 49 | } 50 | 51 | if fou.Protocol != 0 { // gue 52 | t.Errorf("expected protocol 0, got %d", fou.Protocol) 53 | } 54 | 55 | if fou.IfIndex != 0 { 56 | t.Errorf("expected ifindex 0, got %d", fou.Protocol) 57 | } 58 | 59 | if fou.EncapType != FOU_ENCAP_GUE { 60 | t.Errorf("expected encap type %d, got %d", FOU_ENCAP_GUE, fou.EncapType) 61 | } 62 | 63 | if expected := net.IPv4(1, 2, 3, 4); !fou.Local.Equal(expected) { 64 | t.Errorf("expected local %v, got %v", expected, fou.Local) 65 | } 66 | 67 | if expected := net.IPv4(5, 6, 7, 8); !fou.Peer.Equal(expected) { 68 | t.Errorf("expected peer %v, got %v", expected, fou.Peer) 69 | } 70 | 71 | if fou.PeerPort != 22222 { 72 | t.Errorf("expected peer port 0, got %d", fou.PeerPort) 73 | } 74 | } 75 | 76 | // unknown attribute should be skipped 77 | msg = []byte{3, 1, 0, 0, 5, 0, 112, 0, 2, 0, 0, 0, 5, 0, 2, 0, 2, 0, 0} 78 | if fou, err := deserializeFouMsg(msg); err != nil { 79 | t.Errorf("unexpected error: %s", err.Error()) 80 | } else if fou.Family != 2 { 81 | t.Errorf("expected family 2, got %d", fou.Family) 82 | } 83 | } 84 | 85 | func TestFouAddDel(t *testing.T) { 86 | // foo-over-udp was merged in 3.18 so skip these tests if the kernel is too old 87 | minKernelRequired(t, 3, 18) 88 | 89 | // the fou module is usually not compiled in the kernel so we'll load it 90 | tearDown := setUpNetlinkTestWithKModule(t, "fou") 91 | defer tearDown() 92 | 93 | fou := Fou{ 94 | Port: 5555, 95 | Family: FAMILY_V4, 96 | Protocol: 4, // ipip 97 | EncapType: FOU_ENCAP_DIRECT, 98 | } 99 | 100 | if err := FouAdd(fou); err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | list, err := FouList(FAMILY_V4) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | if len(list) != 1 { 110 | t.Fatalf("expected 1 fou, got %d", len(list)) 111 | } 112 | 113 | if list[0].Port != fou.Port { 114 | t.Errorf("expected port %d, got %d", fou.Port, list[0].Port) 115 | } 116 | 117 | if list[0].Family != fou.Family { 118 | t.Errorf("expected family %d, got %d", fou.Family, list[0].Family) 119 | } 120 | 121 | if list[0].Protocol != fou.Protocol { 122 | t.Errorf("expected protocol %d, got %d", fou.Protocol, list[0].Protocol) 123 | } 124 | 125 | if list[0].EncapType != fou.EncapType { 126 | t.Errorf("expected encaptype %d, got %d", fou.EncapType, list[0].EncapType) 127 | } 128 | 129 | if err := FouDel(Fou{Port: fou.Port, Family: fou.Family}); err != nil { 130 | t.Fatal(err) 131 | } 132 | 133 | list, err = FouList(FAMILY_V4) 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | 138 | if len(list) != 0 { 139 | t.Fatalf("expected 0 fou, got %d", len(list)) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /fou_unspecified.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package netlink 5 | 6 | func FouAdd(f Fou) error { 7 | return ErrNotImplemented 8 | } 9 | 10 | func FouDel(f Fou) error { 11 | return ErrNotImplemented 12 | } 13 | 14 | func FouList(fam int) ([]Fou, error) { 15 | return nil, ErrNotImplemented 16 | } 17 | -------------------------------------------------------------------------------- /genetlink_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "syscall" 7 | 8 | "github.com/vishvananda/netlink/nl" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | type GenlOp struct { 13 | ID uint32 14 | Flags uint32 15 | } 16 | 17 | type GenlMulticastGroup struct { 18 | ID uint32 19 | Name string 20 | } 21 | 22 | type GenlFamily struct { 23 | ID uint16 24 | HdrSize uint32 25 | Name string 26 | Version uint32 27 | MaxAttr uint32 28 | Ops []GenlOp 29 | Groups []GenlMulticastGroup 30 | } 31 | 32 | func parseOps(b []byte) ([]GenlOp, error) { 33 | attrs, err := nl.ParseRouteAttr(b) 34 | if err != nil { 35 | return nil, err 36 | } 37 | ops := make([]GenlOp, 0, len(attrs)) 38 | for _, a := range attrs { 39 | nattrs, err := nl.ParseRouteAttr(a.Value) 40 | if err != nil { 41 | return nil, err 42 | } 43 | var op GenlOp 44 | for _, na := range nattrs { 45 | switch na.Attr.Type { 46 | case nl.GENL_CTRL_ATTR_OP_ID: 47 | op.ID = native.Uint32(na.Value) 48 | case nl.GENL_CTRL_ATTR_OP_FLAGS: 49 | op.Flags = native.Uint32(na.Value) 50 | } 51 | } 52 | ops = append(ops, op) 53 | } 54 | return ops, nil 55 | } 56 | 57 | func parseMulticastGroups(b []byte) ([]GenlMulticastGroup, error) { 58 | attrs, err := nl.ParseRouteAttr(b) 59 | if err != nil { 60 | return nil, err 61 | } 62 | groups := make([]GenlMulticastGroup, 0, len(attrs)) 63 | for _, a := range attrs { 64 | nattrs, err := nl.ParseRouteAttr(a.Value) 65 | if err != nil { 66 | return nil, err 67 | } 68 | var g GenlMulticastGroup 69 | for _, na := range nattrs { 70 | switch na.Attr.Type { 71 | case nl.GENL_CTRL_ATTR_MCAST_GRP_NAME: 72 | g.Name = nl.BytesToString(na.Value) 73 | case nl.GENL_CTRL_ATTR_MCAST_GRP_ID: 74 | g.ID = native.Uint32(na.Value) 75 | } 76 | } 77 | groups = append(groups, g) 78 | } 79 | return groups, nil 80 | } 81 | 82 | func (f *GenlFamily) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { 83 | for _, a := range attrs { 84 | switch a.Attr.Type { 85 | case nl.GENL_CTRL_ATTR_FAMILY_NAME: 86 | f.Name = nl.BytesToString(a.Value) 87 | case nl.GENL_CTRL_ATTR_FAMILY_ID: 88 | f.ID = native.Uint16(a.Value) 89 | case nl.GENL_CTRL_ATTR_VERSION: 90 | f.Version = native.Uint32(a.Value) 91 | case nl.GENL_CTRL_ATTR_HDRSIZE: 92 | f.HdrSize = native.Uint32(a.Value) 93 | case nl.GENL_CTRL_ATTR_MAXATTR: 94 | f.MaxAttr = native.Uint32(a.Value) 95 | case nl.GENL_CTRL_ATTR_OPS: 96 | ops, err := parseOps(a.Value) 97 | if err != nil { 98 | return err 99 | } 100 | f.Ops = ops 101 | case nl.GENL_CTRL_ATTR_MCAST_GROUPS: 102 | groups, err := parseMulticastGroups(a.Value) 103 | if err != nil { 104 | return err 105 | } 106 | f.Groups = groups 107 | } 108 | } 109 | 110 | return nil 111 | } 112 | 113 | func parseFamilies(msgs [][]byte) ([]*GenlFamily, error) { 114 | families := make([]*GenlFamily, 0, len(msgs)) 115 | for _, m := range msgs { 116 | attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) 117 | if err != nil { 118 | return nil, err 119 | } 120 | family := &GenlFamily{} 121 | if err := family.parseAttributes(attrs); err != nil { 122 | return nil, err 123 | } 124 | 125 | families = append(families, family) 126 | } 127 | return families, nil 128 | } 129 | 130 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 131 | // or incomplete. 132 | func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) { 133 | msg := &nl.Genlmsg{ 134 | Command: nl.GENL_CTRL_CMD_GETFAMILY, 135 | Version: nl.GENL_CTRL_VERSION, 136 | } 137 | req := h.newNetlinkRequest(nl.GENL_ID_CTRL, unix.NLM_F_DUMP) 138 | req.AddData(msg) 139 | msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) 140 | if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { 141 | return nil, executeErr 142 | } 143 | families, err := parseFamilies(msgs) 144 | if err != nil { 145 | return nil, err 146 | } 147 | return families, executeErr 148 | } 149 | 150 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 151 | // or incomplete. 152 | func GenlFamilyList() ([]*GenlFamily, error) { 153 | return pkgHandle.GenlFamilyList() 154 | } 155 | 156 | func (h *Handle) GenlFamilyGet(name string) (*GenlFamily, error) { 157 | msg := &nl.Genlmsg{ 158 | Command: nl.GENL_CTRL_CMD_GETFAMILY, 159 | Version: nl.GENL_CTRL_VERSION, 160 | } 161 | req := h.newNetlinkRequest(nl.GENL_ID_CTRL, 0) 162 | req.AddData(msg) 163 | req.AddData(nl.NewRtAttr(nl.GENL_CTRL_ATTR_FAMILY_NAME, nl.ZeroTerminated(name))) 164 | msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) 165 | if err != nil { 166 | return nil, err 167 | } 168 | families, err := parseFamilies(msgs) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if len(families) != 1 { 173 | return nil, fmt.Errorf("invalid response for GENL_CTRL_CMD_GETFAMILY") 174 | } 175 | return families[0], nil 176 | } 177 | 178 | func GenlFamilyGet(name string) (*GenlFamily, error) { 179 | return pkgHandle.GenlFamilyGet(name) 180 | } 181 | -------------------------------------------------------------------------------- /genetlink_unspecified.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package netlink 4 | 5 | type GenlOp struct{} 6 | 7 | type GenlMulticastGroup struct{} 8 | 9 | type GenlFamily struct{} 10 | 11 | func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) { 12 | return nil, ErrNotImplemented 13 | } 14 | 15 | func GenlFamilyList() ([]*GenlFamily, error) { 16 | return nil, ErrNotImplemented 17 | } 18 | 19 | func (h *Handle) GenlFamilyGet(name string) (*GenlFamily, error) { 20 | return nil, ErrNotImplemented 21 | } 22 | 23 | func GenlFamilyGet(name string) (*GenlFamily, error) { 24 | return nil, ErrNotImplemented 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vishvananda/netlink 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/vishvananda/netns v0.0.5 7 | golang.org/x/sys v0.10.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= 2 | github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 3 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 4 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 5 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 6 | -------------------------------------------------------------------------------- /gtp_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package netlink 4 | 5 | import ( 6 | "net" 7 | "testing" 8 | ) 9 | 10 | func TestPDPv0AddDel(t *testing.T) { 11 | tearDown := setUpNetlinkTestWithKModule(t, "gtp") 12 | defer tearDown() 13 | 14 | if err := LinkAdd(testGTPLink(t)); err != nil { 15 | t.Fatal(err) 16 | } 17 | link, err := LinkByName("gtp0") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | err = GTPPDPAdd(link, &PDP{ 22 | PeerAddress: net.ParseIP("1.1.1.1"), 23 | MSAddress: net.ParseIP("2.2.2.2"), 24 | TID: 10, 25 | }) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | list, err := GTPPDPList() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | if len(list) != 1 { 34 | t.Fatal("Failed to add v0 PDP") 35 | } 36 | pdp, err := GTPPDPByMSAddress(link, net.ParseIP("2.2.2.2")) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if pdp == nil { 41 | t.Fatal("failed to get v0 PDP by MS address") 42 | } 43 | pdp, err = GTPPDPByTID(link, 10) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if pdp == nil { 48 | t.Fatal("failed to get v0 PDP by TID") 49 | } 50 | err = GTPPDPDel(link, &PDP{TID: 10}) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | list, err = GTPPDPList() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(list) != 0 { 59 | t.Fatal("Failed to delete v0 PDP") 60 | } 61 | } 62 | 63 | func TestPDPv1AddDel(t *testing.T) { 64 | tearDown := setUpNetlinkTestWithKModule(t, "gtp") 65 | defer tearDown() 66 | 67 | if err := LinkAdd(testGTPLink(t)); err != nil { 68 | t.Fatal(err) 69 | } 70 | link, err := LinkByName("gtp0") 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | err = GTPPDPAdd(link, &PDP{ 75 | PeerAddress: net.ParseIP("1.1.1.1"), 76 | MSAddress: net.ParseIP("2.2.2.2"), 77 | Version: 1, 78 | ITEI: 10, 79 | OTEI: 10, 80 | }) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | list, err := GTPPDPList() 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if len(list) != 1 { 89 | t.Fatal("Failed to add v1 PDP") 90 | } 91 | pdp, err := GTPPDPByMSAddress(link, net.ParseIP("2.2.2.2")) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | if pdp == nil { 96 | t.Fatal("failed to get v1 PDP by MS address") 97 | } 98 | pdp, err = GTPPDPByITEI(link, 10) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | if pdp == nil { 103 | t.Fatal("failed to get v1 PDP by ITEI") 104 | } 105 | err = GTPPDPDel(link, &PDP{Version: 1, ITEI: 10}) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | list, err = GTPPDPList() 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | if len(list) != 0 { 114 | t.Fatal("Failed to delete v1 PDP") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /handle_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/vishvananda/netlink/nl" 8 | "github.com/vishvananda/netns" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // Empty handle used by the netlink package methods 13 | var pkgHandle = &Handle{} 14 | 15 | // Handle is an handle for the netlink requests on a 16 | // specific network namespace. All the requests on the 17 | // same netlink family share the same netlink socket, 18 | // which gets released when the handle is Close'd. 19 | type Handle struct { 20 | sockets map[int]*nl.SocketHandle 21 | lookupByDump bool 22 | } 23 | 24 | // SetSocketTimeout configures timeout for default netlink sockets 25 | func SetSocketTimeout(to time.Duration) error { 26 | if to < time.Microsecond { 27 | return fmt.Errorf("invalid timeout, minimul value is %s", time.Microsecond) 28 | } 29 | 30 | nl.SocketTimeoutTv = unix.NsecToTimeval(to.Nanoseconds()) 31 | return nil 32 | } 33 | 34 | // GetSocketTimeout returns the timeout value used by default netlink sockets 35 | func GetSocketTimeout() time.Duration { 36 | nsec := unix.TimevalToNsec(nl.SocketTimeoutTv) 37 | return time.Duration(nsec) * time.Nanosecond 38 | } 39 | 40 | // SupportsNetlinkFamily reports whether the passed netlink family is supported by this Handle 41 | func (h *Handle) SupportsNetlinkFamily(nlFamily int) bool { 42 | _, ok := h.sockets[nlFamily] 43 | return ok 44 | } 45 | 46 | // NewHandle returns a netlink handle on the current network namespace. 47 | // Caller may specify the netlink families the handle should support. 48 | // If no families are specified, all the families the netlink package 49 | // supports will be automatically added. 50 | func NewHandle(nlFamilies ...int) (*Handle, error) { 51 | return newHandle(netns.None(), netns.None(), nlFamilies...) 52 | } 53 | 54 | // SetSocketTimeout sets the send and receive timeout for each socket in the 55 | // netlink handle. Although the socket timeout has granularity of one 56 | // microsecond, the effective granularity is floored by the kernel timer tick, 57 | // which default value is four milliseconds. 58 | func (h *Handle) SetSocketTimeout(to time.Duration) error { 59 | if to < time.Microsecond { 60 | return fmt.Errorf("invalid timeout, minimul value is %s", time.Microsecond) 61 | } 62 | tv := unix.NsecToTimeval(to.Nanoseconds()) 63 | for _, sh := range h.sockets { 64 | if err := sh.Socket.SetSendTimeout(&tv); err != nil { 65 | return err 66 | } 67 | if err := sh.Socket.SetReceiveTimeout(&tv); err != nil { 68 | return err 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | // SetSocketReceiveBufferSize sets the receive buffer size for each 75 | // socket in the netlink handle. The maximum value is capped by 76 | // /proc/sys/net/core/rmem_max. 77 | func (h *Handle) SetSocketReceiveBufferSize(size int, force bool) error { 78 | opt := unix.SO_RCVBUF 79 | if force { 80 | opt = unix.SO_RCVBUFFORCE 81 | } 82 | for _, sh := range h.sockets { 83 | fd := sh.Socket.GetFd() 84 | err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, opt, size) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | 92 | // GetSocketReceiveBufferSize gets the receiver buffer size for each 93 | // socket in the netlink handle. The retrieved value should be the 94 | // double to the one set for SetSocketReceiveBufferSize. 95 | func (h *Handle) GetSocketReceiveBufferSize() ([]int, error) { 96 | results := make([]int, len(h.sockets)) 97 | i := 0 98 | for _, sh := range h.sockets { 99 | fd := sh.Socket.GetFd() 100 | size, err := unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF) 101 | if err != nil { 102 | return nil, err 103 | } 104 | results[i] = size 105 | i++ 106 | } 107 | return results, nil 108 | } 109 | 110 | // SetStrictCheck sets the strict check socket option for each socket in the netlink handle. Returns early if any set operation fails 111 | func (h *Handle) SetStrictCheck(state bool) error { 112 | for _, sh := range h.sockets { 113 | var stateInt int = 0 114 | if state { 115 | stateInt = 1 116 | } 117 | err := unix.SetsockoptInt(sh.Socket.GetFd(), unix.SOL_NETLINK, unix.NETLINK_GET_STRICT_CHK, stateInt) 118 | if err != nil { 119 | return err 120 | } 121 | } 122 | return nil 123 | } 124 | 125 | // NewHandleAt returns a netlink handle on the network namespace 126 | // specified by ns. If ns=netns.None(), current network namespace 127 | // will be assumed 128 | func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (*Handle, error) { 129 | return newHandle(ns, netns.None(), nlFamilies...) 130 | } 131 | 132 | // NewHandleAtFrom works as NewHandle but allows client to specify the 133 | // new and the origin netns Handle. 134 | func NewHandleAtFrom(newNs, curNs netns.NsHandle) (*Handle, error) { 135 | return newHandle(newNs, curNs) 136 | } 137 | 138 | func newHandle(newNs, curNs netns.NsHandle, nlFamilies ...int) (*Handle, error) { 139 | h := &Handle{sockets: map[int]*nl.SocketHandle{}} 140 | fams := nl.SupportedNlFamilies 141 | if len(nlFamilies) != 0 { 142 | fams = nlFamilies 143 | } 144 | for _, f := range fams { 145 | s, err := nl.GetNetlinkSocketAt(newNs, curNs, f) 146 | if err != nil { 147 | return nil, err 148 | } 149 | h.sockets[f] = &nl.SocketHandle{Socket: s} 150 | } 151 | return h, nil 152 | } 153 | 154 | // Close releases the resources allocated to this handle 155 | func (h *Handle) Close() { 156 | for _, sh := range h.sockets { 157 | sh.Close() 158 | } 159 | h.sockets = nil 160 | } 161 | 162 | // Delete releases the resources allocated to this handle 163 | // 164 | // Deprecated: use Close instead which is in line with typical resource release 165 | // patterns for files and other resources. 166 | func (h *Handle) Delete() { 167 | h.Close() 168 | } 169 | 170 | func (h *Handle) newNetlinkRequest(proto, flags int) *nl.NetlinkRequest { 171 | // Do this so that package API still use nl package variable nextSeqNr 172 | if h.sockets == nil { 173 | return nl.NewNetlinkRequest(proto, flags) 174 | } 175 | return &nl.NetlinkRequest{ 176 | NlMsghdr: unix.NlMsghdr{ 177 | Len: uint32(unix.SizeofNlMsghdr), 178 | Type: uint16(proto), 179 | Flags: unix.NLM_F_REQUEST | uint16(flags), 180 | }, 181 | Sockets: h.sockets, 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /handle_linux_test.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestSetGetSocketTimeout(t *testing.T) { 9 | timeout := 10 * time.Second 10 | if err := SetSocketTimeout(10 * time.Second); err != nil { 11 | t.Fatalf("Set socket timeout for default handle failed: %v", err) 12 | } 13 | 14 | if val := GetSocketTimeout(); val != timeout { 15 | t.Fatalf("Unexpected socket timeout value: got=%v, expected=%v", val, timeout) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inet_diag.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | // INET_DIAG constatns 4 | const ( 5 | INET_DIAG_NONE = iota 6 | INET_DIAG_MEMINFO 7 | INET_DIAG_INFO 8 | INET_DIAG_VEGASINFO 9 | INET_DIAG_CONG 10 | INET_DIAG_TOS 11 | INET_DIAG_TCLASS 12 | INET_DIAG_SKMEMINFO 13 | INET_DIAG_SHUTDOWN 14 | INET_DIAG_DCTCPINFO 15 | INET_DIAG_PROTOCOL 16 | INET_DIAG_SKV6ONLY 17 | INET_DIAG_LOCALS 18 | INET_DIAG_PEERS 19 | INET_DIAG_PAD 20 | INET_DIAG_MARK 21 | INET_DIAG_BBRINFO 22 | INET_DIAG_CLASS_ID 23 | INET_DIAG_MD5SIG 24 | INET_DIAG_ULP_INFO 25 | INET_DIAG_SK_BPF_STORAGES 26 | INET_DIAG_CGROUP_ID 27 | INET_DIAG_SOCKOPT 28 | INET_DIAG_MAX 29 | ) 30 | 31 | type InetDiagTCPInfoResp struct { 32 | InetDiagMsg *Socket 33 | TCPInfo *TCPInfo 34 | TCPBBRInfo *TCPBBRInfo 35 | } 36 | 37 | type InetDiagUDPInfoResp struct { 38 | InetDiagMsg *Socket 39 | Memory *MemInfo 40 | } 41 | -------------------------------------------------------------------------------- /ioctl_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // ioctl for statistics. 11 | const ( 12 | // ETHTOOL_GSSET_INFO gets string set info 13 | ETHTOOL_GSSET_INFO = 0x00000037 14 | // SIOCETHTOOL is Ethtool interface 15 | SIOCETHTOOL = 0x8946 16 | // ETHTOOL_GSTRINGS gets specified string set 17 | ETHTOOL_GSTRINGS = 0x0000001b 18 | // ETHTOOL_GSTATS gets NIC-specific statistics 19 | ETHTOOL_GSTATS = 0x0000001d 20 | ) 21 | 22 | // string set id. 23 | const ( 24 | // ETH_SS_TEST is self-test result names, for use with %ETHTOOL_TEST 25 | ETH_SS_TEST = iota 26 | // ETH_SS_STATS statistic names, for use with %ETHTOOL_GSTATS 27 | ETH_SS_STATS 28 | // ETH_SS_PRIV_FLAGS are driver private flag names 29 | ETH_SS_PRIV_FLAGS 30 | // _ETH_SS_NTUPLE_FILTERS is deprecated 31 | _ETH_SS_NTUPLE_FILTERS 32 | // ETH_SS_FEATURES are device feature names 33 | ETH_SS_FEATURES 34 | // ETH_SS_RSS_HASH_FUNCS is RSS hush function names 35 | ETH_SS_RSS_HASH_FUNCS 36 | ) 37 | 38 | // IfreqSlave is a struct for ioctl bond manipulation syscalls. 39 | // It is used to assign slave to bond interface with Name. 40 | type IfreqSlave struct { 41 | Name [unix.IFNAMSIZ]byte 42 | Slave [unix.IFNAMSIZ]byte 43 | } 44 | 45 | // Ifreq is a struct for ioctl ethernet manipulation syscalls. 46 | type Ifreq struct { 47 | Name [unix.IFNAMSIZ]byte 48 | Data uintptr 49 | } 50 | 51 | // ethtoolSset is a string set information 52 | type ethtoolSset struct { 53 | cmd uint32 54 | reserved uint32 55 | mask uint64 56 | data [1]uint32 57 | } 58 | 59 | type ethtoolStats struct { 60 | cmd uint32 61 | nStats uint32 62 | // Followed by nStats * []uint64. 63 | } 64 | 65 | // newIocltSlaveReq returns filled IfreqSlave with proper interface names 66 | // It is used by ioctl to assign slave to bond master 67 | func newIocltSlaveReq(slave, master string) *IfreqSlave { 68 | ifreq := &IfreqSlave{} 69 | copy(ifreq.Name[:unix.IFNAMSIZ-1], master) 70 | copy(ifreq.Slave[:unix.IFNAMSIZ-1], slave) 71 | return ifreq 72 | } 73 | 74 | // newIocltStringSetReq creates request to get interface string set 75 | func newIocltStringSetReq(linkName string) (*Ifreq, *ethtoolSset) { 76 | e := ðtoolSset{ 77 | cmd: ETHTOOL_GSSET_INFO, 78 | mask: 1 << ETH_SS_STATS, 79 | } 80 | 81 | ifreq := &Ifreq{Data: uintptr(unsafe.Pointer(e))} 82 | copy(ifreq.Name[:unix.IFNAMSIZ-1], linkName) 83 | return ifreq, e 84 | } 85 | 86 | // getSocketUDP returns file descriptor to new UDP socket 87 | // It is used for communication with ioctl interface. 88 | func getSocketUDP() (int, error) { 89 | return syscall.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0) 90 | } 91 | -------------------------------------------------------------------------------- /link_tuntap_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // ideally golang.org/x/sys/unix would define IfReq but it only has 13 | // IFNAMSIZ, hence this minimalistic implementation 14 | const ( 15 | SizeOfIfReq = 40 16 | IFNAMSIZ = 16 17 | ) 18 | 19 | const TUN = "/dev/net/tun" 20 | 21 | type ifReq struct { 22 | Name [IFNAMSIZ]byte 23 | Flags uint16 24 | pad [SizeOfIfReq - IFNAMSIZ - 2]byte 25 | } 26 | 27 | // AddQueues opens and attaches multiple queue file descriptors to an existing 28 | // TUN/TAP interface in multi-queue mode. 29 | // 30 | // It performs TUNSETIFF ioctl on each opened file descriptor with the current 31 | // tuntap configuration. Each resulting fd is set to non-blocking mode and 32 | // returned as *os.File. 33 | // 34 | // If the interface was created with a name pattern (e.g. "tap%d"), 35 | // the first successful TUNSETIFF call will return the resolved name, 36 | // which is saved back into tuntap.Name. 37 | // 38 | // This method assumes that the interface already exists and is in multi-queue mode. 39 | // The returned FDs are also appended to tuntap.Fds and tuntap.Queues is updated. 40 | // 41 | // It is the caller's responsibility to close the FDs when they are no longer needed. 42 | func (tuntap *Tuntap) AddQueues(count int) ([]*os.File, error) { 43 | if tuntap.Mode < unix.IFF_TUN || tuntap.Mode > unix.IFF_TAP { 44 | return nil, fmt.Errorf("Tuntap.Mode %v unknown", tuntap.Mode) 45 | } 46 | if tuntap.Flags&TUNTAP_MULTI_QUEUE == 0 { 47 | return nil, fmt.Errorf("TUNTAP_MULTI_QUEUE not set") 48 | } 49 | if count < 1 { 50 | return nil, fmt.Errorf("count must be >= 1") 51 | } 52 | 53 | req, err := unix.NewIfreq(tuntap.Name) 54 | if err != nil { 55 | return nil, err 56 | } 57 | req.SetUint16(uint16(tuntap.Mode) | uint16(tuntap.Flags)) 58 | 59 | var fds []*os.File 60 | for i := 0; i < count; i++ { 61 | localReq := req 62 | fd, err := unix.Open(TUN, os.O_RDWR|syscall.O_CLOEXEC, 0) 63 | if err != nil { 64 | cleanupFds(fds) 65 | return nil, err 66 | } 67 | 68 | err = unix.IoctlIfreq(fd, unix.TUNSETIFF, req) 69 | if err != nil { 70 | // close the new fd 71 | unix.Close(fd) 72 | // and the already opened ones 73 | cleanupFds(fds) 74 | return nil, fmt.Errorf("tuntap IOCTL TUNSETIFF failed [%d]: %w", i, err) 75 | } 76 | 77 | // Set the tun device to non-blocking before use. The below comment 78 | // taken from: 79 | // 80 | // https://github.com/mistsys/tuntap/commit/161418c25003bbee77d085a34af64d189df62bea 81 | // 82 | // Note there is a complication because in go, if a device node is 83 | // opened, go sets it to use nonblocking I/O. However a /dev/net/tun 84 | // doesn't work with epoll until after the TUNSETIFF ioctl has been 85 | // done. So we open the unix fd directly, do the ioctl, then put the 86 | // fd in nonblocking mode, an then finally wrap it in a os.File, 87 | // which will see the nonblocking mode and add the fd to the 88 | // pollable set, so later on when we Read() from it blocked the 89 | // calling thread in the kernel. 90 | // 91 | // See 92 | // https://github.com/golang/go/issues/30426 93 | // which got exposed in go 1.13 by the fix to 94 | // https://github.com/golang/go/issues/30624 95 | err = unix.SetNonblock(fd, true) 96 | if err != nil { 97 | cleanupFds(fds) 98 | return nil, fmt.Errorf("tuntap set to non-blocking failed [%d]: %w", i, err) 99 | } 100 | 101 | // create the file from the file descriptor and store it 102 | file := os.NewFile(uintptr(fd), TUN) 103 | fds = append(fds, file) 104 | 105 | // 1) we only care for the name of the first tap in the multi queue set 106 | // 2) if the original name was empty, the localReq has now the actual name 107 | // 108 | // In addition: 109 | // This ensures that the link name is always identical to what the kernel returns. 110 | // Not only in case of an empty name, but also when using name templates. 111 | // e.g. when the provided name is "tap%d", the kernel replaces %d with the next available number. 112 | if i == 0 { 113 | tuntap.Name = strings.Trim(localReq.Name(), "\x00") 114 | } 115 | } 116 | 117 | tuntap.Fds = append(tuntap.Fds, fds...) 118 | tuntap.Queues = len(tuntap.Fds) 119 | return fds, nil 120 | } 121 | 122 | // RemoveQueues closes the given TAP queue file descriptors and removes them 123 | // from the tuntap.Fds list. 124 | // 125 | // This is a logical counterpart to AddQueues and allows releasing specific queues 126 | // (e.g., to simulate queue failure or perform partial detach). 127 | // 128 | // The method updates tuntap.Queues to reflect the number of remaining active queues. 129 | // 130 | // It is safe to call with a subset of tuntap.Fds, but the caller must ensure 131 | // that the passed *os.File descriptors belong to this interface. 132 | func (tuntap *Tuntap) RemoveQueues(fds ...*os.File) error { 133 | toClose := make(map[uintptr]struct{}, len(fds)) 134 | for _, fd := range fds { 135 | toClose[fd.Fd()] = struct{}{} 136 | } 137 | 138 | var newFds []*os.File 139 | for _, fd := range tuntap.Fds { 140 | if _, shouldClose := toClose[fd.Fd()]; shouldClose { 141 | if err := fd.Close(); err != nil { 142 | return fmt.Errorf("failed to close queue fd %d: %w", fd.Fd(), err) 143 | } 144 | tuntap.Queues-- 145 | } else { 146 | newFds = append(newFds, fd) 147 | } 148 | } 149 | tuntap.Fds = newFds 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /neigh.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // Neigh represents a link layer neighbor from netlink. 9 | type Neigh struct { 10 | LinkIndex int 11 | Family int 12 | State int 13 | Type int 14 | Flags int 15 | FlagsExt int 16 | IP net.IP 17 | HardwareAddr net.HardwareAddr 18 | LLIPAddr net.IP //Used in the case of NHRP 19 | Vlan int 20 | VNI int 21 | MasterIndex int 22 | 23 | // These values are expressed as "clock ticks ago". To 24 | // convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK). 25 | // When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed 26 | // in centiseconds. 27 | Confirmed uint32 // The last time ARP/ND succeeded OR higher layer confirmation was received 28 | Used uint32 // The last time ARP/ND took place for this neighbor 29 | Updated uint32 // The time when the current NUD state was entered 30 | } 31 | 32 | // String returns $ip/$hwaddr $label 33 | func (neigh *Neigh) String() string { 34 | return fmt.Sprintf("%s %s", neigh.IP, neigh.HardwareAddr) 35 | } 36 | 37 | // NeighUpdate is sent when a neighbor changes - type is RTM_NEWNEIGH or RTM_DELNEIGH. 38 | type NeighUpdate struct { 39 | Type uint16 40 | Neigh 41 | } 42 | -------------------------------------------------------------------------------- /netlink.go: -------------------------------------------------------------------------------- 1 | // Package netlink provides a simple library for netlink. Netlink is 2 | // the interface a user-space program in linux uses to communicate with 3 | // the kernel. It can be used to add and remove interfaces, set up ip 4 | // addresses and routes, and confiugre ipsec. Netlink communication 5 | // requires elevated privileges, so in most cases this code needs to 6 | // be run as root. The low level primitives for netlink are contained 7 | // in the nl subpackage. This package attempts to provide a high-level 8 | // interface that is loosly modeled on the iproute2 cli. 9 | package netlink 10 | 11 | import ( 12 | "errors" 13 | "net" 14 | ) 15 | 16 | var ( 17 | // ErrNotImplemented is returned when a requested feature is not implemented. 18 | ErrNotImplemented = errors.New("not implemented") 19 | ) 20 | 21 | // ParseIPNet parses a string in ip/net format and returns a net.IPNet. 22 | // This is valuable because addresses in netlink are often IPNets and 23 | // ParseCIDR returns an IPNet with the IP part set to the base IP of the 24 | // range. 25 | func ParseIPNet(s string) (*net.IPNet, error) { 26 | ip, ipNet, err := net.ParseCIDR(s) 27 | if err != nil { 28 | return nil, err 29 | } 30 | ipNet.IP = ip 31 | return ipNet, nil 32 | } 33 | 34 | // NewIPNet generates an IPNet from an ip address using a netmask of 32 or 128. 35 | func NewIPNet(ip net.IP) *net.IPNet { 36 | if ip.To4() != nil { 37 | return &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 32)} 38 | } 39 | return &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)} 40 | } 41 | -------------------------------------------------------------------------------- /netlink_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import "github.com/vishvananda/netlink/nl" 4 | 5 | // Family type definitions 6 | const ( 7 | FAMILY_ALL = nl.FAMILY_ALL 8 | FAMILY_V4 = nl.FAMILY_V4 9 | FAMILY_V6 = nl.FAMILY_V6 10 | FAMILY_MPLS = nl.FAMILY_MPLS 11 | ) 12 | 13 | // ErrDumpInterrupted is an alias for [nl.ErrDumpInterrupted]. 14 | var ErrDumpInterrupted = nl.ErrDumpInterrupted 15 | -------------------------------------------------------------------------------- /netns_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | // Network namespace ID functions 4 | // 5 | // The kernel has a weird concept called the network namespace ID. 6 | // This is different from the file reference in proc (and any bind-mounted 7 | // namespaces, etc.) 8 | // 9 | // Instead, namespaces can be assigned a numeric ID at any time. Once set, 10 | // the ID is fixed. The ID can either be set manually by the user, or 11 | // automatically, triggered by certain kernel actions. The most common kernel 12 | // action that triggers namespace ID creation is moving one end of a veth pair 13 | // in to that namespace. 14 | 15 | import ( 16 | "fmt" 17 | 18 | "github.com/vishvananda/netlink/nl" 19 | "golang.org/x/sys/unix" 20 | ) 21 | 22 | // These can be replaced by the values from sys/unix when it is next released. 23 | const ( 24 | _ = iota 25 | NETNSA_NSID 26 | NETNSA_PID 27 | NETNSA_FD 28 | ) 29 | 30 | // GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id). 31 | // Returns -1 if the namespace does not have an ID set. 32 | func (h *Handle) GetNetNsIdByPid(pid int) (int, error) { 33 | return h.getNetNsId(NETNSA_PID, uint32(pid)) 34 | } 35 | 36 | // GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id). 37 | // Returns -1 if the namespace does not have an ID set. 38 | func GetNetNsIdByPid(pid int) (int, error) { 39 | return pkgHandle.GetNetNsIdByPid(pid) 40 | } 41 | 42 | // SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id). 43 | // The ID can only be set for namespaces without an ID already set. 44 | func (h *Handle) SetNetNsIdByPid(pid, nsid int) error { 45 | return h.setNetNsId(NETNSA_PID, uint32(pid), uint32(nsid)) 46 | } 47 | 48 | // SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id). 49 | // The ID can only be set for namespaces without an ID already set. 50 | func SetNetNsIdByPid(pid, nsid int) error { 51 | return pkgHandle.SetNetNsIdByPid(pid, nsid) 52 | } 53 | 54 | // GetNetNsIdByFd looks up the network namespace ID for a given fd. 55 | // fd must be an open file descriptor to a namespace file. 56 | // Returns -1 if the namespace does not have an ID set. 57 | func (h *Handle) GetNetNsIdByFd(fd int) (int, error) { 58 | return h.getNetNsId(NETNSA_FD, uint32(fd)) 59 | } 60 | 61 | // GetNetNsIdByFd looks up the network namespace ID for a given fd. 62 | // fd must be an open file descriptor to a namespace file. 63 | // Returns -1 if the namespace does not have an ID set. 64 | func GetNetNsIdByFd(fd int) (int, error) { 65 | return pkgHandle.GetNetNsIdByFd(fd) 66 | } 67 | 68 | // SetNetNSIdByFd sets the ID of the network namespace for a given fd. 69 | // fd must be an open file descriptor to a namespace file. 70 | // The ID can only be set for namespaces without an ID already set. 71 | func (h *Handle) SetNetNsIdByFd(fd, nsid int) error { 72 | return h.setNetNsId(NETNSA_FD, uint32(fd), uint32(nsid)) 73 | } 74 | 75 | // SetNetNSIdByFd sets the ID of the network namespace for a given fd. 76 | // fd must be an open file descriptor to a namespace file. 77 | // The ID can only be set for namespaces without an ID already set. 78 | func SetNetNsIdByFd(fd, nsid int) error { 79 | return pkgHandle.SetNetNsIdByFd(fd, nsid) 80 | } 81 | 82 | // getNetNsId requests the netnsid for a given type-val pair 83 | // type should be either NETNSA_PID or NETNSA_FD 84 | func (h *Handle) getNetNsId(attrType int, val uint32) (int, error) { 85 | req := h.newNetlinkRequest(unix.RTM_GETNSID, unix.NLM_F_REQUEST) 86 | 87 | rtgen := nl.NewRtGenMsg() 88 | req.AddData(rtgen) 89 | 90 | b := make([]byte, 4) 91 | native.PutUint32(b, val) 92 | attr := nl.NewRtAttr(attrType, b) 93 | req.AddData(attr) 94 | 95 | msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID) 96 | 97 | if err != nil { 98 | return 0, err 99 | } 100 | 101 | for _, m := range msgs { 102 | msg := nl.DeserializeRtGenMsg(m) 103 | 104 | attrs, err := nl.ParseRouteAttr(m[msg.Len():]) 105 | if err != nil { 106 | return 0, err 107 | } 108 | 109 | for _, attr := range attrs { 110 | switch attr.Attr.Type { 111 | case NETNSA_NSID: 112 | return int(int32(native.Uint32(attr.Value))), nil 113 | } 114 | } 115 | } 116 | 117 | return 0, fmt.Errorf("unexpected empty result") 118 | } 119 | 120 | // setNetNsId sets the netnsid for a given type-val pair 121 | // type should be either NETNSA_PID or NETNSA_FD 122 | // The ID can only be set for namespaces without an ID already set 123 | func (h *Handle) setNetNsId(attrType int, val uint32, newnsid uint32) error { 124 | req := h.newNetlinkRequest(unix.RTM_NEWNSID, unix.NLM_F_REQUEST|unix.NLM_F_ACK) 125 | 126 | rtgen := nl.NewRtGenMsg() 127 | req.AddData(rtgen) 128 | 129 | b := make([]byte, 4) 130 | native.PutUint32(b, val) 131 | attr := nl.NewRtAttr(attrType, b) 132 | req.AddData(attr) 133 | 134 | b1 := make([]byte, 4) 135 | native.PutUint32(b1, newnsid) 136 | attr1 := nl.NewRtAttr(NETNSA_NSID, b1) 137 | req.AddData(attr1) 138 | 139 | _, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID) 140 | return err 141 | } 142 | -------------------------------------------------------------------------------- /netns_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package netlink 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | "syscall" 9 | "testing" 10 | 11 | "github.com/vishvananda/netns" 12 | ) 13 | 14 | // TestNetNsIdByFd tests setting and getting the network namespace ID 15 | // by file descriptor. It opens a namespace fd, sets it to a random id, 16 | // then retrieves the ID. 17 | // This does not do any namespace switching. 18 | func TestNetNsIdByFd(t *testing.T) { 19 | skipUnlessRoot(t) 20 | // create a network namespace 21 | ns, err := netns.New() 22 | CheckErrorFail(t, err) 23 | 24 | // set its ID 25 | // In an attempt to avoid namespace id collisions, set this to something 26 | // insanely high. When the kernel assigns IDs, it does so starting from 0 27 | // So, just use our pid shifted up 16 bits 28 | wantID := os.Getpid() << 16 29 | 30 | h, err := NewHandle() 31 | CheckErrorFail(t, err) 32 | err = h.SetNetNsIdByFd(int(ns), wantID) 33 | CheckErrorFail(t, err) 34 | 35 | // Get the ID back, make sure it matches 36 | haveID, _ := h.GetNetNsIdByFd(int(ns)) 37 | if haveID != wantID { 38 | t.Errorf("GetNetNsIdByFd returned %d, want %d", haveID, wantID) 39 | } 40 | 41 | ns.Close() 42 | } 43 | 44 | // TestNetNsIdByPid tests manipulating namespace IDs by pid (really, task / thread id) 45 | // Does the same as TestNetNsIdByFd, but we need to change namespaces so we 46 | // actually have a pid in that namespace 47 | func TestNetNsIdByPid(t *testing.T) { 48 | skipUnlessRoot(t) 49 | runtime.LockOSThread() // we need a constant OS thread 50 | origNs, _ := netns.Get() 51 | 52 | // create and enter a new netns 53 | ns, err := netns.New() 54 | CheckErrorFail(t, err) 55 | err = netns.Set(ns) 56 | CheckErrorFail(t, err) 57 | // make sure we go back to the original namespace when done 58 | defer func() { 59 | err := netns.Set(origNs) 60 | if err != nil { 61 | panic("failed to restore network ns, bailing") 62 | } 63 | runtime.UnlockOSThread() 64 | }() 65 | 66 | // As above, we'll pick a crazy large netnsid to avoid collisions 67 | wantID := syscall.Gettid() << 16 68 | 69 | h, err := NewHandle() 70 | CheckErrorFail(t, err) 71 | err = h.SetNetNsIdByPid(syscall.Gettid(), wantID) 72 | CheckErrorFail(t, err) 73 | 74 | //Get the ID and see if it worked 75 | haveID, _ := h.GetNetNsIdByPid(syscall.Gettid()) 76 | if haveID != wantID { 77 | t.Errorf("GetNetNsIdByPid returned %d, want %d", haveID, wantID) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /netns_unspecified.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package netlink 4 | 5 | func GetNetNsIdByPid(pid int) (int, error) { 6 | return 0, ErrNotImplemented 7 | } 8 | 9 | func SetNetNsIdByPid(pid, nsid int) error { 10 | return ErrNotImplemented 11 | } 12 | 13 | func GetNetNsIdByFd(fd int) (int, error) { 14 | return 0, ErrNotImplemented 15 | } 16 | 17 | func SetNetNsIdByFd(fd, nsid int) error { 18 | return ErrNotImplemented 19 | } 20 | -------------------------------------------------------------------------------- /nl/addr_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | type IfAddrmsg struct { 10 | unix.IfAddrmsg 11 | } 12 | 13 | func NewIfAddrmsg(family int) *IfAddrmsg { 14 | return &IfAddrmsg{ 15 | IfAddrmsg: unix.IfAddrmsg{ 16 | Family: uint8(family), 17 | }, 18 | } 19 | } 20 | 21 | // struct ifaddrmsg { 22 | // __u8 ifa_family; 23 | // __u8 ifa_prefixlen; /* The prefix length */ 24 | // __u8 ifa_flags; /* Flags */ 25 | // __u8 ifa_scope; /* Address scope */ 26 | // __u32 ifa_index; /* Link index */ 27 | // }; 28 | 29 | // type IfAddrmsg struct { 30 | // Family uint8 31 | // Prefixlen uint8 32 | // Flags uint8 33 | // Scope uint8 34 | // Index uint32 35 | // } 36 | // SizeofIfAddrmsg = 0x8 37 | 38 | func DeserializeIfAddrmsg(b []byte) *IfAddrmsg { 39 | return (*IfAddrmsg)(unsafe.Pointer(&b[0:unix.SizeofIfAddrmsg][0])) 40 | } 41 | 42 | func (msg *IfAddrmsg) Serialize() []byte { 43 | return (*(*[unix.SizeofIfAddrmsg]byte)(unsafe.Pointer(msg)))[:] 44 | } 45 | 46 | func (msg *IfAddrmsg) Len() int { 47 | return unix.SizeofIfAddrmsg 48 | } 49 | 50 | // struct ifa_cacheinfo { 51 | // __u32 ifa_prefered; 52 | // __u32 ifa_valid; 53 | // __u32 cstamp; /* created timestamp, hundredths of seconds */ 54 | // __u32 tstamp; /* updated timestamp, hundredths of seconds */ 55 | // }; 56 | 57 | type IfaCacheInfo struct { 58 | unix.IfaCacheinfo 59 | } 60 | 61 | func (msg *IfaCacheInfo) Len() int { 62 | return unix.SizeofIfaCacheinfo 63 | } 64 | 65 | func DeserializeIfaCacheInfo(b []byte) *IfaCacheInfo { 66 | return (*IfaCacheInfo)(unsafe.Pointer(&b[0:unix.SizeofIfaCacheinfo][0])) 67 | } 68 | 69 | func (msg *IfaCacheInfo) Serialize() []byte { 70 | return (*(*[unix.SizeofIfaCacheinfo]byte)(unsafe.Pointer(msg)))[:] 71 | } 72 | -------------------------------------------------------------------------------- /nl/addr_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func (msg *IfAddrmsg) write(b []byte) { 13 | native := NativeEndian() 14 | b[0] = msg.Family 15 | b[1] = msg.Prefixlen 16 | b[2] = msg.Flags 17 | b[3] = msg.Scope 18 | native.PutUint32(b[4:8], msg.Index) 19 | } 20 | 21 | func (msg *IfAddrmsg) serializeSafe() []byte { 22 | len := unix.SizeofIfAddrmsg 23 | b := make([]byte, len) 24 | msg.write(b) 25 | return b 26 | } 27 | 28 | func deserializeIfAddrmsgSafe(b []byte) *IfAddrmsg { 29 | var msg = IfAddrmsg{} 30 | binary.Read(bytes.NewReader(b[0:unix.SizeofIfAddrmsg]), NativeEndian(), &msg) 31 | return &msg 32 | } 33 | 34 | func TestIfAddrmsgDeserializeSerialize(t *testing.T) { 35 | var orig = make([]byte, unix.SizeofIfAddrmsg) 36 | rand.Read(orig) 37 | safemsg := deserializeIfAddrmsgSafe(orig) 38 | msg := DeserializeIfAddrmsg(orig) 39 | testDeserializeSerialize(t, orig, safemsg, msg) 40 | } 41 | 42 | func (msg *IfaCacheInfo) write(b []byte) { 43 | native := NativeEndian() 44 | native.PutUint32(b[0:4], uint32(msg.Prefered)) 45 | native.PutUint32(b[4:8], uint32(msg.Valid)) 46 | native.PutUint32(b[8:12], uint32(msg.Cstamp)) 47 | native.PutUint32(b[12:16], uint32(msg.Tstamp)) 48 | } 49 | 50 | func (msg *IfaCacheInfo) serializeSafe() []byte { 51 | length := unix.SizeofIfaCacheinfo 52 | b := make([]byte, length) 53 | msg.write(b) 54 | return b 55 | } 56 | 57 | func deserializeIfaCacheInfoSafe(b []byte) *IfaCacheInfo { 58 | var msg = IfaCacheInfo{} 59 | binary.Read(bytes.NewReader(b[0:unix.SizeofIfaCacheinfo]), NativeEndian(), &msg) 60 | return &msg 61 | } 62 | 63 | func TestIfaCacheInfoDeserializeSerialize(t *testing.T) { 64 | var orig = make([]byte, unix.SizeofIfaCacheinfo) 65 | rand.Read(orig) 66 | safemsg := deserializeIfaCacheInfoSafe(orig) 67 | msg := DeserializeIfaCacheInfo(orig) 68 | testDeserializeSerialize(t, orig, safemsg, msg) 69 | } 70 | -------------------------------------------------------------------------------- /nl/bridge_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | SizeofBridgeVlanInfo = 0x04 10 | ) 11 | 12 | /* Bridge Flags */ 13 | const ( 14 | BRIDGE_FLAGS_MASTER = iota + 1 /* Bridge command to/from master */ 15 | BRIDGE_FLAGS_SELF /* Bridge command to/from lowerdev */ 16 | ) 17 | 18 | /* Bridge management nested attributes 19 | * [IFLA_AF_SPEC] = { 20 | * [IFLA_BRIDGE_FLAGS] 21 | * [IFLA_BRIDGE_MODE] 22 | * [IFLA_BRIDGE_VLAN_INFO] 23 | * } 24 | */ 25 | const ( 26 | IFLA_BRIDGE_FLAGS = iota 27 | IFLA_BRIDGE_MODE 28 | IFLA_BRIDGE_VLAN_INFO 29 | IFLA_BRIDGE_VLAN_TUNNEL_INFO 30 | ) 31 | 32 | const ( 33 | IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC = iota 34 | IFLA_BRIDGE_VLAN_TUNNEL_ID 35 | IFLA_BRIDGE_VLAN_TUNNEL_VID 36 | IFLA_BRIDGE_VLAN_TUNNEL_FLAGS 37 | ) 38 | 39 | const ( 40 | BRIDGE_VLAN_INFO_MASTER = 1 << iota 41 | BRIDGE_VLAN_INFO_PVID 42 | BRIDGE_VLAN_INFO_UNTAGGED 43 | BRIDGE_VLAN_INFO_RANGE_BEGIN 44 | BRIDGE_VLAN_INFO_RANGE_END 45 | ) 46 | 47 | // struct bridge_vlan_info { 48 | // __u16 flags; 49 | // __u16 vid; 50 | // }; 51 | 52 | type TunnelInfo struct { 53 | TunId uint32 54 | Vid uint16 55 | } 56 | 57 | type BridgeVlanInfo struct { 58 | Flags uint16 59 | Vid uint16 60 | } 61 | 62 | func (b *BridgeVlanInfo) Serialize() []byte { 63 | return (*(*[SizeofBridgeVlanInfo]byte)(unsafe.Pointer(b)))[:] 64 | } 65 | 66 | func DeserializeBridgeVlanInfo(b []byte) *BridgeVlanInfo { 67 | return (*BridgeVlanInfo)(unsafe.Pointer(&b[0:SizeofBridgeVlanInfo][0])) 68 | } 69 | 70 | func (b *BridgeVlanInfo) PortVID() bool { 71 | return b.Flags&BRIDGE_VLAN_INFO_PVID > 0 72 | } 73 | 74 | func (b *BridgeVlanInfo) EngressUntag() bool { 75 | return b.Flags&BRIDGE_VLAN_INFO_UNTAGGED > 0 76 | } 77 | 78 | func (b *BridgeVlanInfo) String() string { 79 | return fmt.Sprintf("%+v", *b) 80 | } 81 | 82 | /* New extended info filters for IFLA_EXT_MASK */ 83 | const ( 84 | RTEXT_FILTER_VF = 1 << iota 85 | RTEXT_FILTER_BRVLAN 86 | RTEXT_FILTER_BRVLAN_COMPRESSED 87 | ) 88 | -------------------------------------------------------------------------------- /nl/bridge_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | func (msg *BridgeVlanInfo) write(b []byte) { 11 | native := NativeEndian() 12 | native.PutUint16(b[0:2], msg.Flags) 13 | native.PutUint16(b[2:4], msg.Vid) 14 | } 15 | 16 | func (msg *BridgeVlanInfo) serializeSafe() []byte { 17 | length := SizeofBridgeVlanInfo 18 | b := make([]byte, length) 19 | msg.write(b) 20 | return b 21 | } 22 | 23 | func deserializeBridgeVlanInfoSafe(b []byte) *BridgeVlanInfo { 24 | var msg = BridgeVlanInfo{} 25 | binary.Read(bytes.NewReader(b[0:SizeofBridgeVlanInfo]), NativeEndian(), &msg) 26 | return &msg 27 | } 28 | 29 | func TestBridgeVlanInfoDeserializeSerialize(t *testing.T) { 30 | var orig = make([]byte, SizeofBridgeVlanInfo) 31 | rand.Read(orig) 32 | safemsg := deserializeBridgeVlanInfoSafe(orig) 33 | msg := DeserializeBridgeVlanInfo(orig) 34 | testDeserializeSerialize(t, orig, safemsg, msg) 35 | } 36 | -------------------------------------------------------------------------------- /nl/devlink_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | // All the following constants are coming from: 4 | // https://github.com/torvalds/linux/blob/master/include/uapi/linux/devlink.h 5 | 6 | const ( 7 | GENL_DEVLINK_VERSION = 1 8 | GENL_DEVLINK_NAME = "devlink" 9 | ) 10 | 11 | const ( 12 | DEVLINK_CMD_GET = 1 13 | DEVLINK_CMD_PORT_GET = 5 14 | DEVLINK_CMD_PORT_SET = 6 15 | DEVLINK_CMD_PORT_NEW = 7 16 | DEVLINK_CMD_PORT_DEL = 8 17 | DEVLINK_CMD_ESWITCH_GET = 29 18 | DEVLINK_CMD_ESWITCH_SET = 30 19 | DEVLINK_CMD_RESOURCE_DUMP = 36 20 | DEVLINK_CMD_PARAM_GET = 38 21 | DEVLINK_CMD_PARAM_SET = 39 22 | DEVLINK_CMD_INFO_GET = 51 23 | ) 24 | 25 | const ( 26 | DEVLINK_ATTR_BUS_NAME = 1 27 | DEVLINK_ATTR_DEV_NAME = 2 28 | DEVLINK_ATTR_PORT_INDEX = 3 29 | DEVLINK_ATTR_PORT_TYPE = 4 30 | DEVLINK_ATTR_PORT_NETDEV_IFINDEX = 6 31 | DEVLINK_ATTR_PORT_NETDEV_NAME = 7 32 | DEVLINK_ATTR_PORT_IBDEV_NAME = 8 33 | DEVLINK_ATTR_ESWITCH_MODE = 25 34 | DEVLINK_ATTR_ESWITCH_INLINE_MODE = 26 35 | DEVLINK_ATTR_ESWITCH_ENCAP_MODE = 62 36 | DEVLINK_ATTR_RESOURCE_LIST = 63 /* nested */ 37 | DEVLINK_ATTR_RESOURCE = 64 /* nested */ 38 | DEVLINK_ATTR_RESOURCE_NAME = 65 /* string */ 39 | DEVLINK_ATTR_RESOURCE_ID = 66 /* u64 */ 40 | DEVLINK_ATTR_RESOURCE_SIZE = 67 /* u64 */ 41 | DEVLINK_ATTR_RESOURCE_SIZE_NEW = 68 /* u64 */ 42 | DEVLINK_ATTR_RESOURCE_SIZE_VALID = 69 /* u8 */ 43 | DEVLINK_ATTR_RESOURCE_SIZE_MIN = 70 /* u64 */ 44 | DEVLINK_ATTR_RESOURCE_SIZE_MAX = 71 /* u64 */ 45 | DEVLINK_ATTR_RESOURCE_SIZE_GRAN = 72 /* u64 */ 46 | DEVLINK_ATTR_RESOURCE_UNIT = 73 /* u8 */ 47 | DEVLINK_ATTR_RESOURCE_OCC = 74 /* u64 */ 48 | DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID = 75 /* u64 */ 49 | DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS = 76 /* u64 */ 50 | DEVLINK_ATTR_PORT_FLAVOUR = 77 51 | DEVLINK_ATTR_INFO_DRIVER_NAME = 98 52 | DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99 53 | DEVLINK_ATTR_INFO_VERSION_FIXED = 100 54 | DEVLINK_ATTR_INFO_VERSION_RUNNING = 101 55 | DEVLINK_ATTR_INFO_VERSION_STORED = 102 56 | DEVLINK_ATTR_INFO_VERSION_NAME = 103 57 | DEVLINK_ATTR_INFO_VERSION_VALUE = 104 58 | DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127 59 | DEVLINK_ATTR_PORT_FUNCTION = 145 60 | DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150 61 | DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164 62 | ) 63 | 64 | const ( 65 | DEVLINK_ESWITCH_MODE_LEGACY = 0 66 | DEVLINK_ESWITCH_MODE_SWITCHDEV = 1 67 | ) 68 | 69 | const ( 70 | DEVLINK_ESWITCH_INLINE_MODE_NONE = 0 71 | DEVLINK_ESWITCH_INLINE_MODE_LINK = 1 72 | DEVLINK_ESWITCH_INLINE_MODE_NETWORK = 2 73 | DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT = 3 74 | ) 75 | 76 | const ( 77 | DEVLINK_ESWITCH_ENCAP_MODE_NONE = 0 78 | DEVLINK_ESWITCH_ENCAP_MODE_BASIC = 1 79 | ) 80 | 81 | const ( 82 | DEVLINK_PORT_FLAVOUR_PHYSICAL = 0 83 | DEVLINK_PORT_FLAVOUR_CPU = 1 84 | DEVLINK_PORT_FLAVOUR_DSA = 2 85 | DEVLINK_PORT_FLAVOUR_PCI_PF = 3 86 | DEVLINK_PORT_FLAVOUR_PCI_VF = 4 87 | DEVLINK_PORT_FLAVOUR_VIRTUAL = 5 88 | DEVLINK_PORT_FLAVOUR_UNUSED = 6 89 | DEVLINK_PORT_FLAVOUR_PCI_SF = 7 90 | ) 91 | 92 | const ( 93 | DEVLINK_PORT_TYPE_NOTSET = 0 94 | DEVLINK_PORT_TYPE_AUTO = 1 95 | DEVLINK_PORT_TYPE_ETH = 2 96 | DEVLINK_PORT_TYPE_IB = 3 97 | ) 98 | 99 | const ( 100 | DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR = 1 101 | DEVLINK_PORT_FN_ATTR_STATE = 2 102 | DEVLINK_PORT_FN_ATTR_OPSTATE = 3 103 | ) 104 | 105 | const ( 106 | DEVLINK_PORT_FN_STATE_INACTIVE = 0 107 | DEVLINK_PORT_FN_STATE_ACTIVE = 1 108 | ) 109 | 110 | const ( 111 | DEVLINK_PORT_FN_OPSTATE_DETACHED = 0 112 | DEVLINK_PORT_FN_OPSTATE_ATTACHED = 1 113 | ) 114 | 115 | const ( 116 | DEVLINK_RESOURCE_UNIT_ENTRY uint8 = 0 117 | ) 118 | 119 | const ( 120 | DEVLINK_ATTR_PARAM = iota + 80 /* nested */ 121 | DEVLINK_ATTR_PARAM_NAME /* string */ 122 | DEVLINK_ATTR_PARAM_GENERIC /* flag */ 123 | DEVLINK_ATTR_PARAM_TYPE /* u8 */ 124 | DEVLINK_ATTR_PARAM_VALUES_LIST /* nested */ 125 | DEVLINK_ATTR_PARAM_VALUE /* nested */ 126 | DEVLINK_ATTR_PARAM_VALUE_DATA /* dynamic */ 127 | DEVLINK_ATTR_PARAM_VALUE_CMODE /* u8 */ 128 | ) 129 | 130 | const ( 131 | DEVLINK_PARAM_TYPE_U8 = 1 132 | DEVLINK_PARAM_TYPE_U16 = 2 133 | DEVLINK_PARAM_TYPE_U32 = 3 134 | DEVLINK_PARAM_TYPE_STRING = 5 135 | DEVLINK_PARAM_TYPE_BOOL = 6 136 | ) 137 | 138 | const ( 139 | DEVLINK_PARAM_CMODE_RUNTIME = iota 140 | DEVLINK_PARAM_CMODE_DRIVERINIT 141 | DEVLINK_PARAM_CMODE_PERMANENT 142 | ) 143 | -------------------------------------------------------------------------------- /nl/genetlink_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | const SizeofGenlmsg = 4 8 | 9 | const ( 10 | GENL_ID_CTRL = 0x10 11 | GENL_CTRL_VERSION = 2 12 | GENL_CTRL_NAME = "nlctrl" 13 | ) 14 | 15 | const ( 16 | GENL_CTRL_CMD_GETFAMILY = 3 17 | ) 18 | 19 | const ( 20 | GENL_CTRL_ATTR_UNSPEC = iota 21 | GENL_CTRL_ATTR_FAMILY_ID 22 | GENL_CTRL_ATTR_FAMILY_NAME 23 | GENL_CTRL_ATTR_VERSION 24 | GENL_CTRL_ATTR_HDRSIZE 25 | GENL_CTRL_ATTR_MAXATTR 26 | GENL_CTRL_ATTR_OPS 27 | GENL_CTRL_ATTR_MCAST_GROUPS 28 | ) 29 | 30 | const ( 31 | GENL_CTRL_ATTR_OP_UNSPEC = iota 32 | GENL_CTRL_ATTR_OP_ID 33 | GENL_CTRL_ATTR_OP_FLAGS 34 | ) 35 | 36 | const ( 37 | GENL_ADMIN_PERM = 1 << iota 38 | GENL_CMD_CAP_DO 39 | GENL_CMD_CAP_DUMP 40 | GENL_CMD_CAP_HASPOL 41 | ) 42 | 43 | const ( 44 | GENL_CTRL_ATTR_MCAST_GRP_UNSPEC = iota 45 | GENL_CTRL_ATTR_MCAST_GRP_NAME 46 | GENL_CTRL_ATTR_MCAST_GRP_ID 47 | ) 48 | 49 | const ( 50 | GENL_GTP_VERSION = 0 51 | GENL_GTP_NAME = "gtp" 52 | ) 53 | 54 | const ( 55 | GENL_GTP_CMD_NEWPDP = iota 56 | GENL_GTP_CMD_DELPDP 57 | GENL_GTP_CMD_GETPDP 58 | ) 59 | 60 | const ( 61 | GENL_GTP_ATTR_UNSPEC = iota 62 | GENL_GTP_ATTR_LINK 63 | GENL_GTP_ATTR_VERSION 64 | GENL_GTP_ATTR_TID 65 | GENL_GTP_ATTR_PEER_ADDRESS 66 | GENL_GTP_ATTR_MS_ADDRESS 67 | GENL_GTP_ATTR_FLOW 68 | GENL_GTP_ATTR_NET_NS_FD 69 | GENL_GTP_ATTR_I_TEI 70 | GENL_GTP_ATTR_O_TEI 71 | GENL_GTP_ATTR_PAD 72 | ) 73 | 74 | type Genlmsg struct { 75 | Command uint8 76 | Version uint8 77 | } 78 | 79 | func (msg *Genlmsg) Len() int { 80 | return SizeofGenlmsg 81 | } 82 | 83 | func DeserializeGenlmsg(b []byte) *Genlmsg { 84 | return (*Genlmsg)(unsafe.Pointer(&b[0:SizeofGenlmsg][0])) 85 | } 86 | 87 | func (msg *Genlmsg) Serialize() []byte { 88 | return (*(*[SizeofGenlmsg]byte)(unsafe.Pointer(msg)))[:] 89 | } 90 | -------------------------------------------------------------------------------- /nl/ip6tnl_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | // id's of route attribute from https://elixir.bootlin.com/linux/v5.17.3/source/include/uapi/linux/lwtunnel.h#L38 4 | // the value's size are specified in https://elixir.bootlin.com/linux/v5.17.3/source/net/ipv4/ip_tunnel_core.c#L928 5 | 6 | const ( 7 | LWTUNNEL_IP6_UNSPEC = iota 8 | LWTUNNEL_IP6_ID 9 | LWTUNNEL_IP6_DST 10 | LWTUNNEL_IP6_SRC 11 | LWTUNNEL_IP6_HOPLIMIT 12 | LWTUNNEL_IP6_TC 13 | LWTUNNEL_IP6_FLAGS 14 | LWTUNNEL_IP6_PAD // not implemented 15 | LWTUNNEL_IP6_OPTS // not implemented 16 | __LWTUNNEL_IP6_MAX 17 | ) 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /nl/ipset_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "strconv" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | const ( 10 | /* The protocol version */ 11 | IPSET_PROTOCOL = 6 12 | 13 | /* The max length of strings including NUL: set and type identifiers */ 14 | IPSET_MAXNAMELEN = 32 15 | 16 | /* The maximum permissible comment length we will accept over netlink */ 17 | IPSET_MAX_COMMENT_SIZE = 255 18 | ) 19 | 20 | const ( 21 | _ = iota 22 | IPSET_CMD_PROTOCOL /* 1: Return protocol version */ 23 | IPSET_CMD_CREATE /* 2: Create a new (empty) set */ 24 | IPSET_CMD_DESTROY /* 3: Destroy a (empty) set */ 25 | IPSET_CMD_FLUSH /* 4: Remove all elements from a set */ 26 | IPSET_CMD_RENAME /* 5: Rename a set */ 27 | IPSET_CMD_SWAP /* 6: Swap two sets */ 28 | IPSET_CMD_LIST /* 7: List sets */ 29 | IPSET_CMD_SAVE /* 8: Save sets */ 30 | IPSET_CMD_ADD /* 9: Add an element to a set */ 31 | IPSET_CMD_DEL /* 10: Delete an element from a set */ 32 | IPSET_CMD_TEST /* 11: Test an element in a set */ 33 | IPSET_CMD_HEADER /* 12: Get set header data only */ 34 | IPSET_CMD_TYPE /* 13: Get set type */ 35 | ) 36 | 37 | /* Attributes at command level */ 38 | const ( 39 | _ = iota 40 | IPSET_ATTR_PROTOCOL /* 1: Protocol version */ 41 | IPSET_ATTR_SETNAME /* 2: Name of the set */ 42 | IPSET_ATTR_TYPENAME /* 3: Typename */ 43 | IPSET_ATTR_REVISION /* 4: Settype revision */ 44 | IPSET_ATTR_FAMILY /* 5: Settype family */ 45 | IPSET_ATTR_FLAGS /* 6: Flags at command level */ 46 | IPSET_ATTR_DATA /* 7: Nested attributes */ 47 | IPSET_ATTR_ADT /* 8: Multiple data containers */ 48 | IPSET_ATTR_LINENO /* 9: Restore lineno */ 49 | IPSET_ATTR_PROTOCOL_MIN /* 10: Minimal supported version number */ 50 | 51 | IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME /* Setname at rename/swap */ 52 | IPSET_ATTR_REVISION_MIN = IPSET_ATTR_PROTOCOL_MIN /* type rev min */ 53 | ) 54 | 55 | /* CADT specific attributes */ 56 | const ( 57 | IPSET_ATTR_IP = 1 58 | IPSET_ATTR_IP_FROM = 1 59 | IPSET_ATTR_IP_TO = 2 60 | IPSET_ATTR_CIDR = 3 61 | IPSET_ATTR_PORT = 4 62 | IPSET_ATTR_PORT_FROM = 4 63 | IPSET_ATTR_PORT_TO = 5 64 | IPSET_ATTR_TIMEOUT = 6 65 | IPSET_ATTR_PROTO = 7 66 | IPSET_ATTR_CADT_FLAGS = 8 67 | IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO /* 9 */ 68 | IPSET_ATTR_MARK = 10 69 | IPSET_ATTR_MARKMASK = 11 70 | 71 | /* Reserve empty slots */ 72 | IPSET_ATTR_CADT_MAX = 16 73 | 74 | /* Create-only specific attributes */ 75 | IPSET_ATTR_GC = 3 + iota 76 | IPSET_ATTR_HASHSIZE 77 | IPSET_ATTR_MAXELEM 78 | IPSET_ATTR_NETMASK 79 | IPSET_ATTR_PROBES 80 | IPSET_ATTR_RESIZE 81 | IPSET_ATTR_SIZE 82 | 83 | /* Kernel-only */ 84 | IPSET_ATTR_ELEMENTS 85 | IPSET_ATTR_REFERENCES 86 | IPSET_ATTR_MEMSIZE 87 | 88 | SET_ATTR_CREATE_MAX 89 | ) 90 | 91 | const ( 92 | IPSET_ATTR_IPADDR_IPV4 = 1 93 | IPSET_ATTR_IPADDR_IPV6 = 2 94 | ) 95 | 96 | /* ADT specific attributes */ 97 | const ( 98 | IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + iota + 1 99 | IPSET_ATTR_NAME 100 | IPSET_ATTR_NAMEREF 101 | IPSET_ATTR_IP2 102 | IPSET_ATTR_CIDR2 103 | IPSET_ATTR_IP2_TO 104 | IPSET_ATTR_IFACE 105 | IPSET_ATTR_BYTES 106 | IPSET_ATTR_PACKETS 107 | IPSET_ATTR_COMMENT 108 | IPSET_ATTR_SKBMARK 109 | IPSET_ATTR_SKBPRIO 110 | IPSET_ATTR_SKBQUEUE 111 | ) 112 | 113 | /* Flags at CADT attribute level, upper half of cmdattrs */ 114 | const ( 115 | IPSET_FLAG_BIT_BEFORE = 0 116 | IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE) 117 | IPSET_FLAG_BIT_PHYSDEV = 1 118 | IPSET_FLAG_PHYSDEV = (1 << IPSET_FLAG_BIT_PHYSDEV) 119 | IPSET_FLAG_BIT_NOMATCH = 2 120 | IPSET_FLAG_NOMATCH = (1 << IPSET_FLAG_BIT_NOMATCH) 121 | IPSET_FLAG_BIT_WITH_COUNTERS = 3 122 | IPSET_FLAG_WITH_COUNTERS = (1 << IPSET_FLAG_BIT_WITH_COUNTERS) 123 | IPSET_FLAG_BIT_WITH_COMMENT = 4 124 | IPSET_FLAG_WITH_COMMENT = (1 << IPSET_FLAG_BIT_WITH_COMMENT) 125 | IPSET_FLAG_BIT_WITH_FORCEADD = 5 126 | IPSET_FLAG_WITH_FORCEADD = (1 << IPSET_FLAG_BIT_WITH_FORCEADD) 127 | IPSET_FLAG_BIT_WITH_SKBINFO = 6 128 | IPSET_FLAG_WITH_SKBINFO = (1 << IPSET_FLAG_BIT_WITH_SKBINFO) 129 | IPSET_FLAG_CADT_MAX = 15 130 | ) 131 | 132 | const ( 133 | IPSET_ERR_PRIVATE = 4096 + iota 134 | IPSET_ERR_PROTOCOL 135 | IPSET_ERR_FIND_TYPE 136 | IPSET_ERR_MAX_SETS 137 | IPSET_ERR_BUSY 138 | IPSET_ERR_EXIST_SETNAME2 139 | IPSET_ERR_TYPE_MISMATCH 140 | IPSET_ERR_EXIST 141 | IPSET_ERR_INVALID_CIDR 142 | IPSET_ERR_INVALID_NETMASK 143 | IPSET_ERR_INVALID_FAMILY 144 | IPSET_ERR_TIMEOUT 145 | IPSET_ERR_REFERENCED 146 | IPSET_ERR_IPADDR_IPV4 147 | IPSET_ERR_IPADDR_IPV6 148 | IPSET_ERR_COUNTER 149 | IPSET_ERR_COMMENT 150 | IPSET_ERR_INVALID_MARKMASK 151 | IPSET_ERR_SKBINFO 152 | 153 | /* Type specific error codes */ 154 | IPSET_ERR_TYPE_SPECIFIC = 4352 155 | ) 156 | 157 | type IPSetError uintptr 158 | 159 | func (e IPSetError) Error() string { 160 | switch int(e) { 161 | case IPSET_ERR_PRIVATE: 162 | return "private" 163 | case IPSET_ERR_PROTOCOL: 164 | return "invalid protocol" 165 | case IPSET_ERR_FIND_TYPE: 166 | return "invalid type" 167 | case IPSET_ERR_MAX_SETS: 168 | return "max sets reached" 169 | case IPSET_ERR_BUSY: 170 | return "busy" 171 | case IPSET_ERR_EXIST_SETNAME2: 172 | return "exist_setname2" 173 | case IPSET_ERR_TYPE_MISMATCH: 174 | return "type mismatch" 175 | case IPSET_ERR_EXIST: 176 | return "exist" 177 | case IPSET_ERR_INVALID_CIDR: 178 | return "invalid cidr" 179 | case IPSET_ERR_INVALID_NETMASK: 180 | return "invalid netmask" 181 | case IPSET_ERR_INVALID_FAMILY: 182 | return "invalid family" 183 | case IPSET_ERR_TIMEOUT: 184 | return "timeout" 185 | case IPSET_ERR_REFERENCED: 186 | return "referenced" 187 | case IPSET_ERR_IPADDR_IPV4: 188 | return "invalid ipv4 address" 189 | case IPSET_ERR_IPADDR_IPV6: 190 | return "invalid ipv6 address" 191 | case IPSET_ERR_COUNTER: 192 | return "invalid counter" 193 | case IPSET_ERR_COMMENT: 194 | return "invalid comment" 195 | case IPSET_ERR_INVALID_MARKMASK: 196 | return "invalid markmask" 197 | case IPSET_ERR_SKBINFO: 198 | return "skbinfo" 199 | default: 200 | return "errno " + strconv.Itoa(int(e)) 201 | } 202 | } 203 | 204 | func GetIpsetFlags(cmd int) int { 205 | switch cmd { 206 | case IPSET_CMD_CREATE: 207 | return unix.NLM_F_REQUEST | unix.NLM_F_ACK | unix.NLM_F_CREATE 208 | case IPSET_CMD_DESTROY, 209 | IPSET_CMD_FLUSH, 210 | IPSET_CMD_RENAME, 211 | IPSET_CMD_SWAP, 212 | IPSET_CMD_TEST: 213 | return unix.NLM_F_REQUEST | unix.NLM_F_ACK 214 | case IPSET_CMD_LIST, 215 | IPSET_CMD_SAVE: 216 | return unix.NLM_F_REQUEST | unix.NLM_F_ACK | unix.NLM_F_ROOT | unix.NLM_F_MATCH | unix.NLM_F_DUMP 217 | case IPSET_CMD_ADD, 218 | IPSET_CMD_DEL: 219 | return unix.NLM_F_REQUEST | unix.NLM_F_ACK 220 | case IPSET_CMD_HEADER, 221 | IPSET_CMD_TYPE, 222 | IPSET_CMD_PROTOCOL: 223 | return unix.NLM_F_REQUEST 224 | default: 225 | return 0 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /nl/link_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | func (msg *VfMac) write(b []byte) { 11 | native := NativeEndian() 12 | native.PutUint32(b[0:4], uint32(msg.Vf)) 13 | copy(b[4:36], msg.Mac[:]) 14 | } 15 | 16 | func (msg *VfMac) serializeSafe() []byte { 17 | length := SizeofVfMac 18 | b := make([]byte, length) 19 | msg.write(b) 20 | return b 21 | } 22 | 23 | func deserializeVfMacSafe(b []byte) *VfMac { 24 | var msg = VfMac{} 25 | binary.Read(bytes.NewReader(b[0:SizeofVfMac]), NativeEndian(), &msg) 26 | return &msg 27 | } 28 | 29 | func TestVfMacDeserializeSerialize(t *testing.T) { 30 | var orig = make([]byte, SizeofVfMac) 31 | rand.Read(orig) 32 | safemsg := deserializeVfMacSafe(orig) 33 | msg := DeserializeVfMac(orig) 34 | testDeserializeSerialize(t, orig, safemsg, msg) 35 | } 36 | 37 | func (msg *VfVlan) write(b []byte) { 38 | native := NativeEndian() 39 | native.PutUint32(b[0:4], uint32(msg.Vf)) 40 | native.PutUint32(b[4:8], uint32(msg.Vlan)) 41 | native.PutUint32(b[8:12], uint32(msg.Qos)) 42 | } 43 | 44 | func (msg *VfVlan) serializeSafe() []byte { 45 | length := SizeofVfVlan 46 | b := make([]byte, length) 47 | msg.write(b) 48 | return b 49 | } 50 | 51 | func deserializeVfVlanSafe(b []byte) *VfVlan { 52 | var msg = VfVlan{} 53 | binary.Read(bytes.NewReader(b[0:SizeofVfVlan]), NativeEndian(), &msg) 54 | return &msg 55 | } 56 | 57 | func TestVfVlanDeserializeSerialize(t *testing.T) { 58 | var orig = make([]byte, SizeofVfVlan) 59 | rand.Read(orig) 60 | safemsg := deserializeVfVlanSafe(orig) 61 | msg := DeserializeVfVlan(orig) 62 | testDeserializeSerialize(t, orig, safemsg, msg) 63 | } 64 | 65 | func (msg *VfTxRate) write(b []byte) { 66 | native := NativeEndian() 67 | native.PutUint32(b[0:4], uint32(msg.Vf)) 68 | native.PutUint32(b[4:8], uint32(msg.Rate)) 69 | } 70 | 71 | func (msg *VfTxRate) serializeSafe() []byte { 72 | length := SizeofVfTxRate 73 | b := make([]byte, length) 74 | msg.write(b) 75 | return b 76 | } 77 | 78 | func deserializeVfTxRateSafe(b []byte) *VfTxRate { 79 | var msg = VfTxRate{} 80 | binary.Read(bytes.NewReader(b[0:SizeofVfTxRate]), NativeEndian(), &msg) 81 | return &msg 82 | } 83 | 84 | func TestVfTxRateDeserializeSerialize(t *testing.T) { 85 | var orig = make([]byte, SizeofVfTxRate) 86 | rand.Read(orig) 87 | safemsg := deserializeVfTxRateSafe(orig) 88 | msg := DeserializeVfTxRate(orig) 89 | testDeserializeSerialize(t, orig, safemsg, msg) 90 | } 91 | 92 | func (msg *VfRate) write(b []byte) { 93 | native := NativeEndian() 94 | native.PutUint32(b[0:4], uint32(msg.Vf)) 95 | native.PutUint32(b[4:8], uint32(msg.MinTxRate)) 96 | native.PutUint32(b[8:12], uint32(msg.MaxTxRate)) 97 | } 98 | 99 | func (msg *VfRate) serializeSafe() []byte { 100 | length := SizeofVfRate 101 | b := make([]byte, length) 102 | msg.write(b) 103 | return b 104 | } 105 | 106 | func deserializeVfRateSafe(b []byte) *VfRate { 107 | var msg = VfRate{} 108 | binary.Read(bytes.NewReader(b[0:SizeofVfRate]), NativeEndian(), &msg) 109 | return &msg 110 | } 111 | 112 | func TestVfRateDeserializeSerialize(t *testing.T) { 113 | var orig = make([]byte, SizeofVfRate) 114 | rand.Read(orig) 115 | safemsg := deserializeVfRateSafe(orig) 116 | msg := DeserializeVfRate(orig) 117 | testDeserializeSerialize(t, orig, safemsg, msg) 118 | } 119 | 120 | func (msg *VfSpoofchk) write(b []byte) { 121 | native := NativeEndian() 122 | native.PutUint32(b[0:4], uint32(msg.Vf)) 123 | native.PutUint32(b[4:8], uint32(msg.Setting)) 124 | } 125 | 126 | func (msg *VfSpoofchk) serializeSafe() []byte { 127 | length := SizeofVfSpoofchk 128 | b := make([]byte, length) 129 | msg.write(b) 130 | return b 131 | } 132 | 133 | func deserializeVfSpoofchkSafe(b []byte) *VfSpoofchk { 134 | var msg = VfSpoofchk{} 135 | binary.Read(bytes.NewReader(b[0:SizeofVfSpoofchk]), NativeEndian(), &msg) 136 | return &msg 137 | } 138 | 139 | func TestVfSpoofchkDeserializeSerialize(t *testing.T) { 140 | var orig = make([]byte, SizeofVfSpoofchk) 141 | rand.Read(orig) 142 | safemsg := deserializeVfSpoofchkSafe(orig) 143 | msg := DeserializeVfSpoofchk(orig) 144 | testDeserializeSerialize(t, orig, safemsg, msg) 145 | } 146 | 147 | func (msg *VfLinkState) write(b []byte) { 148 | native := NativeEndian() 149 | native.PutUint32(b[0:4], uint32(msg.Vf)) 150 | native.PutUint32(b[4:8], uint32(msg.LinkState)) 151 | } 152 | 153 | func (msg *VfLinkState) serializeSafe() []byte { 154 | length := SizeofVfLinkState 155 | b := make([]byte, length) 156 | msg.write(b) 157 | return b 158 | } 159 | 160 | func deserializeVfLinkStateSafe(b []byte) *VfLinkState { 161 | var msg = VfLinkState{} 162 | binary.Read(bytes.NewReader(b[0:SizeofVfLinkState]), NativeEndian(), &msg) 163 | return &msg 164 | } 165 | 166 | func TestVfLinkStateDeserializeSerialize(t *testing.T) { 167 | var orig = make([]byte, SizeofVfLinkState) 168 | rand.Read(orig) 169 | safemsg := deserializeVfLinkStateSafe(orig) 170 | msg := DeserializeVfLinkState(orig) 171 | testDeserializeSerialize(t, orig, safemsg, msg) 172 | } 173 | 174 | func (msg *VfRssQueryEn) write(b []byte) { 175 | native := NativeEndian() 176 | native.PutUint32(b[0:4], uint32(msg.Vf)) 177 | native.PutUint32(b[4:8], uint32(msg.Setting)) 178 | } 179 | 180 | func (msg *VfRssQueryEn) serializeSafe() []byte { 181 | length := SizeofVfRssQueryEn 182 | b := make([]byte, length) 183 | msg.write(b) 184 | return b 185 | } 186 | 187 | func deserializeVfRssQueryEnSafe(b []byte) *VfRssQueryEn { 188 | var msg = VfRssQueryEn{} 189 | binary.Read(bytes.NewReader(b[0:SizeofVfRssQueryEn]), NativeEndian(), &msg) 190 | return &msg 191 | } 192 | 193 | func TestVfRssQueryEnDeserializeSerialize(t *testing.T) { 194 | var orig = make([]byte, SizeofVfRssQueryEn) 195 | rand.Read(orig) 196 | safemsg := deserializeVfRssQueryEnSafe(orig) 197 | msg := DeserializeVfRssQueryEn(orig) 198 | testDeserializeSerialize(t, orig, safemsg, msg) 199 | } 200 | -------------------------------------------------------------------------------- /nl/lwt_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | const ( 4 | LWT_BPF_PROG_UNSPEC = iota 5 | LWT_BPF_PROG_FD 6 | LWT_BPF_PROG_NAME 7 | __LWT_BPF_PROG_MAX 8 | ) 9 | 10 | const ( 11 | LWT_BPF_PROG_MAX = __LWT_BPF_PROG_MAX - 1 12 | ) 13 | 14 | const ( 15 | LWT_BPF_UNSPEC = iota 16 | LWT_BPF_IN 17 | LWT_BPF_OUT 18 | LWT_BPF_XMIT 19 | LWT_BPF_XMIT_HEADROOM 20 | __LWT_BPF_MAX 21 | ) 22 | 23 | const ( 24 | LWT_BPF_MAX = __LWT_BPF_MAX - 1 25 | ) 26 | 27 | const ( 28 | LWT_BPF_MAX_HEADROOM = 256 29 | ) 30 | -------------------------------------------------------------------------------- /nl/mpls_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import "encoding/binary" 4 | 5 | const ( 6 | MPLS_LS_LABEL_SHIFT = 12 7 | MPLS_LS_S_SHIFT = 8 8 | ) 9 | 10 | func EncodeMPLSStack(labels ...int) []byte { 11 | b := make([]byte, 4*len(labels)) 12 | for idx, label := range labels { 13 | l := label << MPLS_LS_LABEL_SHIFT 14 | if idx == len(labels)-1 { 15 | l |= 1 << MPLS_LS_S_SHIFT 16 | } 17 | binary.BigEndian.PutUint32(b[idx*4:], uint32(l)) 18 | } 19 | return b 20 | } 21 | 22 | func DecodeMPLSStack(buf []byte) []int { 23 | if len(buf)%4 != 0 { 24 | return nil 25 | } 26 | stack := make([]int, 0, len(buf)/4) 27 | for len(buf) > 0 { 28 | l := binary.BigEndian.Uint32(buf[:4]) 29 | buf = buf[4:] 30 | stack = append(stack, int(l)>>MPLS_LS_LABEL_SHIFT) 31 | if (l>>MPLS_LS_S_SHIFT)&1 > 0 { 32 | break 33 | } 34 | } 35 | return stack 36 | } 37 | -------------------------------------------------------------------------------- /nl/nl_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "reflect" 8 | "testing" 9 | "time" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | type testSerializer interface { 15 | serializeSafe() []byte 16 | Serialize() []byte 17 | } 18 | 19 | func testDeserializeSerialize(t *testing.T, orig []byte, safemsg testSerializer, msg testSerializer) { 20 | if !reflect.DeepEqual(safemsg, msg) { 21 | t.Fatal("Deserialization failed.\n", safemsg, "\n", msg) 22 | } 23 | safe := msg.serializeSafe() 24 | if !bytes.Equal(safe, orig) { 25 | t.Fatal("Safe serialization failed.\n", safe, "\n", orig) 26 | } 27 | b := msg.Serialize() 28 | if !bytes.Equal(b, safe) { 29 | t.Fatal("Serialization failed.\n", b, "\n", safe) 30 | } 31 | } 32 | 33 | func (msg *IfInfomsg) write(b []byte) { 34 | native := NativeEndian() 35 | b[0] = msg.Family 36 | // pad byte is skipped because it is not exported on linux/s390x 37 | native.PutUint16(b[2:4], msg.Type) 38 | native.PutUint32(b[4:8], uint32(msg.Index)) 39 | native.PutUint32(b[8:12], msg.Flags) 40 | native.PutUint32(b[12:16], msg.Change) 41 | } 42 | 43 | func (msg *IfInfomsg) serializeSafe() []byte { 44 | length := unix.SizeofIfInfomsg 45 | b := make([]byte, length) 46 | msg.write(b) 47 | return b 48 | } 49 | 50 | func deserializeIfInfomsgSafe(b []byte) *IfInfomsg { 51 | var msg = IfInfomsg{} 52 | binary.Read(bytes.NewReader(b[0:unix.SizeofIfInfomsg]), NativeEndian(), &msg) 53 | return &msg 54 | } 55 | 56 | func TestIfInfomsgDeserializeSerialize(t *testing.T) { 57 | var orig = make([]byte, unix.SizeofIfInfomsg) 58 | rand.Read(orig) 59 | // zero out the pad byte 60 | orig[1] = 0 61 | safemsg := deserializeIfInfomsgSafe(orig) 62 | msg := DeserializeIfInfomsg(orig) 63 | testDeserializeSerialize(t, orig, safemsg, msg) 64 | } 65 | 66 | func TestIfSocketCloses(t *testing.T) { 67 | nlSock, err := Subscribe(unix.NETLINK_ROUTE, unix.RTNLGRP_NEIGH) 68 | if err != nil { 69 | t.Fatalf("Error on creating the socket: %v", err) 70 | } 71 | endCh := make(chan error) 72 | go func(sk *NetlinkSocket, endCh chan error) { 73 | endCh <- nil 74 | for { 75 | _, _, err := sk.Receive() 76 | if err == unix.EAGAIN { 77 | endCh <- err 78 | return 79 | } 80 | } 81 | }(nlSock, endCh) 82 | 83 | // first receive nil 84 | if msg := <-endCh; msg != nil { 85 | t.Fatalf("Expected nil instead got: %v", msg) 86 | } 87 | // this to guarantee that the receive is invoked before the close 88 | time.Sleep(4 * time.Second) 89 | 90 | // Close the socket 91 | nlSock.Close() 92 | 93 | // Expect to have an error 94 | msg := <-endCh 95 | if msg == nil { 96 | t.Fatalf("Expected error instead received nil") 97 | } 98 | } 99 | 100 | func TestReceiveTimeout(t *testing.T) { 101 | nlSock, err := getNetlinkSocket(unix.NETLINK_ROUTE) 102 | if err != nil { 103 | t.Fatalf("Error creating the socket: %v", err) 104 | } 105 | // Even if the test fails because the timeout doesn't work, closing the 106 | // socket at the end of the test should result in an EAGAIN (as long as 107 | // TestIfSocketCloses completed, otherwise this test will leak the 108 | // goroutines running the Receive). 109 | defer nlSock.Close() 110 | const failAfter = time.Second 111 | 112 | tests := []struct { 113 | name string 114 | timeout time.Duration 115 | }{ 116 | { 117 | name: "1us timeout", // The smallest value accepted by Handle.SetSocketTimeout 118 | timeout: time.Microsecond, 119 | }, 120 | { 121 | name: "100ms timeout", 122 | timeout: 100 * time.Millisecond, 123 | }, 124 | { 125 | name: "500ms timeout", 126 | timeout: 500 * time.Millisecond, 127 | }, 128 | } 129 | for _, tc := range tests { 130 | tc := tc 131 | t.Run(tc.name, func(t *testing.T) { 132 | timeout := unix.NsecToTimeval(int64(tc.timeout)) 133 | nlSock.SetReceiveTimeout(&timeout) 134 | 135 | doneC := make(chan time.Duration) 136 | errC := make(chan error) 137 | go func() { 138 | start := time.Now() 139 | _, _, err := nlSock.Receive() 140 | dur := time.Since(start) 141 | if err != unix.EAGAIN { 142 | errC <- err 143 | return 144 | } 145 | doneC <- dur 146 | }() 147 | 148 | failTimerC := time.After(failAfter) 149 | select { 150 | case dur := <-doneC: 151 | if dur < tc.timeout || dur > (tc.timeout+(100*time.Millisecond)) { 152 | t.Fatalf("Expected timeout %v got %v", tc.timeout, dur) 153 | } 154 | case err := <-errC: 155 | t.Fatalf("Expected EAGAIN, but got: %v", err) 156 | case <-failTimerC: 157 | t.Fatalf("No timeout received") 158 | } 159 | }) 160 | } 161 | } 162 | 163 | func (msg *CnMsgOp) write(b []byte) { 164 | native := NativeEndian() 165 | native.PutUint32(b[0:4], msg.ID.Idx) 166 | native.PutUint32(b[4:8], msg.ID.Val) 167 | native.PutUint32(b[8:12], msg.Seq) 168 | native.PutUint32(b[12:16], msg.Ack) 169 | native.PutUint16(b[16:18], msg.Length) 170 | native.PutUint16(b[18:20], msg.Flags) 171 | native.PutUint32(b[20:24], msg.Op) 172 | } 173 | 174 | func (msg *CnMsgOp) serializeSafe() []byte { 175 | length := msg.Len() 176 | b := make([]byte, length) 177 | msg.write(b) 178 | return b 179 | } 180 | 181 | func deserializeCnMsgOpSafe(b []byte) *CnMsgOp { 182 | var msg = CnMsgOp{} 183 | binary.Read(bytes.NewReader(b[0:SizeofCnMsgOp]), NativeEndian(), &msg) 184 | return &msg 185 | } 186 | 187 | func TestCnMsgOpDeserializeSerialize(t *testing.T) { 188 | var orig = make([]byte, SizeofCnMsgOp) 189 | rand.Read(orig) 190 | safemsg := deserializeCnMsgOpSafe(orig) 191 | msg := DeserializeCnMsgOp(orig) 192 | testDeserializeSerialize(t, orig, safemsg, msg) 193 | } 194 | 195 | func TestParseRouteAttrAsMap(t *testing.T) { 196 | attr1 := NewRtAttr(0x1, ZeroTerminated("foo")) 197 | attr2 := NewRtAttr(0x2, ZeroTerminated("bar")) 198 | raw := make([]byte, 0) 199 | raw = append(raw, attr1.Serialize()...) 200 | raw = append(raw, attr2.Serialize()...) 201 | attrs, err := ParseRouteAttrAsMap(raw) 202 | if err != nil { 203 | t.Errorf("failed to parse route attributes %s", err) 204 | } 205 | 206 | attr, ok := attrs[0x1] 207 | if !ok || BytesToString(attr.Value) != "foo" { 208 | t.Error("missing/incorrect \"foo\" attribute") 209 | } 210 | 211 | attr, ok = attrs[0x2] 212 | if !ok || BytesToString(attr.Value) != "bar" { 213 | t.Error("missing/incorrect \"bar\" attribute") 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /nl/nl_unspecified.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package nl 4 | 5 | import "encoding/binary" 6 | 7 | var SupportedNlFamilies = []int{} 8 | 9 | func NativeEndian() binary.ByteOrder { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /nl/parse_attr_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | type Attribute struct { 10 | Type uint16 11 | Value []byte 12 | } 13 | 14 | func ParseAttributes(data []byte) <-chan Attribute { 15 | native := NativeEndian() 16 | result := make(chan Attribute) 17 | 18 | go func() { 19 | i := 0 20 | for i+4 <= len(data) { 21 | length := int(native.Uint16(data[i : i+2])) 22 | attrType := native.Uint16(data[i+2 : i+4]) 23 | 24 | if length < 4 { 25 | log.Printf("attribute 0x%02x has invalid length of %d bytes", attrType, length) 26 | break 27 | } 28 | 29 | if len(data) < i+length { 30 | log.Printf("attribute 0x%02x of length %d is truncated, only %d bytes remaining", attrType, length, len(data)-i) 31 | break 32 | } 33 | 34 | result <- Attribute{ 35 | Type: attrType, 36 | Value: data[i+4 : i+length], 37 | } 38 | i += rtaAlignOf(length) 39 | } 40 | close(result) 41 | }() 42 | 43 | return result 44 | } 45 | 46 | func PrintAttributes(data []byte) { 47 | printAttributes(data, 0) 48 | } 49 | 50 | func printAttributes(data []byte, level int) { 51 | for attr := range ParseAttributes(data) { 52 | for i := 0; i < level; i++ { 53 | print("> ") 54 | } 55 | nested := attr.Type&NLA_F_NESTED != 0 56 | fmt.Printf("type=%d nested=%v len=%v %v\n", attr.Type&NLA_TYPE_MASK, nested, len(attr.Value), attr.Value) 57 | if nested { 58 | printAttributes(attr.Value, level+1) 59 | } 60 | } 61 | } 62 | 63 | // Uint32 returns the uint32 value respecting the NET_BYTEORDER flag 64 | func (attr *Attribute) Uint32() uint32 { 65 | if attr.Type&NLA_F_NET_BYTEORDER != 0 { 66 | return binary.BigEndian.Uint32(attr.Value) 67 | } else { 68 | return NativeEndian().Uint32(attr.Value) 69 | } 70 | } 71 | 72 | // Uint64 returns the uint64 value respecting the NET_BYTEORDER flag 73 | func (attr *Attribute) Uint64() uint64 { 74 | if attr.Type&NLA_F_NET_BYTEORDER != 0 { 75 | return binary.BigEndian.Uint64(attr.Value) 76 | } else { 77 | return NativeEndian().Uint64(attr.Value) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /nl/rdma_link_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | const ( 4 | RDMA_NL_GET_CLIENT_SHIFT = 10 5 | ) 6 | 7 | const ( 8 | RDMA_NL_NLDEV = 5 9 | ) 10 | 11 | const ( 12 | RDMA_NLDEV_CMD_GET = 1 13 | RDMA_NLDEV_CMD_SET = 2 14 | RDMA_NLDEV_CMD_NEWLINK = 3 15 | RDMA_NLDEV_CMD_DELLINK = 4 16 | RDMA_NLDEV_CMD_SYS_GET = 6 17 | RDMA_NLDEV_CMD_SYS_SET = 7 18 | RDMA_NLDEV_CMD_RES_GET = 9 19 | RDMA_NLDEV_CMD_STAT_GET = 17 20 | ) 21 | 22 | const ( 23 | RDMA_NLDEV_ATTR_DEV_INDEX = 1 24 | RDMA_NLDEV_ATTR_DEV_NAME = 2 25 | RDMA_NLDEV_ATTR_PORT_INDEX = 3 26 | RDMA_NLDEV_ATTR_CAP_FLAGS = 4 27 | RDMA_NLDEV_ATTR_FW_VERSION = 5 28 | RDMA_NLDEV_ATTR_NODE_GUID = 6 29 | RDMA_NLDEV_ATTR_SYS_IMAGE_GUID = 7 30 | RDMA_NLDEV_ATTR_SUBNET_PREFIX = 8 31 | RDMA_NLDEV_ATTR_LID = 9 32 | RDMA_NLDEV_ATTR_SM_LID = 10 33 | RDMA_NLDEV_ATTR_LMC = 11 34 | RDMA_NLDEV_ATTR_PORT_STATE = 12 35 | RDMA_NLDEV_ATTR_PORT_PHYS_STATE = 13 36 | RDMA_NLDEV_ATTR_DEV_NODE_TYPE = 14 37 | RDMA_NLDEV_ATTR_RES_SUMMARY = 15 38 | RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY = 16 39 | RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME = 17 40 | RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR = 18 41 | RDMA_NLDEV_ATTR_NDEV_NAME = 51 42 | RDMA_NLDEV_ATTR_LINK_TYPE = 65 43 | RDMA_NLDEV_SYS_ATTR_NETNS_MODE = 66 44 | RDMA_NLDEV_NET_NS_FD = 68 45 | RDMA_NLDEV_ATTR_STAT_HWCOUNTERS = 80 46 | RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY = 81 47 | RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_NAME = 82 48 | RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_VALUE = 83 49 | ) 50 | -------------------------------------------------------------------------------- /nl/route_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | type RtMsg struct { 10 | unix.RtMsg 11 | } 12 | 13 | func NewRtMsg() *RtMsg { 14 | return &RtMsg{ 15 | RtMsg: unix.RtMsg{ 16 | Table: unix.RT_TABLE_MAIN, 17 | Scope: unix.RT_SCOPE_UNIVERSE, 18 | Protocol: unix.RTPROT_BOOT, 19 | Type: unix.RTN_UNICAST, 20 | }, 21 | } 22 | } 23 | 24 | func NewRtDelMsg() *RtMsg { 25 | return &RtMsg{ 26 | RtMsg: unix.RtMsg{ 27 | Table: unix.RT_TABLE_MAIN, 28 | Scope: unix.RT_SCOPE_NOWHERE, 29 | }, 30 | } 31 | } 32 | 33 | func (msg *RtMsg) Len() int { 34 | return unix.SizeofRtMsg 35 | } 36 | 37 | func DeserializeRtMsg(b []byte) *RtMsg { 38 | return (*RtMsg)(unsafe.Pointer(&b[0:unix.SizeofRtMsg][0])) 39 | } 40 | 41 | func (msg *RtMsg) Serialize() []byte { 42 | return (*(*[unix.SizeofRtMsg]byte)(unsafe.Pointer(msg)))[:] 43 | } 44 | 45 | type RtNexthop struct { 46 | unix.RtNexthop 47 | Children []NetlinkRequestData 48 | } 49 | 50 | func DeserializeRtNexthop(b []byte) *RtNexthop { 51 | return &RtNexthop{ 52 | RtNexthop: *((*unix.RtNexthop)(unsafe.Pointer(&b[0:unix.SizeofRtNexthop][0]))), 53 | } 54 | } 55 | 56 | func (msg *RtNexthop) Len() int { 57 | if len(msg.Children) == 0 { 58 | return unix.SizeofRtNexthop 59 | } 60 | 61 | l := 0 62 | for _, child := range msg.Children { 63 | l += rtaAlignOf(child.Len()) 64 | } 65 | l += unix.SizeofRtNexthop 66 | return rtaAlignOf(l) 67 | } 68 | 69 | func (msg *RtNexthop) Serialize() []byte { 70 | length := msg.Len() 71 | msg.RtNexthop.Len = uint16(length) 72 | buf := make([]byte, length) 73 | copy(buf, (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(msg)))[:]) 74 | next := rtaAlignOf(unix.SizeofRtNexthop) 75 | if len(msg.Children) > 0 { 76 | for _, child := range msg.Children { 77 | childBuf := child.Serialize() 78 | copy(buf[next:], childBuf) 79 | next += rtaAlignOf(len(childBuf)) 80 | } 81 | } 82 | return buf 83 | } 84 | 85 | type RtGenMsg struct { 86 | unix.RtGenmsg 87 | } 88 | 89 | func NewRtGenMsg() *RtGenMsg { 90 | return &RtGenMsg{ 91 | RtGenmsg: unix.RtGenmsg{ 92 | Family: unix.AF_UNSPEC, 93 | }, 94 | } 95 | } 96 | 97 | func (msg *RtGenMsg) Len() int { 98 | return rtaAlignOf(unix.SizeofRtGenmsg) 99 | } 100 | 101 | func DeserializeRtGenMsg(b []byte) *RtGenMsg { 102 | return &RtGenMsg{RtGenmsg: unix.RtGenmsg{Family: b[0]}} 103 | } 104 | 105 | func (msg *RtGenMsg) Serialize() []byte { 106 | out := make([]byte, msg.Len()) 107 | out[0] = msg.Family 108 | return out 109 | } 110 | -------------------------------------------------------------------------------- /nl/route_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func (msg *RtMsg) write(b []byte) { 13 | native := NativeEndian() 14 | b[0] = msg.Family 15 | b[1] = msg.Dst_len 16 | b[2] = msg.Src_len 17 | b[3] = msg.Tos 18 | b[4] = msg.Table 19 | b[5] = msg.Protocol 20 | b[6] = msg.Scope 21 | b[7] = msg.Type 22 | native.PutUint32(b[8:12], msg.Flags) 23 | } 24 | 25 | func (msg *RtMsg) serializeSafe() []byte { 26 | len := unix.SizeofRtMsg 27 | b := make([]byte, len) 28 | msg.write(b) 29 | return b 30 | } 31 | 32 | func deserializeRtMsgSafe(b []byte) *RtMsg { 33 | var msg = RtMsg{} 34 | binary.Read(bytes.NewReader(b[0:unix.SizeofRtMsg]), NativeEndian(), &msg) 35 | return &msg 36 | } 37 | 38 | func TestRtMsgDeserializeSerialize(t *testing.T) { 39 | var orig = make([]byte, unix.SizeofRtMsg) 40 | rand.Read(orig) 41 | safemsg := deserializeRtMsgSafe(orig) 42 | msg := DeserializeRtMsg(orig) 43 | testDeserializeSerialize(t, orig, safemsg, msg) 44 | } 45 | 46 | func TestDeserializeRtNexthop(t *testing.T) { 47 | buf := make([]byte, unix.SizeofRtNexthop+64) 48 | native := NativeEndian() 49 | native.PutUint16(buf[0:2], unix.SizeofRtNexthop) 50 | buf[2] = 17 51 | buf[3] = 1 52 | native.PutUint32(buf[4:8], 1234) 53 | 54 | msg := DeserializeRtNexthop(buf) 55 | safemsg := &RtNexthop{ 56 | unix.RtNexthop{ 57 | Len: unix.SizeofRtNexthop, 58 | Flags: 17, 59 | Hops: 1, 60 | Ifindex: 1234, 61 | }, 62 | nil, 63 | } 64 | if msg.Len() != safemsg.Len() || msg.Flags != safemsg.Flags || msg.Hops != safemsg.Hops || msg.Ifindex != safemsg.Ifindex { 65 | t.Fatal("Deserialization failed.\nIn:", buf, "\nOut:", msg, "\n", msg.Serialize(), "\nExpected:", safemsg, "\n", safemsg.Serialize()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /nl/seg6_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | type IPv6SrHdr struct { 10 | nextHdr uint8 11 | hdrLen uint8 12 | routingType uint8 13 | segmentsLeft uint8 14 | firstSegment uint8 15 | flags uint8 16 | reserved uint16 17 | 18 | Segments []net.IP 19 | } 20 | 21 | func (s1 *IPv6SrHdr) Equal(s2 IPv6SrHdr) bool { 22 | if len(s1.Segments) != len(s2.Segments) { 23 | return false 24 | } 25 | for i := range s1.Segments { 26 | if !s1.Segments[i].Equal(s2.Segments[i]) { 27 | return false 28 | } 29 | } 30 | return s1.nextHdr == s2.nextHdr && 31 | s1.hdrLen == s2.hdrLen && 32 | s1.routingType == s2.routingType && 33 | s1.segmentsLeft == s2.segmentsLeft && 34 | s1.firstSegment == s2.firstSegment && 35 | s1.flags == s2.flags 36 | // reserved doesn't need to be identical. 37 | } 38 | 39 | // seg6 encap mode 40 | const ( 41 | SEG6_IPTUN_MODE_INLINE = iota 42 | SEG6_IPTUN_MODE_ENCAP 43 | ) 44 | 45 | // number of nested RTATTR 46 | // from include/uapi/linux/seg6_iptunnel.h 47 | const ( 48 | SEG6_IPTUNNEL_UNSPEC = iota 49 | SEG6_IPTUNNEL_SRH 50 | __SEG6_IPTUNNEL_MAX 51 | ) 52 | const ( 53 | SEG6_IPTUNNEL_MAX = __SEG6_IPTUNNEL_MAX - 1 54 | ) 55 | 56 | func EncodeSEG6Encap(mode int, segments []net.IP) ([]byte, error) { 57 | nsegs := len(segments) // nsegs: number of segments 58 | if nsegs == 0 { 59 | return nil, errors.New("EncodeSEG6Encap: No Segment in srh") 60 | } 61 | b := make([]byte, 12, 12+len(segments)*16) 62 | native := NativeEndian() 63 | native.PutUint32(b, uint32(mode)) 64 | b[4] = 0 // srh.nextHdr (0 when calling netlink) 65 | b[5] = uint8(16 * nsegs >> 3) // srh.hdrLen (in 8-octets unit) 66 | b[6] = IPV6_SRCRT_TYPE_4 // srh.routingType (assigned by IANA) 67 | b[7] = uint8(nsegs - 1) // srh.segmentsLeft 68 | b[8] = uint8(nsegs - 1) // srh.firstSegment 69 | b[9] = 0 // srh.flags (SR6_FLAG1_HMAC for srh_hmac) 70 | // srh.reserved: Defined as "Tag" in draft-ietf-6man-segment-routing-header-07 71 | native.PutUint16(b[10:], 0) // srh.reserved 72 | for _, netIP := range segments { 73 | b = append(b, netIP...) // srh.Segments 74 | } 75 | return b, nil 76 | } 77 | 78 | func DecodeSEG6Encap(buf []byte) (int, []net.IP, error) { 79 | native := NativeEndian() 80 | mode := int(native.Uint32(buf)) 81 | srh := IPv6SrHdr{ 82 | nextHdr: buf[4], 83 | hdrLen: buf[5], 84 | routingType: buf[6], 85 | segmentsLeft: buf[7], 86 | firstSegment: buf[8], 87 | flags: buf[9], 88 | reserved: native.Uint16(buf[10:12]), 89 | } 90 | buf = buf[12:] 91 | if len(buf)%16 != 0 { 92 | err := fmt.Errorf("DecodeSEG6Encap: error parsing Segment List (buf len: %d)", len(buf)) 93 | return mode, nil, err 94 | } 95 | for len(buf) > 0 { 96 | srh.Segments = append(srh.Segments, net.IP(buf[:16])) 97 | buf = buf[16:] 98 | } 99 | return mode, srh.Segments, nil 100 | } 101 | 102 | func DecodeSEG6Srh(buf []byte) ([]net.IP, error) { 103 | native := NativeEndian() 104 | srh := IPv6SrHdr{ 105 | nextHdr: buf[0], 106 | hdrLen: buf[1], 107 | routingType: buf[2], 108 | segmentsLeft: buf[3], 109 | firstSegment: buf[4], 110 | flags: buf[5], 111 | reserved: native.Uint16(buf[6:8]), 112 | } 113 | buf = buf[8:] 114 | if len(buf)%16 != 0 { 115 | err := fmt.Errorf("DecodeSEG6Srh: error parsing Segment List (buf len: %d)", len(buf)) 116 | return nil, err 117 | } 118 | for len(buf) > 0 { 119 | srh.Segments = append(srh.Segments, net.IP(buf[:16])) 120 | buf = buf[16:] 121 | } 122 | return srh.Segments, nil 123 | } 124 | func EncodeSEG6Srh(segments []net.IP) ([]byte, error) { 125 | nsegs := len(segments) // nsegs: number of segments 126 | if nsegs == 0 { 127 | return nil, errors.New("EncodeSEG6Srh: No Segments") 128 | } 129 | b := make([]byte, 8, 8+len(segments)*16) 130 | native := NativeEndian() 131 | b[0] = 0 // srh.nextHdr (0 when calling netlink) 132 | b[1] = uint8(16 * nsegs >> 3) // srh.hdrLen (in 8-octets unit) 133 | b[2] = IPV6_SRCRT_TYPE_4 // srh.routingType (assigned by IANA) 134 | b[3] = uint8(nsegs - 1) // srh.segmentsLeft 135 | b[4] = uint8(nsegs - 1) // srh.firstSegment 136 | b[5] = 0 // srh.flags (SR6_FLAG1_HMAC for srh_hmac) 137 | // srh.reserved: Defined as "Tag" in draft-ietf-6man-segment-routing-header-07 138 | native.PutUint16(b[6:], 0) // srh.reserved 139 | for _, netIP := range segments { 140 | b = append(b, netIP...) // srh.Segments 141 | } 142 | return b, nil 143 | } 144 | 145 | // Helper functions 146 | func SEG6EncapModeString(mode int) string { 147 | switch mode { 148 | case SEG6_IPTUN_MODE_INLINE: 149 | return "inline" 150 | case SEG6_IPTUN_MODE_ENCAP: 151 | return "encap" 152 | } 153 | return "unknown" 154 | } 155 | -------------------------------------------------------------------------------- /nl/seg6local_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import () 4 | 5 | // seg6local parameters 6 | const ( 7 | SEG6_LOCAL_UNSPEC = iota 8 | SEG6_LOCAL_ACTION 9 | SEG6_LOCAL_SRH 10 | SEG6_LOCAL_TABLE 11 | SEG6_LOCAL_NH4 12 | SEG6_LOCAL_NH6 13 | SEG6_LOCAL_IIF 14 | SEG6_LOCAL_OIF 15 | SEG6_LOCAL_BPF 16 | SEG6_LOCAL_VRFTABLE 17 | __SEG6_LOCAL_MAX 18 | ) 19 | const ( 20 | SEG6_LOCAL_MAX = __SEG6_LOCAL_MAX 21 | ) 22 | 23 | // seg6local actions 24 | const ( 25 | SEG6_LOCAL_ACTION_END = iota + 1 // 1 26 | SEG6_LOCAL_ACTION_END_X // 2 27 | SEG6_LOCAL_ACTION_END_T // 3 28 | SEG6_LOCAL_ACTION_END_DX2 // 4 29 | SEG6_LOCAL_ACTION_END_DX6 // 5 30 | SEG6_LOCAL_ACTION_END_DX4 // 6 31 | SEG6_LOCAL_ACTION_END_DT6 // 7 32 | SEG6_LOCAL_ACTION_END_DT4 // 8 33 | SEG6_LOCAL_ACTION_END_B6 // 9 34 | SEG6_LOCAL_ACTION_END_B6_ENCAPS // 10 35 | SEG6_LOCAL_ACTION_END_BM // 11 36 | SEG6_LOCAL_ACTION_END_S // 12 37 | SEG6_LOCAL_ACTION_END_AS // 13 38 | SEG6_LOCAL_ACTION_END_AM // 14 39 | SEG6_LOCAL_ACTION_END_BPF // 15 40 | __SEG6_LOCAL_ACTION_MAX 41 | ) 42 | const ( 43 | SEG6_LOCAL_ACTION_MAX = __SEG6_LOCAL_ACTION_MAX - 1 44 | ) 45 | 46 | // Helper functions 47 | func SEG6LocalActionString(action int) string { 48 | switch action { 49 | case SEG6_LOCAL_ACTION_END: 50 | return "End" 51 | case SEG6_LOCAL_ACTION_END_X: 52 | return "End.X" 53 | case SEG6_LOCAL_ACTION_END_T: 54 | return "End.T" 55 | case SEG6_LOCAL_ACTION_END_DX2: 56 | return "End.DX2" 57 | case SEG6_LOCAL_ACTION_END_DX6: 58 | return "End.DX6" 59 | case SEG6_LOCAL_ACTION_END_DX4: 60 | return "End.DX4" 61 | case SEG6_LOCAL_ACTION_END_DT6: 62 | return "End.DT6" 63 | case SEG6_LOCAL_ACTION_END_DT4: 64 | return "End.DT4" 65 | case SEG6_LOCAL_ACTION_END_B6: 66 | return "End.B6" 67 | case SEG6_LOCAL_ACTION_END_B6_ENCAPS: 68 | return "End.B6.Encaps" 69 | case SEG6_LOCAL_ACTION_END_BM: 70 | return "End.BM" 71 | case SEG6_LOCAL_ACTION_END_S: 72 | return "End.S" 73 | case SEG6_LOCAL_ACTION_END_AS: 74 | return "End.AS" 75 | case SEG6_LOCAL_ACTION_END_AM: 76 | return "End.AM" 77 | case SEG6_LOCAL_ACTION_END_BPF: 78 | return "End.BPF" 79 | } 80 | return "unknown" 81 | } 82 | -------------------------------------------------------------------------------- /nl/syscall.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | // syscall package lack of rule attributes type. 4 | // Thus there are defined below 5 | const ( 6 | FRA_UNSPEC = iota 7 | FRA_DST /* destination address */ 8 | FRA_SRC /* source address */ 9 | FRA_IIFNAME /* interface name */ 10 | FRA_GOTO /* target to jump to (FR_ACT_GOTO) */ 11 | FRA_UNUSED2 12 | FRA_PRIORITY /* priority/preference */ 13 | FRA_UNUSED3 14 | FRA_UNUSED4 15 | FRA_UNUSED5 16 | FRA_FWMARK /* mark */ 17 | FRA_FLOW /* flow/class id */ 18 | FRA_TUN_ID 19 | FRA_SUPPRESS_IFGROUP 20 | FRA_SUPPRESS_PREFIXLEN 21 | FRA_TABLE /* Extended table id */ 22 | FRA_FWMASK /* mask for netfilter mark */ 23 | FRA_OIFNAME 24 | FRA_PAD 25 | FRA_L3MDEV /* iif or oif is l3mdev goto its table */ 26 | FRA_UID_RANGE /* UID range */ 27 | FRA_PROTOCOL /* Originator of the rule */ 28 | FRA_IP_PROTO /* ip proto */ 29 | FRA_SPORT_RANGE /* sport */ 30 | FRA_DPORT_RANGE /* dport */ 31 | ) 32 | 33 | // ip rule netlink request types 34 | const ( 35 | FR_ACT_UNSPEC = iota 36 | FR_ACT_TO_TBL /* Pass to fixed table */ 37 | FR_ACT_GOTO /* Jump to another rule */ 38 | FR_ACT_NOP /* No operation */ 39 | FR_ACT_RES3 40 | FR_ACT_RES4 41 | FR_ACT_BLACKHOLE /* Drop without notification */ 42 | FR_ACT_UNREACHABLE /* Drop with ENETUNREACH */ 43 | FR_ACT_PROHIBIT /* Drop with EACCES */ 44 | ) 45 | 46 | // socket diags related 47 | const ( 48 | SOCK_DIAG_BY_FAMILY = 20 /* linux.sock_diag.h */ 49 | SOCK_DESTROY = 21 50 | TCPDIAG_NOCOOKIE = 0xFFFFFFFF /* TCPDIAG_NOCOOKIE in net/ipv4/tcp_diag.h*/ 51 | ) 52 | 53 | // RTA_ENCAP subtype 54 | const ( 55 | MPLS_IPTUNNEL_UNSPEC = iota 56 | MPLS_IPTUNNEL_DST 57 | ) 58 | 59 | // light weight tunnel encap types 60 | const ( 61 | LWTUNNEL_ENCAP_NONE = iota 62 | LWTUNNEL_ENCAP_MPLS 63 | LWTUNNEL_ENCAP_IP 64 | LWTUNNEL_ENCAP_ILA 65 | LWTUNNEL_ENCAP_IP6 66 | LWTUNNEL_ENCAP_SEG6 67 | LWTUNNEL_ENCAP_BPF 68 | LWTUNNEL_ENCAP_SEG6_LOCAL 69 | ) 70 | 71 | // routing header types 72 | const ( 73 | IPV6_SRCRT_STRICT = 0x01 // Deprecated; will be removed 74 | IPV6_SRCRT_TYPE_0 = 0 // Deprecated; will be removed 75 | IPV6_SRCRT_TYPE_2 = 2 // IPv6 type 2 Routing Header 76 | IPV6_SRCRT_TYPE_4 = 4 // Segment Routing with IPv6 77 | ) 78 | -------------------------------------------------------------------------------- /nl/tc_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | /* TcMsg */ 11 | func (msg *TcMsg) write(b []byte) { 12 | native := NativeEndian() 13 | b[0] = msg.Family 14 | copy(b[1:4], msg.Pad[:]) 15 | native.PutUint32(b[4:8], uint32(msg.Ifindex)) 16 | native.PutUint32(b[8:12], msg.Handle) 17 | native.PutUint32(b[12:16], msg.Parent) 18 | native.PutUint32(b[16:20], msg.Info) 19 | } 20 | 21 | func (msg *TcMsg) serializeSafe() []byte { 22 | length := SizeofTcMsg 23 | b := make([]byte, length) 24 | msg.write(b) 25 | return b 26 | } 27 | 28 | func deserializeTcMsgSafe(b []byte) *TcMsg { 29 | var msg = TcMsg{} 30 | binary.Read(bytes.NewReader(b[0:SizeofTcMsg]), NativeEndian(), &msg) 31 | return &msg 32 | } 33 | 34 | func TestTcMsgDeserializeSerialize(t *testing.T) { 35 | var orig = make([]byte, SizeofTcMsg) 36 | rand.Read(orig) 37 | safemsg := deserializeTcMsgSafe(orig) 38 | msg := DeserializeTcMsg(orig) 39 | testDeserializeSerialize(t, orig, safemsg, msg) 40 | } 41 | 42 | /* TcActionMsg */ 43 | func (msg *TcActionMsg) write(b []byte) { 44 | b[0] = msg.Family 45 | copy(b[1:4], msg.Pad[:]) 46 | } 47 | 48 | func (msg *TcActionMsg) serializeSafe() []byte { 49 | length := SizeofTcActionMsg 50 | b := make([]byte, length) 51 | msg.write(b) 52 | return b 53 | } 54 | 55 | func deserializeTcActionMsgSafe(b []byte) *TcActionMsg { 56 | var msg = TcActionMsg{} 57 | binary.Read(bytes.NewReader(b[0:SizeofTcActionMsg]), NativeEndian(), &msg) 58 | return &msg 59 | } 60 | 61 | func TestTcActionMsgDeserializeSerialize(t *testing.T) { 62 | var orig = make([]byte, SizeofTcActionMsg) 63 | rand.Read(orig) 64 | safemsg := deserializeTcActionMsgSafe(orig) 65 | msg := DeserializeTcActionMsg(orig) 66 | testDeserializeSerialize(t, orig, safemsg, msg) 67 | } 68 | 69 | /* TcRateSpec */ 70 | func (msg *TcRateSpec) write(b []byte) { 71 | native := NativeEndian() 72 | b[0] = msg.CellLog 73 | b[1] = msg.Linklayer 74 | native.PutUint16(b[2:4], msg.Overhead) 75 | native.PutUint16(b[4:6], uint16(msg.CellAlign)) 76 | native.PutUint16(b[6:8], msg.Mpu) 77 | native.PutUint32(b[8:12], msg.Rate) 78 | } 79 | 80 | func (msg *TcRateSpec) serializeSafe() []byte { 81 | length := SizeofTcRateSpec 82 | b := make([]byte, length) 83 | msg.write(b) 84 | return b 85 | } 86 | 87 | func deserializeTcRateSpecSafe(b []byte) *TcRateSpec { 88 | var msg = TcRateSpec{} 89 | binary.Read(bytes.NewReader(b[0:SizeofTcRateSpec]), NativeEndian(), &msg) 90 | return &msg 91 | } 92 | 93 | func TestTcRateSpecDeserializeSerialize(t *testing.T) { 94 | var orig = make([]byte, SizeofTcRateSpec) 95 | rand.Read(orig) 96 | safemsg := deserializeTcRateSpecSafe(orig) 97 | msg := DeserializeTcRateSpec(orig) 98 | testDeserializeSerialize(t, orig, safemsg, msg) 99 | } 100 | 101 | /* TcTbfQopt */ 102 | func (msg *TcTbfQopt) write(b []byte) { 103 | native := NativeEndian() 104 | msg.Rate.write(b[0:SizeofTcRateSpec]) 105 | start := SizeofTcRateSpec 106 | msg.Peakrate.write(b[start : start+SizeofTcRateSpec]) 107 | start += SizeofTcRateSpec 108 | native.PutUint32(b[start:start+4], msg.Limit) 109 | start += 4 110 | native.PutUint32(b[start:start+4], msg.Buffer) 111 | start += 4 112 | native.PutUint32(b[start:start+4], msg.Mtu) 113 | } 114 | 115 | func (msg *TcTbfQopt) serializeSafe() []byte { 116 | length := SizeofTcTbfQopt 117 | b := make([]byte, length) 118 | msg.write(b) 119 | return b 120 | } 121 | 122 | func deserializeTcTbfQoptSafe(b []byte) *TcTbfQopt { 123 | var msg = TcTbfQopt{} 124 | binary.Read(bytes.NewReader(b[0:SizeofTcTbfQopt]), NativeEndian(), &msg) 125 | return &msg 126 | } 127 | 128 | func TestTcTbfQoptDeserializeSerialize(t *testing.T) { 129 | var orig = make([]byte, SizeofTcTbfQopt) 130 | rand.Read(orig) 131 | safemsg := deserializeTcTbfQoptSafe(orig) 132 | msg := DeserializeTcTbfQopt(orig) 133 | testDeserializeSerialize(t, orig, safemsg, msg) 134 | } 135 | 136 | /* TcHtbCopt */ 137 | func (msg *TcHtbCopt) write(b []byte) { 138 | native := NativeEndian() 139 | msg.Rate.write(b[0:SizeofTcRateSpec]) 140 | start := SizeofTcRateSpec 141 | msg.Ceil.write(b[start : start+SizeofTcRateSpec]) 142 | start += SizeofTcRateSpec 143 | native.PutUint32(b[start:start+4], msg.Buffer) 144 | start += 4 145 | native.PutUint32(b[start:start+4], msg.Cbuffer) 146 | start += 4 147 | native.PutUint32(b[start:start+4], msg.Quantum) 148 | start += 4 149 | native.PutUint32(b[start:start+4], msg.Level) 150 | start += 4 151 | native.PutUint32(b[start:start+4], msg.Prio) 152 | } 153 | 154 | func (msg *TcHtbCopt) serializeSafe() []byte { 155 | length := SizeofTcHtbCopt 156 | b := make([]byte, length) 157 | msg.write(b) 158 | return b 159 | } 160 | 161 | func deserializeTcHtbCoptSafe(b []byte) *TcHtbCopt { 162 | var msg = TcHtbCopt{} 163 | binary.Read(bytes.NewReader(b[0:SizeofTcHtbCopt]), NativeEndian(), &msg) 164 | return &msg 165 | } 166 | 167 | func TestTcHtbCoptDeserializeSerialize(t *testing.T) { 168 | var orig = make([]byte, SizeofTcHtbCopt) 169 | rand.Read(orig) 170 | safemsg := deserializeTcHtbCoptSafe(orig) 171 | msg := DeserializeTcHtbCopt(orig) 172 | testDeserializeSerialize(t, orig, safemsg, msg) 173 | } 174 | -------------------------------------------------------------------------------- /nl/vdpa_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | const ( 4 | VDPA_GENL_NAME = "vdpa" 5 | VDPA_GENL_VERSION = 0x1 6 | ) 7 | 8 | const ( 9 | VDPA_CMD_UNSPEC = iota 10 | VDPA_CMD_MGMTDEV_NEW 11 | VDPA_CMD_MGMTDEV_GET /* can dump */ 12 | VDPA_CMD_DEV_NEW 13 | VDPA_CMD_DEV_DEL 14 | VDPA_CMD_DEV_GET /* can dump */ 15 | VDPA_CMD_DEV_CONFIG_GET /* can dump */ 16 | VDPA_CMD_DEV_VSTATS_GET 17 | ) 18 | 19 | const ( 20 | VDPA_ATTR_UNSPEC = iota 21 | VDPA_ATTR_MGMTDEV_BUS_NAME 22 | VDPA_ATTR_MGMTDEV_DEV_NAME 23 | VDPA_ATTR_MGMTDEV_SUPPORTED_CLASSES 24 | VDPA_ATTR_DEV_NAME 25 | VDPA_ATTR_DEV_ID 26 | VDPA_ATTR_DEV_VENDOR_ID 27 | VDPA_ATTR_DEV_MAX_VQS 28 | VDPA_ATTR_DEV_MAX_VQ_SIZE 29 | VDPA_ATTR_DEV_MIN_VQ_SIZE 30 | VDPA_ATTR_DEV_NET_CFG_MACADDR 31 | VDPA_ATTR_DEV_NET_STATUS 32 | VDPA_ATTR_DEV_NET_CFG_MAX_VQP 33 | VDPA_ATTR_DEV_NET_CFG_MTU 34 | VDPA_ATTR_DEV_NEGOTIATED_FEATURES 35 | VDPA_ATTR_DEV_MGMTDEV_MAX_VQS 36 | VDPA_ATTR_DEV_SUPPORTED_FEATURES 37 | VDPA_ATTR_DEV_QUEUE_INDEX 38 | VDPA_ATTR_DEV_VENDOR_ATTR_NAME 39 | VDPA_ATTR_DEV_VENDOR_ATTR_VALUE 40 | VDPA_ATTR_DEV_FEATURES 41 | ) 42 | -------------------------------------------------------------------------------- /nl/xfrm_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | func (msg *XfrmAddress) write(b []byte) { 11 | copy(b[0:SizeofXfrmAddress], msg[:]) 12 | } 13 | 14 | func (msg *XfrmAddress) serializeSafe() []byte { 15 | b := make([]byte, SizeofXfrmAddress) 16 | msg.write(b) 17 | return b 18 | } 19 | 20 | func deserializeXfrmAddressSafe(b []byte) *XfrmAddress { 21 | var msg = XfrmAddress{} 22 | binary.Read(bytes.NewReader(b[0:SizeofXfrmAddress]), NativeEndian(), &msg) 23 | return &msg 24 | } 25 | 26 | func TestXfrmAddressDeserializeSerialize(t *testing.T) { 27 | var orig = make([]byte, SizeofXfrmAddress) 28 | rand.Read(orig) 29 | safemsg := deserializeXfrmAddressSafe(orig) 30 | msg := DeserializeXfrmAddress(orig) 31 | testDeserializeSerialize(t, orig, safemsg, msg) 32 | } 33 | 34 | func (msg *XfrmSelector) write(b []byte) { 35 | const AddrEnd = SizeofXfrmAddress * 2 36 | native := NativeEndian() 37 | msg.Daddr.write(b[0:SizeofXfrmAddress]) 38 | msg.Saddr.write(b[SizeofXfrmAddress:AddrEnd]) 39 | native.PutUint16(b[AddrEnd:AddrEnd+2], msg.Dport) 40 | native.PutUint16(b[AddrEnd+2:AddrEnd+4], msg.DportMask) 41 | native.PutUint16(b[AddrEnd+4:AddrEnd+6], msg.Sport) 42 | native.PutUint16(b[AddrEnd+6:AddrEnd+8], msg.SportMask) 43 | native.PutUint16(b[AddrEnd+8:AddrEnd+10], msg.Family) 44 | b[AddrEnd+10] = msg.PrefixlenD 45 | b[AddrEnd+11] = msg.PrefixlenS 46 | b[AddrEnd+12] = msg.Proto 47 | copy(b[AddrEnd+13:AddrEnd+16], msg.Pad[:]) 48 | native.PutUint32(b[AddrEnd+16:AddrEnd+20], uint32(msg.Ifindex)) 49 | native.PutUint32(b[AddrEnd+20:AddrEnd+24], msg.User) 50 | } 51 | 52 | func (msg *XfrmSelector) serializeSafe() []byte { 53 | length := SizeofXfrmSelector 54 | b := make([]byte, length) 55 | msg.write(b) 56 | return b 57 | } 58 | 59 | func deserializeXfrmSelectorSafe(b []byte) *XfrmSelector { 60 | var msg = XfrmSelector{} 61 | binary.Read(bytes.NewReader(b[0:SizeofXfrmSelector]), NativeEndian(), &msg) 62 | return &msg 63 | } 64 | 65 | func TestXfrmSelectorDeserializeSerialize(t *testing.T) { 66 | var orig = make([]byte, SizeofXfrmSelector) 67 | rand.Read(orig) 68 | safemsg := deserializeXfrmSelectorSafe(orig) 69 | msg := DeserializeXfrmSelector(orig) 70 | testDeserializeSerialize(t, orig, safemsg, msg) 71 | } 72 | 73 | func (msg *XfrmLifetimeCfg) write(b []byte) { 74 | native := NativeEndian() 75 | native.PutUint64(b[0:8], msg.SoftByteLimit) 76 | native.PutUint64(b[8:16], msg.HardByteLimit) 77 | native.PutUint64(b[16:24], msg.SoftPacketLimit) 78 | native.PutUint64(b[24:32], msg.HardPacketLimit) 79 | native.PutUint64(b[32:40], msg.SoftAddExpiresSeconds) 80 | native.PutUint64(b[40:48], msg.HardAddExpiresSeconds) 81 | native.PutUint64(b[48:56], msg.SoftUseExpiresSeconds) 82 | native.PutUint64(b[56:64], msg.HardUseExpiresSeconds) 83 | } 84 | 85 | func (msg *XfrmLifetimeCfg) serializeSafe() []byte { 86 | length := SizeofXfrmLifetimeCfg 87 | b := make([]byte, length) 88 | msg.write(b) 89 | return b 90 | } 91 | 92 | func deserializeXfrmLifetimeCfgSafe(b []byte) *XfrmLifetimeCfg { 93 | var msg = XfrmLifetimeCfg{} 94 | binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCfg]), NativeEndian(), &msg) 95 | return &msg 96 | } 97 | 98 | func TestXfrmLifetimeCfgDeserializeSerialize(t *testing.T) { 99 | var orig = make([]byte, SizeofXfrmLifetimeCfg) 100 | rand.Read(orig) 101 | safemsg := deserializeXfrmLifetimeCfgSafe(orig) 102 | msg := DeserializeXfrmLifetimeCfg(orig) 103 | testDeserializeSerialize(t, orig, safemsg, msg) 104 | } 105 | 106 | func (msg *XfrmLifetimeCur) write(b []byte) { 107 | native := NativeEndian() 108 | native.PutUint64(b[0:8], msg.Bytes) 109 | native.PutUint64(b[8:16], msg.Packets) 110 | native.PutUint64(b[16:24], msg.AddTime) 111 | native.PutUint64(b[24:32], msg.UseTime) 112 | } 113 | 114 | func (msg *XfrmLifetimeCur) serializeSafe() []byte { 115 | length := SizeofXfrmLifetimeCur 116 | b := make([]byte, length) 117 | msg.write(b) 118 | return b 119 | } 120 | 121 | func deserializeXfrmLifetimeCurSafe(b []byte) *XfrmLifetimeCur { 122 | var msg = XfrmLifetimeCur{} 123 | binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCur]), NativeEndian(), &msg) 124 | return &msg 125 | } 126 | 127 | func TestXfrmLifetimeCurDeserializeSerialize(t *testing.T) { 128 | var orig = make([]byte, SizeofXfrmLifetimeCur) 129 | rand.Read(orig) 130 | safemsg := deserializeXfrmLifetimeCurSafe(orig) 131 | msg := DeserializeXfrmLifetimeCur(orig) 132 | testDeserializeSerialize(t, orig, safemsg, msg) 133 | } 134 | 135 | func (msg *XfrmId) write(b []byte) { 136 | native := NativeEndian() 137 | msg.Daddr.write(b[0:SizeofXfrmAddress]) 138 | native.PutUint32(b[SizeofXfrmAddress:SizeofXfrmAddress+4], msg.Spi) 139 | b[SizeofXfrmAddress+4] = msg.Proto 140 | copy(b[SizeofXfrmAddress+5:SizeofXfrmAddress+8], msg.Pad[:]) 141 | } 142 | 143 | func (msg *XfrmId) serializeSafe() []byte { 144 | b := make([]byte, SizeofXfrmId) 145 | msg.write(b) 146 | return b 147 | } 148 | 149 | func deserializeXfrmIdSafe(b []byte) *XfrmId { 150 | var msg = XfrmId{} 151 | binary.Read(bytes.NewReader(b[0:SizeofXfrmId]), NativeEndian(), &msg) 152 | return &msg 153 | } 154 | 155 | func TestXfrmIdDeserializeSerialize(t *testing.T) { 156 | var orig = make([]byte, SizeofXfrmId) 157 | rand.Read(orig) 158 | safemsg := deserializeXfrmIdSafe(orig) 159 | msg := DeserializeXfrmId(orig) 160 | testDeserializeSerialize(t, orig, safemsg, msg) 161 | } 162 | -------------------------------------------------------------------------------- /nl/xfrm_monitor_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | const ( 8 | SizeofXfrmUserExpire = 0xe8 9 | ) 10 | 11 | // struct xfrm_user_expire { 12 | // struct xfrm_usersa_info state; 13 | // __u8 hard; 14 | // }; 15 | 16 | type XfrmUserExpire struct { 17 | XfrmUsersaInfo XfrmUsersaInfo 18 | Hard uint8 19 | Pad [7]byte 20 | } 21 | 22 | func (msg *XfrmUserExpire) Len() int { 23 | return SizeofXfrmUserExpire 24 | } 25 | 26 | func DeserializeXfrmUserExpire(b []byte) *XfrmUserExpire { 27 | return (*XfrmUserExpire)(unsafe.Pointer(&b[0:SizeofXfrmUserExpire][0])) 28 | } 29 | 30 | func (msg *XfrmUserExpire) Serialize() []byte { 31 | return (*(*[SizeofXfrmUserExpire]byte)(unsafe.Pointer(msg)))[:] 32 | } 33 | -------------------------------------------------------------------------------- /nl/xfrm_monitor_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | func (msg *XfrmUserExpire) write(b []byte) { 11 | msg.XfrmUsersaInfo.write(b[0:SizeofXfrmUsersaInfo]) 12 | b[SizeofXfrmUsersaInfo] = msg.Hard 13 | copy(b[SizeofXfrmUsersaInfo+1:SizeofXfrmUserExpire], msg.Pad[:]) 14 | } 15 | 16 | func (msg *XfrmUserExpire) serializeSafe() []byte { 17 | b := make([]byte, SizeofXfrmUserExpire) 18 | msg.write(b) 19 | return b 20 | } 21 | 22 | func deserializeXfrmUserExpireSafe(b []byte) *XfrmUserExpire { 23 | var msg = XfrmUserExpire{} 24 | binary.Read(bytes.NewReader(b[0:SizeofXfrmUserExpire]), NativeEndian(), &msg) 25 | return &msg 26 | } 27 | 28 | func TestXfrmUserExpireDeserializeSerialize(t *testing.T) { 29 | var orig = make([]byte, SizeofXfrmUserExpire) 30 | rand.Read(orig) 31 | safemsg := deserializeXfrmUserExpireSafe(orig) 32 | msg := DeserializeXfrmUserExpire(orig) 33 | testDeserializeSerialize(t, orig, safemsg, msg) 34 | } 35 | -------------------------------------------------------------------------------- /nl/xfrm_policy_linux.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | const ( 8 | SizeofXfrmUserpolicyId = 0x40 9 | SizeofXfrmUserpolicyInfo = 0xa8 10 | SizeofXfrmUserTmpl = 0x40 11 | ) 12 | 13 | // struct xfrm_userpolicy_id { 14 | // struct xfrm_selector sel; 15 | // __u32 index; 16 | // __u8 dir; 17 | // }; 18 | // 19 | 20 | type XfrmUserpolicyId struct { 21 | Sel XfrmSelector 22 | Index uint32 23 | Dir uint8 24 | Pad [3]byte 25 | } 26 | 27 | func (msg *XfrmUserpolicyId) Len() int { 28 | return SizeofXfrmUserpolicyId 29 | } 30 | 31 | func DeserializeXfrmUserpolicyId(b []byte) *XfrmUserpolicyId { 32 | return (*XfrmUserpolicyId)(unsafe.Pointer(&b[0:SizeofXfrmUserpolicyId][0])) 33 | } 34 | 35 | func (msg *XfrmUserpolicyId) Serialize() []byte { 36 | return (*(*[SizeofXfrmUserpolicyId]byte)(unsafe.Pointer(msg)))[:] 37 | } 38 | 39 | // struct xfrm_userpolicy_info { 40 | // struct xfrm_selector sel; 41 | // struct xfrm_lifetime_cfg lft; 42 | // struct xfrm_lifetime_cur curlft; 43 | // __u32 priority; 44 | // __u32 index; 45 | // __u8 dir; 46 | // __u8 action; 47 | // #define XFRM_POLICY_ALLOW 0 48 | // #define XFRM_POLICY_BLOCK 1 49 | // __u8 flags; 50 | // #define XFRM_POLICY_LOCALOK 1 /* Allow user to override global policy */ 51 | // /* Automatically expand selector to include matching ICMP payloads. */ 52 | // #define XFRM_POLICY_ICMP 2 53 | // __u8 share; 54 | // }; 55 | 56 | type XfrmUserpolicyInfo struct { 57 | Sel XfrmSelector 58 | Lft XfrmLifetimeCfg 59 | Curlft XfrmLifetimeCur 60 | Priority uint32 61 | Index uint32 62 | Dir uint8 63 | Action uint8 64 | Flags uint8 65 | Share uint8 66 | Pad [4]byte 67 | } 68 | 69 | func (msg *XfrmUserpolicyInfo) Len() int { 70 | return SizeofXfrmUserpolicyInfo 71 | } 72 | 73 | func DeserializeXfrmUserpolicyInfo(b []byte) *XfrmUserpolicyInfo { 74 | return (*XfrmUserpolicyInfo)(unsafe.Pointer(&b[0:SizeofXfrmUserpolicyInfo][0])) 75 | } 76 | 77 | func (msg *XfrmUserpolicyInfo) Serialize() []byte { 78 | return (*(*[SizeofXfrmUserpolicyInfo]byte)(unsafe.Pointer(msg)))[:] 79 | } 80 | 81 | // struct xfrm_user_tmpl { 82 | // struct xfrm_id id; 83 | // __u16 family; 84 | // xfrm_address_t saddr; 85 | // __u32 reqid; 86 | // __u8 mode; 87 | // __u8 share; 88 | // __u8 optional; 89 | // __u32 aalgos; 90 | // __u32 ealgos; 91 | // __u32 calgos; 92 | // } 93 | 94 | type XfrmUserTmpl struct { 95 | XfrmId XfrmId 96 | Family uint16 97 | Pad1 [2]byte 98 | Saddr XfrmAddress 99 | Reqid uint32 100 | Mode uint8 101 | Share uint8 102 | Optional uint8 103 | Pad2 byte 104 | Aalgos uint32 105 | Ealgos uint32 106 | Calgos uint32 107 | } 108 | 109 | func (msg *XfrmUserTmpl) Len() int { 110 | return SizeofXfrmUserTmpl 111 | } 112 | 113 | func DeserializeXfrmUserTmpl(b []byte) *XfrmUserTmpl { 114 | return (*XfrmUserTmpl)(unsafe.Pointer(&b[0:SizeofXfrmUserTmpl][0])) 115 | } 116 | 117 | func (msg *XfrmUserTmpl) Serialize() []byte { 118 | return (*(*[SizeofXfrmUserTmpl]byte)(unsafe.Pointer(msg)))[:] 119 | } 120 | -------------------------------------------------------------------------------- /nl/xfrm_policy_linux_test.go: -------------------------------------------------------------------------------- 1 | package nl 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "testing" 8 | ) 9 | 10 | func (msg *XfrmUserpolicyId) write(b []byte) { 11 | native := NativeEndian() 12 | msg.Sel.write(b[0:SizeofXfrmSelector]) 13 | native.PutUint32(b[SizeofXfrmSelector:SizeofXfrmSelector+4], msg.Index) 14 | b[SizeofXfrmSelector+4] = msg.Dir 15 | copy(b[SizeofXfrmSelector+5:SizeofXfrmSelector+8], msg.Pad[:]) 16 | } 17 | 18 | func (msg *XfrmUserpolicyId) serializeSafe() []byte { 19 | b := make([]byte, SizeofXfrmUserpolicyId) 20 | msg.write(b) 21 | return b 22 | } 23 | 24 | func deserializeXfrmUserpolicyIdSafe(b []byte) *XfrmUserpolicyId { 25 | var msg = XfrmUserpolicyId{} 26 | binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyId]), NativeEndian(), &msg) 27 | return &msg 28 | } 29 | 30 | func TestXfrmUserpolicyIdDeserializeSerialize(t *testing.T) { 31 | var orig = make([]byte, SizeofXfrmUserpolicyId) 32 | rand.Read(orig) 33 | safemsg := deserializeXfrmUserpolicyIdSafe(orig) 34 | msg := DeserializeXfrmUserpolicyId(orig) 35 | testDeserializeSerialize(t, orig, safemsg, msg) 36 | } 37 | 38 | func (msg *XfrmUserpolicyInfo) write(b []byte) { 39 | const CfgEnd = SizeofXfrmSelector + SizeofXfrmLifetimeCfg 40 | const CurEnd = CfgEnd + SizeofXfrmLifetimeCur 41 | native := NativeEndian() 42 | msg.Sel.write(b[0:SizeofXfrmSelector]) 43 | msg.Lft.write(b[SizeofXfrmSelector:CfgEnd]) 44 | msg.Curlft.write(b[CfgEnd:CurEnd]) 45 | native.PutUint32(b[CurEnd:CurEnd+4], msg.Priority) 46 | native.PutUint32(b[CurEnd+4:CurEnd+8], msg.Index) 47 | b[CurEnd+8] = msg.Dir 48 | b[CurEnd+9] = msg.Action 49 | b[CurEnd+10] = msg.Flags 50 | b[CurEnd+11] = msg.Share 51 | copy(b[CurEnd+12:CurEnd+16], msg.Pad[:]) 52 | } 53 | 54 | func (msg *XfrmUserpolicyInfo) serializeSafe() []byte { 55 | b := make([]byte, SizeofXfrmUserpolicyInfo) 56 | msg.write(b) 57 | return b 58 | } 59 | 60 | func deserializeXfrmUserpolicyInfoSafe(b []byte) *XfrmUserpolicyInfo { 61 | var msg = XfrmUserpolicyInfo{} 62 | binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyInfo]), NativeEndian(), &msg) 63 | return &msg 64 | } 65 | 66 | func TestXfrmUserpolicyInfoDeserializeSerialize(t *testing.T) { 67 | var orig = make([]byte, SizeofXfrmUserpolicyInfo) 68 | rand.Read(orig) 69 | safemsg := deserializeXfrmUserpolicyInfoSafe(orig) 70 | msg := DeserializeXfrmUserpolicyInfo(orig) 71 | testDeserializeSerialize(t, orig, safemsg, msg) 72 | } 73 | 74 | func (msg *XfrmUserTmpl) write(b []byte) { 75 | const AddrEnd = SizeofXfrmId + 4 + SizeofXfrmAddress 76 | native := NativeEndian() 77 | msg.XfrmId.write(b[0:SizeofXfrmId]) 78 | native.PutUint16(b[SizeofXfrmId:SizeofXfrmId+2], msg.Family) 79 | copy(b[SizeofXfrmId+2:SizeofXfrmId+4], msg.Pad1[:]) 80 | msg.Saddr.write(b[SizeofXfrmId+4 : AddrEnd]) 81 | native.PutUint32(b[AddrEnd:AddrEnd+4], msg.Reqid) 82 | b[AddrEnd+4] = msg.Mode 83 | b[AddrEnd+5] = msg.Share 84 | b[AddrEnd+6] = msg.Optional 85 | b[AddrEnd+7] = msg.Pad2 86 | native.PutUint32(b[AddrEnd+8:AddrEnd+12], msg.Aalgos) 87 | native.PutUint32(b[AddrEnd+12:AddrEnd+16], msg.Ealgos) 88 | native.PutUint32(b[AddrEnd+16:AddrEnd+20], msg.Calgos) 89 | } 90 | 91 | func (msg *XfrmUserTmpl) serializeSafe() []byte { 92 | b := make([]byte, SizeofXfrmUserTmpl) 93 | msg.write(b) 94 | return b 95 | } 96 | 97 | func deserializeXfrmUserTmplSafe(b []byte) *XfrmUserTmpl { 98 | var msg = XfrmUserTmpl{} 99 | binary.Read(bytes.NewReader(b[0:SizeofXfrmUserTmpl]), NativeEndian(), &msg) 100 | return &msg 101 | } 102 | 103 | func TestXfrmUserTmplDeserializeSerialize(t *testing.T) { 104 | var orig = make([]byte, SizeofXfrmUserTmpl) 105 | rand.Read(orig) 106 | safemsg := deserializeXfrmUserTmplSafe(orig) 107 | msg := DeserializeXfrmUserTmpl(orig) 108 | testDeserializeSerialize(t, orig, safemsg, msg) 109 | } 110 | -------------------------------------------------------------------------------- /order.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/vishvananda/netlink/nl" 7 | ) 8 | 9 | var ( 10 | native = nl.NativeEndian() 11 | networkOrder = binary.BigEndian 12 | ) 13 | 14 | func htonl(val uint32) []byte { 15 | bytes := make([]byte, 4) 16 | binary.BigEndian.PutUint32(bytes, val) 17 | return bytes 18 | } 19 | 20 | func htons(val uint16) []byte { 21 | bytes := make([]byte, 2) 22 | binary.BigEndian.PutUint16(bytes, val) 23 | return bytes 24 | } 25 | 26 | func ntohl(buf []byte) uint32 { 27 | return binary.BigEndian.Uint32(buf) 28 | } 29 | 30 | func ntohs(buf []byte) uint16 { 31 | return binary.BigEndian.Uint16(buf) 32 | } 33 | -------------------------------------------------------------------------------- /proc_event_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "os" 8 | "syscall" 9 | 10 | "github.com/vishvananda/netlink/nl" 11 | "github.com/vishvananda/netns" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | const CN_IDX_PROC = 0x1 16 | 17 | const ( 18 | PROC_EVENT_NONE = 0x00000000 19 | PROC_EVENT_FORK = 0x00000001 20 | PROC_EVENT_EXEC = 0x00000002 21 | PROC_EVENT_UID = 0x00000004 22 | PROC_EVENT_GID = 0x00000040 23 | PROC_EVENT_SID = 0x00000080 24 | PROC_EVENT_PTRACE = 0x00000100 25 | PROC_EVENT_COMM = 0x00000200 26 | PROC_EVENT_COREDUMP = 0x40000000 27 | PROC_EVENT_EXIT = 0x80000000 28 | ) 29 | 30 | const ( 31 | CN_VAL_PROC = 0x1 32 | PROC_CN_MCAST_LISTEN = 0x1 33 | ) 34 | 35 | type ProcEventMsg interface { 36 | Pid() uint32 37 | Tgid() uint32 38 | } 39 | 40 | type ProcEventHeader struct { 41 | What uint32 42 | CPU uint32 43 | Timestamp uint64 44 | } 45 | 46 | type ProcEvent struct { 47 | ProcEventHeader 48 | Msg ProcEventMsg 49 | } 50 | 51 | func (pe *ProcEvent) setHeader(h ProcEventHeader) { 52 | pe.What = h.What 53 | pe.CPU = h.CPU 54 | pe.Timestamp = h.Timestamp 55 | } 56 | 57 | type ExitProcEvent struct { 58 | ProcessPid uint32 59 | ProcessTgid uint32 60 | ExitCode uint32 61 | ExitSignal uint32 62 | ParentPid uint32 63 | ParentTgid uint32 64 | } 65 | 66 | func (e *ExitProcEvent) Pid() uint32 { 67 | return e.ProcessPid 68 | } 69 | 70 | func (e *ExitProcEvent) Tgid() uint32 { 71 | return e.ProcessTgid 72 | } 73 | 74 | type ExecProcEvent struct { 75 | ProcessPid uint32 76 | ProcessTgid uint32 77 | } 78 | 79 | func (e *ExecProcEvent) Pid() uint32 { 80 | return e.ProcessPid 81 | } 82 | 83 | func (e *ExecProcEvent) Tgid() uint32 { 84 | return e.ProcessTgid 85 | } 86 | 87 | type ForkProcEvent struct { 88 | ParentPid uint32 89 | ParentTgid uint32 90 | ChildPid uint32 91 | ChildTgid uint32 92 | } 93 | 94 | func (e *ForkProcEvent) Pid() uint32 { 95 | return e.ParentPid 96 | } 97 | 98 | func (e *ForkProcEvent) Tgid() uint32 { 99 | return e.ParentTgid 100 | } 101 | 102 | type CommProcEvent struct { 103 | ProcessPid uint32 104 | ProcessTgid uint32 105 | Comm [16]byte 106 | } 107 | 108 | func (e *CommProcEvent) Pid() uint32 { 109 | return e.ProcessPid 110 | } 111 | 112 | func (e *CommProcEvent) Tgid() uint32 { 113 | return e.ProcessTgid 114 | } 115 | 116 | func ProcEventMonitor(ch chan<- ProcEvent, done <-chan struct{}, errorChan chan<- error) error { 117 | h, err := NewHandle() 118 | if err != nil { 119 | return err 120 | } 121 | defer h.Delete() 122 | 123 | s, err := nl.SubscribeAt(netns.None(), netns.None(), unix.NETLINK_CONNECTOR, CN_IDX_PROC) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | var nlmsg nl.NetlinkRequest 129 | 130 | nlmsg.Pid = uint32(os.Getpid()) 131 | nlmsg.Type = unix.NLMSG_DONE 132 | nlmsg.Len = uint32(unix.SizeofNlMsghdr) 133 | 134 | cm := nl.NewCnMsg(CN_IDX_PROC, CN_VAL_PROC, PROC_CN_MCAST_LISTEN) 135 | nlmsg.AddData(cm) 136 | 137 | s.Send(&nlmsg) 138 | 139 | if done != nil { 140 | go func() { 141 | <-done 142 | s.Close() 143 | }() 144 | } 145 | 146 | go func() { 147 | defer close(ch) 148 | for { 149 | msgs, from, err := s.Receive() 150 | if err != nil { 151 | errorChan <- err 152 | return 153 | } 154 | if from.Pid != nl.PidKernel { 155 | errorChan <- fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) 156 | return 157 | } 158 | 159 | for _, m := range msgs { 160 | e := parseNetlinkMessage(m) 161 | if e != nil { 162 | ch <- *e 163 | } 164 | } 165 | 166 | } 167 | }() 168 | 169 | return nil 170 | } 171 | 172 | func parseNetlinkMessage(m syscall.NetlinkMessage) *ProcEvent { 173 | if m.Header.Type == unix.NLMSG_DONE { 174 | buf := bytes.NewBuffer(m.Data) 175 | msg := &nl.CnMsg{} 176 | hdr := &ProcEventHeader{} 177 | binary.Read(buf, nl.NativeEndian(), msg) 178 | binary.Read(buf, nl.NativeEndian(), hdr) 179 | 180 | pe := &ProcEvent{} 181 | pe.setHeader(*hdr) 182 | switch hdr.What { 183 | case PROC_EVENT_EXIT: 184 | event := &ExitProcEvent{} 185 | binary.Read(buf, nl.NativeEndian(), event) 186 | pe.Msg = event 187 | return pe 188 | case PROC_EVENT_FORK: 189 | event := &ForkProcEvent{} 190 | binary.Read(buf, nl.NativeEndian(), event) 191 | pe.Msg = event 192 | return pe 193 | case PROC_EVENT_EXEC: 194 | event := &ExecProcEvent{} 195 | binary.Read(buf, nl.NativeEndian(), event) 196 | pe.Msg = event 197 | return pe 198 | case PROC_EVENT_COMM: 199 | event := &CommProcEvent{} 200 | binary.Read(buf, nl.NativeEndian(), event) 201 | pe.Msg = event 202 | return pe 203 | } 204 | return nil 205 | } 206 | 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /proc_event_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package netlink 4 | 5 | import ( 6 | "github.com/vishvananda/netns" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "testing" 11 | ) 12 | 13 | func TestSubscribeProcEvent(t *testing.T) { 14 | skipUnlessRoot(t) 15 | runtime.LockOSThread() 16 | defer runtime.UnlockOSThread() 17 | 18 | pid1ns, err := netns.GetFromPid(1) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | err = netns.Set(pid1ns) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | ch := make(chan ProcEvent) 29 | done := make(chan struct{}) 30 | defer close(done) 31 | 32 | errChan := make(chan error) 33 | 34 | if err := ProcEventMonitor(ch, done, errChan); err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | cmd := exec.Command("false") 39 | if err := cmd.Start(); err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | // first we wait for proc - i.e. childTgid is cmd.Process.Pid 44 | for { 45 | e := <-ch 46 | t.Logf("pid: %+v e: %+v", os.Getpid(), e) 47 | if e.Msg.Tgid() == uint32(os.Getpid()) { 48 | if forkEvent, ok := e.Msg.(*ForkProcEvent); ok { 49 | if forkEvent.ChildTgid == uint32(cmd.Process.Pid) { 50 | break 51 | } 52 | } 53 | } 54 | } 55 | 56 | // wait for exec event 57 | for { 58 | e := <-ch 59 | if e.Msg.Tgid() == uint32(cmd.Process.Pid) { 60 | if _, ok := e.Msg.(*ExecProcEvent); ok { 61 | break 62 | } 63 | } 64 | } 65 | 66 | cmd.Wait() 67 | for { 68 | e := <-ch 69 | if e.Msg.Tgid() == uint32(cmd.Process.Pid) { 70 | if exitEvent, ok := e.Msg.(*ExitProcEvent); ok { 71 | if exitEvent.ExitCode != 256 { 72 | t.Errorf("Expected error code 256 (-1), but got %+v", exitEvent) 73 | } 74 | break 75 | } 76 | } 77 | } 78 | 79 | done <- struct{}{} 80 | } 81 | -------------------------------------------------------------------------------- /protinfo.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Protinfo represents bridge flags from netlink. 8 | type Protinfo struct { 9 | Hairpin bool 10 | Guard bool 11 | FastLeave bool 12 | RootBlock bool 13 | Learning bool 14 | Flood bool 15 | ProxyArp bool 16 | ProxyArpWiFi bool 17 | Isolated bool 18 | NeighSuppress bool 19 | VlanTunnel bool 20 | } 21 | 22 | // String returns a list of enabled flags 23 | func (prot *Protinfo) String() string { 24 | if prot == nil { 25 | return "" 26 | } 27 | 28 | var boolStrings []string 29 | if prot.Hairpin { 30 | boolStrings = append(boolStrings, "Hairpin") 31 | } 32 | if prot.Guard { 33 | boolStrings = append(boolStrings, "Guard") 34 | } 35 | if prot.FastLeave { 36 | boolStrings = append(boolStrings, "FastLeave") 37 | } 38 | if prot.RootBlock { 39 | boolStrings = append(boolStrings, "RootBlock") 40 | } 41 | if prot.Learning { 42 | boolStrings = append(boolStrings, "Learning") 43 | } 44 | if prot.Flood { 45 | boolStrings = append(boolStrings, "Flood") 46 | } 47 | if prot.ProxyArp { 48 | boolStrings = append(boolStrings, "ProxyArp") 49 | } 50 | if prot.ProxyArpWiFi { 51 | boolStrings = append(boolStrings, "ProxyArpWiFi") 52 | } 53 | if prot.Isolated { 54 | boolStrings = append(boolStrings, "Isolated") 55 | } 56 | if prot.NeighSuppress { 57 | boolStrings = append(boolStrings, "NeighSuppress") 58 | } 59 | if prot.VlanTunnel { 60 | boolStrings = append(boolStrings, "VlanTunnel") 61 | } 62 | return strings.Join(boolStrings, " ") 63 | } 64 | 65 | func boolToByte(x bool) []byte { 66 | if x { 67 | return []byte{1} 68 | } 69 | return []byte{0} 70 | } 71 | 72 | func byteToBool(x byte) bool { 73 | return uint8(x) != 0 74 | } 75 | -------------------------------------------------------------------------------- /protinfo_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "syscall" 7 | 8 | "github.com/vishvananda/netlink/nl" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 13 | // or incomplete. 14 | func LinkGetProtinfo(link Link) (Protinfo, error) { 15 | return pkgHandle.LinkGetProtinfo(link) 16 | } 17 | 18 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 19 | // or incomplete. 20 | func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { 21 | base := link.Attrs() 22 | h.ensureIndex(base) 23 | var pi Protinfo 24 | req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) 25 | msg := nl.NewIfInfomsg(unix.AF_BRIDGE) 26 | req.AddData(msg) 27 | msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, 0) 28 | if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { 29 | return pi, executeErr 30 | } 31 | 32 | for _, m := range msgs { 33 | ans := nl.DeserializeIfInfomsg(m) 34 | if int(ans.Index) != base.Index { 35 | continue 36 | } 37 | attrs, err := nl.ParseRouteAttr(m[ans.Len():]) 38 | if err != nil { 39 | return pi, err 40 | } 41 | for _, attr := range attrs { 42 | if attr.Attr.Type != unix.IFLA_PROTINFO|unix.NLA_F_NESTED { 43 | continue 44 | } 45 | infos, err := nl.ParseRouteAttr(attr.Value) 46 | if err != nil { 47 | return pi, err 48 | } 49 | pi = parseProtinfo(infos) 50 | 51 | return pi, executeErr 52 | } 53 | } 54 | return pi, fmt.Errorf("Device with index %d not found", base.Index) 55 | } 56 | 57 | func parseProtinfo(infos []syscall.NetlinkRouteAttr) (pi Protinfo) { 58 | for _, info := range infos { 59 | switch info.Attr.Type { 60 | case nl.IFLA_BRPORT_MODE: 61 | pi.Hairpin = byteToBool(info.Value[0]) 62 | case nl.IFLA_BRPORT_GUARD: 63 | pi.Guard = byteToBool(info.Value[0]) 64 | case nl.IFLA_BRPORT_FAST_LEAVE: 65 | pi.FastLeave = byteToBool(info.Value[0]) 66 | case nl.IFLA_BRPORT_PROTECT: 67 | pi.RootBlock = byteToBool(info.Value[0]) 68 | case nl.IFLA_BRPORT_LEARNING: 69 | pi.Learning = byteToBool(info.Value[0]) 70 | case nl.IFLA_BRPORT_UNICAST_FLOOD: 71 | pi.Flood = byteToBool(info.Value[0]) 72 | case nl.IFLA_BRPORT_PROXYARP: 73 | pi.ProxyArp = byteToBool(info.Value[0]) 74 | case nl.IFLA_BRPORT_PROXYARP_WIFI: 75 | pi.ProxyArpWiFi = byteToBool(info.Value[0]) 76 | case nl.IFLA_BRPORT_ISOLATED: 77 | pi.Isolated = byteToBool(info.Value[0]) 78 | case nl.IFLA_BRPORT_NEIGH_SUPPRESS: 79 | pi.NeighSuppress = byteToBool(info.Value[0]) 80 | case nl.IFLA_BRPORT_VLAN_TUNNEL: 81 | pi.VlanTunnel = byteToBool(info.Value[0]) 82 | } 83 | 84 | } 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /protinfo_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestProtinfo(t *testing.T) { 11 | tearDown := setUpNetlinkTest(t) 12 | defer tearDown() 13 | master := &Bridge{LinkAttrs: LinkAttrs{Name: "foo"}} 14 | if err := LinkAdd(master); err != nil { 15 | t.Fatal(err) 16 | } 17 | iface1 := &Dummy{LinkAttrs{Name: "bar1", MasterIndex: master.Index}} 18 | iface2 := &Dummy{LinkAttrs{Name: "bar2", MasterIndex: master.Index}} 19 | iface3 := &Dummy{LinkAttrs{Name: "bar3"}} 20 | iface4 := &Dummy{LinkAttrs{Name: "bar4", MasterIndex: master.Index}} 21 | 22 | if err := LinkAdd(iface1); err != nil { 23 | t.Fatal(err) 24 | } 25 | if err := LinkAdd(iface2); err != nil { 26 | t.Fatal(err) 27 | } 28 | if err := LinkAdd(iface3); err != nil { 29 | t.Fatal(err) 30 | } 31 | if err := LinkAdd(iface4); err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | oldpi1, err := LinkGetProtinfo(iface1) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | oldpi2, err := LinkGetProtinfo(iface2) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | oldpi4, err := LinkGetProtinfo(iface4) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | if err := LinkSetHairpin(iface1, true); err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | if err := LinkSetRootBlock(iface1, true); err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | pi1, err := LinkGetProtinfo(iface1) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if !pi1.Hairpin { 61 | t.Fatalf("Hairpin mode is not enabled for %s, but should", iface1.Name) 62 | } 63 | if !pi1.RootBlock { 64 | t.Fatalf("RootBlock is not enabled for %s, but should", iface1.Name) 65 | } 66 | if pi1.Isolated { 67 | t.Fatalf("Isolated mode is enabled for %s, but shouldn't", iface1.Name) 68 | } 69 | if pi1.ProxyArp != oldpi1.ProxyArp { 70 | t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface1.Name) 71 | } 72 | if pi1.ProxyArpWiFi != oldpi1.ProxyArp { 73 | t.Fatalf("ProxyArpWiFi ProxyArp field was changed for %s but shouldn't", iface1.Name) 74 | } 75 | if pi1.Guard != oldpi1.Guard { 76 | t.Fatalf("Guard field was changed for %s but shouldn't", iface1.Name) 77 | } 78 | if pi1.FastLeave != oldpi1.FastLeave { 79 | t.Fatalf("FastLeave field was changed for %s but shouldn't", iface1.Name) 80 | } 81 | if pi1.Learning != oldpi1.Learning { 82 | t.Fatalf("Learning field was changed for %s but shouldn't", iface1.Name) 83 | } 84 | if pi1.Flood != oldpi1.Flood { 85 | t.Fatalf("Flood field was changed for %s but shouldn't", iface1.Name) 86 | } 87 | if pi1.NeighSuppress != oldpi1.NeighSuppress { 88 | t.Fatalf("NeighSuppress field was changed for %s but shouldn't", iface1.Name) 89 | } 90 | 91 | if err := LinkSetGuard(iface2, true); err != nil { 92 | t.Fatal(err) 93 | } 94 | if err := LinkSetLearning(iface2, false); err != nil { 95 | t.Fatal(err) 96 | } 97 | pi2, err := LinkGetProtinfo(iface2) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | if pi2.Hairpin { 102 | t.Fatalf("Hairpin mode is enabled for %s, but shouldn't", iface2.Name) 103 | } 104 | if !pi2.Guard { 105 | t.Fatalf("Guard is not enabled for %s, but should", iface2.Name) 106 | } 107 | if pi2.ProxyArp != oldpi2.ProxyArp { 108 | t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface2.Name) 109 | } 110 | if pi2.ProxyArpWiFi != oldpi2.ProxyArpWiFi { 111 | t.Fatalf("ProxyArpWiFi field was changed for %s but shouldn't", iface2.Name) 112 | } 113 | if pi2.Learning { 114 | t.Fatalf("Learning is enabled for %s, but shouldn't", iface2.Name) 115 | } 116 | if pi2.RootBlock != oldpi2.RootBlock { 117 | t.Fatalf("RootBlock field was changed for %s but shouldn't", iface2.Name) 118 | } 119 | if pi2.FastLeave != oldpi2.FastLeave { 120 | t.Fatalf("FastLeave field was changed for %s but shouldn't", iface2.Name) 121 | } 122 | if pi2.Flood != oldpi2.Flood { 123 | t.Fatalf("Flood field was changed for %s but shouldn't", iface2.Name) 124 | } 125 | if pi2.NeighSuppress != oldpi2.NeighSuppress { 126 | t.Fatalf("NeighSuppress field was changed for %s but shouldn't", iface2.Name) 127 | } 128 | 129 | if err := LinkSetHairpin(iface3, true); err == nil || err.Error() != "operation not supported" { 130 | t.Fatalf("Set protinfo attrs for link without master is not supported, but err: %s", err) 131 | } 132 | 133 | // Setting kernel requirement for next tests which require BR_PROXYARP 134 | minKernelRequired(t, 3, 19) 135 | 136 | if err := LinkSetBrProxyArp(iface4, true); err != nil { 137 | t.Fatal(err) 138 | } 139 | 140 | if err := LinkSetBrProxyArpWiFi(iface4, true); err != nil { 141 | t.Fatal(err) 142 | } 143 | pi4, err := LinkGetProtinfo(iface4) 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | if pi4.Hairpin != oldpi4.Hairpin { 148 | t.Fatalf("Hairpin field was changed for %s but shouldn't", iface4.Name) 149 | } 150 | if pi4.Guard != oldpi4.Guard { 151 | t.Fatalf("Guard field was changed for %s but shouldn't", iface4.Name) 152 | } 153 | if pi4.Learning != oldpi4.Learning { 154 | t.Fatalf("Learning field was changed for %s but shouldn't", iface4.Name) 155 | } 156 | if !pi4.ProxyArp { 157 | t.Fatalf("ProxyArp is not enabled for %s, but should", iface4.Name) 158 | } 159 | if !pi4.ProxyArpWiFi { 160 | t.Fatalf("ProxyArpWiFi is not enabled for %s, but should", iface4.Name) 161 | } 162 | if pi4.RootBlock != oldpi4.RootBlock { 163 | t.Fatalf("RootBlock field was changed for %s but shouldn't", iface4.Name) 164 | } 165 | if pi4.FastLeave != oldpi4.FastLeave { 166 | t.Fatalf("FastLeave field was changed for %s but shouldn't", iface4.Name) 167 | } 168 | if pi4.Flood != oldpi4.Flood { 169 | t.Fatalf("Flood field was changed for %s but shouldn't", iface4.Name) 170 | } 171 | 172 | // BR_NEIGH_SUPPRESS added on 4.15 173 | minKernelRequired(t, 4, 15) 174 | 175 | if err := LinkSetBrNeighSuppress(iface1, true); err != nil { 176 | t.Fatal(err) 177 | } 178 | pi1, err = LinkGetProtinfo(iface1) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | if !pi1.NeighSuppress { 183 | t.Fatalf("NeighSuppress is not enabled for %s but should", iface1.Name) 184 | } 185 | 186 | // Setting kernel requirement for next tests which require BRPORT_ISOLATED 187 | minKernelRequired(t, 4, 18) 188 | 189 | if err := LinkSetIsolated(iface1, true); err != nil { 190 | t.Fatal(err) 191 | } 192 | pi1, err = LinkGetProtinfo(iface1) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | if !pi1.Isolated { 197 | t.Fatalf("Isolated mode is not enabled for %s, but should", iface1.Name) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /rdma_link_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package netlink 4 | 5 | import ( 6 | "io/ioutil" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/vishvananda/netns" 11 | ) 12 | 13 | func setupRdmaKModule(t *testing.T, name string) { 14 | skipUnlessRoot(t) 15 | file, err := ioutil.ReadFile("/proc/modules") 16 | if err != nil { 17 | t.Fatal("Failed to open /proc/modules", err) 18 | } 19 | for _, line := range strings.Split(string(file), "\n") { 20 | n := strings.Split(line, " ")[0] 21 | if n == name { 22 | return 23 | } 24 | 25 | } 26 | t.Skipf("Test requires kmodule %q.", name) 27 | } 28 | 29 | func TestRdmaGetRdmaLink(t *testing.T) { 30 | minKernelRequired(t, 4, 16) 31 | setupRdmaKModule(t, "ib_core") 32 | _, err := RdmaLinkByName("foo") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | } 37 | 38 | func TestRdmaSetRdmaLinkName(t *testing.T) { 39 | minKernelRequired(t, 4, 19) 40 | setupRdmaKModule(t, "ib_core") 41 | link, err := RdmaLinkByName("foo") 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | // Set new name 46 | err = RdmaLinkSetName(link, "bar") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | // Revert back to old name 51 | err = RdmaLinkSetName(link, "foo") 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | } 56 | 57 | func TestRdmaSystemGetNetnsMode(t *testing.T) { 58 | minKernelRequired(t, 5, 2) 59 | setupRdmaKModule(t, "ib_core") 60 | 61 | mode, err := RdmaSystemGetNetnsMode() 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | t.Log("rdma system netns mode =", mode) 66 | } 67 | 68 | func TestRdmaSystemSetNetnsMode(t *testing.T) { 69 | var newMode string 70 | var mode string 71 | var err error 72 | 73 | minKernelRequired(t, 5, 2) 74 | setupRdmaKModule(t, "ib_core") 75 | 76 | mode, err = RdmaSystemGetNetnsMode() 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | t.Log("current rdma system mode =", mode) 81 | 82 | err = RdmaSystemSetNetnsMode(mode) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | // Flip the mode from current mode 87 | if mode == "exclusive" { 88 | RdmaSystemSetNetnsMode("shared") 89 | } else { 90 | RdmaSystemSetNetnsMode("exclusive") 91 | } 92 | newMode, err = RdmaSystemGetNetnsMode() 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | t.Log("new rdma system mode =", newMode) 97 | 98 | // Change back to original mode 99 | err = RdmaSystemSetNetnsMode(mode) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | } 104 | 105 | func TestRdmaLinkSetNsFd(t *testing.T) { 106 | minKernelRequired(t, 5, 2) 107 | setupRdmaKModule(t, "ib_core") 108 | 109 | mode, err := RdmaSystemGetNetnsMode() 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | t.Log("current rdma netns mode", mode) 114 | err = RdmaSystemSetNetnsMode("exclusive") 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | basens, err := netns.Get() 119 | if err != nil { 120 | RdmaSystemSetNetnsMode(mode) 121 | t.Fatal("Failed to get basens") 122 | } 123 | defer basens.Close() 124 | 125 | newns, err := netns.New() 126 | if err != nil { 127 | RdmaSystemSetNetnsMode(mode) 128 | t.Fatal("Failed to create newns") 129 | } 130 | 131 | netns.Set(basens) 132 | link, err := RdmaLinkByName("foo") 133 | if err != nil { 134 | // Remove the namespace as RDMA subsystem requires 135 | // no namespace to exist when changing net namespace mode 136 | newns.Close() 137 | RdmaSystemSetNetnsMode(mode) 138 | t.Fatal(err) 139 | } 140 | t.Log("rdma link: ", link) 141 | 142 | err = RdmaLinkSetNsFd(link, uint32(newns)) 143 | if err != nil { 144 | newns.Close() 145 | RdmaSystemSetNetnsMode(mode) 146 | t.Fatal(err) 147 | } 148 | 149 | newns.Close() 150 | //Set the old mode back at start of the test 151 | err = RdmaSystemSetNetnsMode(mode) 152 | if err != nil { 153 | t.Fatal(err) 154 | } 155 | } 156 | 157 | func TestRdmaLinkList(t *testing.T) { 158 | minKernelRequired(t, 4, 16) 159 | setupRdmaKModule(t, "ib_core") 160 | links, err := RdmaLinkList() 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | t.Log("RDMA devices:") 165 | for _, link := range links { 166 | t.Logf("%d: %s", link.Attrs.Index, link.Attrs.Name) 167 | } 168 | } 169 | 170 | func TestRdmaLinkAddAndDel(t *testing.T) { 171 | // related commit is https://github.com/torvalds/linux/commit/3856ec4b93c9463d36ee39098dde1fbbd29ec6dd. 172 | minKernelRequired(t, 5, 1) 173 | setupRdmaKModule(t, "rdma_rxe") 174 | 175 | checkPresence := func(name string, exist bool) { 176 | links, err := RdmaLinkList() 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | 181 | found := false 182 | for _, link := range links { 183 | if link.Attrs.Name == name { 184 | found = true 185 | break 186 | } 187 | } 188 | 189 | if found != exist { 190 | t.Fatalf("expected rdma link %s presence=%v, but got presence=%v", name, exist, found) 191 | } 192 | } 193 | 194 | linkName := t.Name() 195 | 196 | if err := RdmaLinkAdd(linkName, "rxe", "lo"); err != nil { 197 | t.Fatal(err) 198 | } 199 | 200 | checkPresence(linkName, true) 201 | 202 | if err := RdmaLinkDel(linkName); err != nil { 203 | t.Fatal(err) 204 | } 205 | 206 | checkPresence(linkName, false) 207 | } 208 | 209 | func TestRdmaLinkMetrics(t *testing.T) { 210 | minKernelRequired(t, 5, 1) 211 | setupRdmaKModule(t, "rdma_rxe") 212 | if err := RdmaLinkAdd(t.Name(), "rxe", "lo"); err != nil { 213 | t.Fatal(err) 214 | } 215 | link, err := RdmaLinkByName(t.Name()) 216 | if err != nil { 217 | t.Fatal(err) 218 | } 219 | defer RdmaLinkDel(t.Name()) 220 | resources, err := RdmaResourceList() 221 | if err != nil { 222 | t.Fatal(err) 223 | } 224 | for _, resource := range resources { 225 | t.Logf("resource: %+v", resource) 226 | } 227 | stats, err := RdmaStatistic(link) 228 | if err != nil { 229 | t.Fatal(err) 230 | } 231 | for _, stat := range stats.RdmaPortStatistics { 232 | t.Logf("stat: %+v", stat) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /route.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | // Scope is an enum representing a route scope. 10 | type Scope uint8 11 | 12 | type NextHopFlag int 13 | 14 | const ( 15 | RT_FILTER_PROTOCOL uint64 = 1 << (1 + iota) 16 | RT_FILTER_SCOPE 17 | RT_FILTER_TYPE 18 | RT_FILTER_TOS 19 | RT_FILTER_IIF 20 | RT_FILTER_OIF 21 | RT_FILTER_DST 22 | RT_FILTER_SRC 23 | RT_FILTER_GW 24 | RT_FILTER_TABLE 25 | RT_FILTER_HOPLIMIT 26 | RT_FILTER_PRIORITY 27 | RT_FILTER_MARK 28 | RT_FILTER_MASK 29 | RT_FILTER_REALM 30 | ) 31 | 32 | type Destination interface { 33 | Family() int 34 | Decode([]byte) error 35 | Encode() ([]byte, error) 36 | String() string 37 | Equal(Destination) bool 38 | } 39 | 40 | type Encap interface { 41 | Type() int 42 | Decode([]byte) error 43 | Encode() ([]byte, error) 44 | String() string 45 | Equal(Encap) bool 46 | } 47 | 48 | // Protocol describe what was the originator of the route 49 | type RouteProtocol int 50 | 51 | // Route represents a netlink route. 52 | type Route struct { 53 | LinkIndex int 54 | ILinkIndex int 55 | Scope Scope 56 | Dst *net.IPNet 57 | Src net.IP 58 | Gw net.IP 59 | MultiPath []*NexthopInfo 60 | Protocol RouteProtocol 61 | Priority int 62 | Family int 63 | Table int 64 | Type int 65 | Tos int 66 | Flags int 67 | MPLSDst *int 68 | NewDst Destination 69 | Encap Encap 70 | Via Destination 71 | Realm int 72 | MTU int 73 | MTULock bool 74 | Window int 75 | Rtt int 76 | RttVar int 77 | Ssthresh int 78 | Cwnd int 79 | AdvMSS int 80 | Reordering int 81 | Hoplimit int 82 | InitCwnd int 83 | Features int 84 | RtoMin int 85 | RtoMinLock bool 86 | InitRwnd int 87 | QuickACK int 88 | Congctl string 89 | FastOpenNoCookie int 90 | } 91 | 92 | func (r Route) String() string { 93 | elems := []string{} 94 | if len(r.MultiPath) == 0 { 95 | elems = append(elems, fmt.Sprintf("Ifindex: %d", r.LinkIndex)) 96 | } 97 | if r.MPLSDst != nil { 98 | elems = append(elems, fmt.Sprintf("Dst: %d", r.MPLSDst)) 99 | } else { 100 | elems = append(elems, fmt.Sprintf("Dst: %s", r.Dst)) 101 | } 102 | if r.NewDst != nil { 103 | elems = append(elems, fmt.Sprintf("NewDst: %s", r.NewDst)) 104 | } 105 | if r.Encap != nil { 106 | elems = append(elems, fmt.Sprintf("Encap: %s", r.Encap)) 107 | } 108 | if r.Via != nil { 109 | elems = append(elems, fmt.Sprintf("Via: %s", r.Via)) 110 | } 111 | elems = append(elems, fmt.Sprintf("Src: %s", r.Src)) 112 | if len(r.MultiPath) > 0 { 113 | elems = append(elems, fmt.Sprintf("Gw: %s", r.MultiPath)) 114 | } else { 115 | elems = append(elems, fmt.Sprintf("Gw: %s", r.Gw)) 116 | } 117 | elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags())) 118 | elems = append(elems, fmt.Sprintf("Table: %d", r.Table)) 119 | elems = append(elems, fmt.Sprintf("Realm: %d", r.Realm)) 120 | return fmt.Sprintf("{%s}", strings.Join(elems, " ")) 121 | } 122 | 123 | func (r Route) Equal(x Route) bool { 124 | return r.LinkIndex == x.LinkIndex && 125 | r.ILinkIndex == x.ILinkIndex && 126 | r.Scope == x.Scope && 127 | ipNetEqual(r.Dst, x.Dst) && 128 | r.Src.Equal(x.Src) && 129 | r.Gw.Equal(x.Gw) && 130 | nexthopInfoSlice(r.MultiPath).Equal(x.MultiPath) && 131 | r.Protocol == x.Protocol && 132 | r.Priority == x.Priority && 133 | r.Realm == x.Realm && 134 | r.Table == x.Table && 135 | r.Type == x.Type && 136 | r.Tos == x.Tos && 137 | r.Hoplimit == x.Hoplimit && 138 | r.Flags == x.Flags && 139 | (r.MPLSDst == x.MPLSDst || (r.MPLSDst != nil && x.MPLSDst != nil && *r.MPLSDst == *x.MPLSDst)) && 140 | (r.NewDst == x.NewDst || (r.NewDst != nil && r.NewDst.Equal(x.NewDst))) && 141 | (r.Via == x.Via || (r.Via != nil && r.Via.Equal(x.Via))) && 142 | (r.Encap == x.Encap || (r.Encap != nil && r.Encap.Equal(x.Encap))) 143 | } 144 | 145 | func (r *Route) SetFlag(flag NextHopFlag) { 146 | r.Flags |= int(flag) 147 | } 148 | 149 | func (r *Route) ClearFlag(flag NextHopFlag) { 150 | r.Flags &^= int(flag) 151 | } 152 | 153 | type flagString struct { 154 | f NextHopFlag 155 | s string 156 | } 157 | 158 | // RouteUpdate is sent when a route changes - type is RTM_NEWROUTE or RTM_DELROUTE 159 | 160 | // NlFlags is only non-zero for RTM_NEWROUTE, the following flags can be set: 161 | // - unix.NLM_F_REPLACE - Replace existing matching config object with this request 162 | // - unix.NLM_F_EXCL - Don't replace the config object if it already exists 163 | // - unix.NLM_F_CREATE - Create config object if it doesn't already exist 164 | // - unix.NLM_F_APPEND - Add to the end of the object list 165 | type RouteUpdate struct { 166 | Type uint16 167 | NlFlags uint16 168 | Route 169 | } 170 | 171 | type NexthopInfo struct { 172 | LinkIndex int 173 | Hops int 174 | Gw net.IP 175 | Flags int 176 | NewDst Destination 177 | Encap Encap 178 | Via Destination 179 | } 180 | 181 | func (n *NexthopInfo) String() string { 182 | elems := []string{} 183 | elems = append(elems, fmt.Sprintf("Ifindex: %d", n.LinkIndex)) 184 | if n.NewDst != nil { 185 | elems = append(elems, fmt.Sprintf("NewDst: %s", n.NewDst)) 186 | } 187 | if n.Encap != nil { 188 | elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap)) 189 | } 190 | if n.Via != nil { 191 | elems = append(elems, fmt.Sprintf("Via: %s", n.Via)) 192 | } 193 | elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1)) 194 | elems = append(elems, fmt.Sprintf("Gw: %s", n.Gw)) 195 | elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags())) 196 | return fmt.Sprintf("{%s}", strings.Join(elems, " ")) 197 | } 198 | 199 | func (n NexthopInfo) Equal(x NexthopInfo) bool { 200 | return n.LinkIndex == x.LinkIndex && 201 | n.Hops == x.Hops && 202 | n.Gw.Equal(x.Gw) && 203 | n.Flags == x.Flags && 204 | (n.NewDst == x.NewDst || (n.NewDst != nil && n.NewDst.Equal(x.NewDst))) && 205 | (n.Encap == x.Encap || (n.Encap != nil && n.Encap.Equal(x.Encap))) 206 | } 207 | 208 | type nexthopInfoSlice []*NexthopInfo 209 | 210 | func (n nexthopInfoSlice) Equal(x []*NexthopInfo) bool { 211 | if len(n) != len(x) { 212 | return false 213 | } 214 | for i := range n { 215 | if n[i] == nil || x[i] == nil { 216 | return false 217 | } 218 | if !n[i].Equal(*x[i]) { 219 | return false 220 | } 221 | } 222 | return true 223 | } 224 | 225 | // ipNetEqual returns true iff both IPNet are equal 226 | func ipNetEqual(ipn1 *net.IPNet, ipn2 *net.IPNet) bool { 227 | if ipn1 == ipn2 { 228 | return true 229 | } 230 | if ipn1 == nil || ipn2 == nil { 231 | return false 232 | } 233 | m1, _ := ipn1.Mask.Size() 234 | m2, _ := ipn2.Mask.Size() 235 | return m1 == m2 && ipn1.IP.Equal(ipn2.IP) 236 | } 237 | -------------------------------------------------------------------------------- /route_unspecified.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package netlink 4 | 5 | import "strconv" 6 | 7 | func (r *Route) ListFlags() []string { 8 | return []string{} 9 | } 10 | 11 | func (n *NexthopInfo) ListFlags() []string { 12 | return []string{} 13 | } 14 | 15 | func (s Scope) String() string { 16 | return "unknown" 17 | } 18 | 19 | func (p RouteProtocol) String() string { 20 | return strconv.Itoa(int(p)) 21 | } 22 | -------------------------------------------------------------------------------- /rule.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // Rule represents a netlink rule. 9 | type Rule struct { 10 | Priority int 11 | Family int 12 | Table int 13 | Mark uint32 14 | Mask *uint32 15 | Tos uint 16 | TunID uint 17 | Goto int 18 | Src *net.IPNet 19 | Dst *net.IPNet 20 | Flow int 21 | IifName string 22 | OifName string 23 | SuppressIfgroup int 24 | SuppressPrefixlen int 25 | Invert bool 26 | Dport *RulePortRange 27 | Sport *RulePortRange 28 | IPProto int 29 | UIDRange *RuleUIDRange 30 | Protocol uint8 31 | Type uint8 32 | } 33 | 34 | func (r Rule) String() string { 35 | from := "all" 36 | if r.Src != nil && r.Src.String() != "" { 37 | from = r.Src.String() 38 | } 39 | 40 | to := "all" 41 | if r.Dst != nil && r.Dst.String() != "" { 42 | to = r.Dst.String() 43 | } 44 | 45 | return fmt.Sprintf("ip rule %d: from %s to %s table %d %s", 46 | r.Priority, from, to, r.Table, r.typeString()) 47 | } 48 | 49 | // NewRule return empty rules. 50 | func NewRule() *Rule { 51 | return &Rule{ 52 | SuppressIfgroup: -1, 53 | SuppressPrefixlen: -1, 54 | Priority: -1, 55 | Mark: 0, 56 | Mask: nil, 57 | Goto: -1, 58 | Flow: -1, 59 | } 60 | } 61 | 62 | // NewRulePortRange creates rule sport/dport range. 63 | func NewRulePortRange(start, end uint16) *RulePortRange { 64 | return &RulePortRange{Start: start, End: end} 65 | } 66 | 67 | // RulePortRange represents rule sport/dport range. 68 | type RulePortRange struct { 69 | Start uint16 70 | End uint16 71 | } 72 | 73 | // NewRuleUIDRange creates rule uid range. 74 | func NewRuleUIDRange(start, end uint32) *RuleUIDRange { 75 | return &RuleUIDRange{Start: start, End: end} 76 | } 77 | 78 | // RuleUIDRange represents rule uid range. 79 | type RuleUIDRange struct { 80 | Start uint32 81 | End uint32 82 | } 83 | -------------------------------------------------------------------------------- /rule_nonlinux.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package netlink 5 | 6 | func (r Rule) typeString() string { 7 | return "" 8 | } 9 | -------------------------------------------------------------------------------- /socket.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import "net" 4 | 5 | // SocketID identifies a single socket. 6 | type SocketID struct { 7 | SourcePort uint16 8 | DestinationPort uint16 9 | Source net.IP 10 | Destination net.IP 11 | Interface uint32 12 | Cookie [2]uint32 13 | } 14 | 15 | // Socket represents a netlink socket. 16 | type Socket struct { 17 | Family uint8 18 | State uint8 19 | Timer uint8 20 | Retrans uint8 21 | ID SocketID 22 | Expires uint32 23 | RQueue uint32 24 | WQueue uint32 25 | UID uint32 26 | INode uint32 27 | } 28 | 29 | // UnixSocket represents a netlink unix socket. 30 | type UnixSocket struct { 31 | Type uint8 32 | Family uint8 33 | State uint8 34 | pad uint8 35 | INode uint32 36 | Cookie [2]uint32 37 | } 38 | 39 | // XDPSocket represents an XDP socket (and the common diagnosis part in 40 | // particular). Please note that in contrast to [UnixSocket] the XDPSocket type 41 | // does not feature “State” information. 42 | type XDPSocket struct { 43 | // xdp_diag_msg 44 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21 45 | Family uint8 46 | Type uint8 47 | pad uint16 48 | Ino uint32 49 | Cookie [2]uint32 50 | } 51 | 52 | type XDPInfo struct { 53 | // XDP_DIAG_INFO/xdp_diag_info 54 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L51 55 | Ifindex uint32 56 | QueueID uint32 57 | 58 | // XDP_DIAG_UID 59 | UID uint32 60 | 61 | // XDP_RX_RING 62 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L56 63 | RxRingEntries uint32 64 | TxRingEntries uint32 65 | UmemFillRingEntries uint32 66 | UmemCompletionRingEntries uint32 67 | 68 | // XDR_DIAG_UMEM 69 | Umem *XDPDiagUmem 70 | 71 | // XDR_DIAG_STATS 72 | Stats *XDPDiagStats 73 | } 74 | 75 | const ( 76 | XDP_DU_F_ZEROCOPY = 1 << iota 77 | ) 78 | 79 | // XDPDiagUmem describes the umem attached to an XDP socket. 80 | // 81 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L62 82 | type XDPDiagUmem struct { 83 | Size uint64 84 | ID uint32 85 | NumPages uint32 86 | ChunkSize uint32 87 | Headroom uint32 88 | Ifindex uint32 89 | QueueID uint32 90 | Flags uint32 91 | Refs uint32 92 | } 93 | 94 | // XDPDiagStats contains ring statistics for an XDP socket. 95 | // 96 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L74 97 | type XDPDiagStats struct { 98 | RxDropped uint64 99 | RxInvalid uint64 100 | RxFull uint64 101 | FillRingEmpty uint64 102 | TxInvalid uint64 103 | TxRingEmpty uint64 104 | } 105 | -------------------------------------------------------------------------------- /socket_linux_test.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "reflect" 5 | "syscall" 6 | "testing" 7 | ) 8 | 9 | func TestAttrsToInetDiagTCPInfoResp(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | attrs []syscall.NetlinkRouteAttr 13 | expected *InetDiagTCPInfoResp 14 | wantFail bool 15 | }{ 16 | { 17 | name: "Empty", 18 | attrs: []syscall.NetlinkRouteAttr{}, 19 | expected: &InetDiagTCPInfoResp{}, 20 | }, 21 | { 22 | name: "BBRInfo Only", 23 | attrs: []syscall.NetlinkRouteAttr{ 24 | { 25 | Attr: syscall.RtAttr{ 26 | Len: 20, 27 | Type: INET_DIAG_BBRINFO, 28 | }, 29 | Value: []byte{ 30 | 100, 0, 0, 0, 0, 0, 0, 0, 31 | 111, 0, 0, 0, 32 | 222, 0, 0, 0, 33 | 123, 0, 0, 0, 34 | }, 35 | }, 36 | }, 37 | expected: &InetDiagTCPInfoResp{ 38 | TCPBBRInfo: &TCPBBRInfo{ 39 | BBRBW: 100, 40 | BBRMinRTT: 111, 41 | BBRPacingGain: 222, 42 | BBRCwndGain: 123, 43 | }, 44 | }, 45 | }, 46 | { 47 | name: "TCPInfo Only", 48 | attrs: []syscall.NetlinkRouteAttr{ 49 | { 50 | Attr: syscall.RtAttr{ 51 | Len: 232, 52 | Type: INET_DIAG_INFO, 53 | }, 54 | Value: tcpInfoData, 55 | }, 56 | }, 57 | expected: &InetDiagTCPInfoResp{ 58 | TCPInfo: tcpInfo, 59 | }, 60 | }, 61 | { 62 | name: "TCPInfo + TCPBBR", 63 | attrs: []syscall.NetlinkRouteAttr{ 64 | { 65 | Attr: syscall.RtAttr{ 66 | Len: 232, 67 | Type: INET_DIAG_INFO, 68 | }, 69 | Value: tcpInfoData, 70 | }, 71 | { 72 | Attr: syscall.RtAttr{ 73 | Len: 20, 74 | Type: INET_DIAG_BBRINFO, 75 | }, 76 | Value: []byte{ 77 | 100, 0, 0, 0, 0, 0, 0, 0, 78 | 111, 0, 0, 0, 79 | 222, 0, 0, 0, 80 | 123, 0, 0, 0, 81 | }, 82 | }, 83 | }, 84 | expected: &InetDiagTCPInfoResp{ 85 | TCPInfo: tcpInfo, 86 | TCPBBRInfo: &TCPBBRInfo{ 87 | BBRBW: 100, 88 | BBRMinRTT: 111, 89 | BBRPacingGain: 222, 90 | BBRCwndGain: 123, 91 | }, 92 | }, 93 | }, 94 | { 95 | name: "TCPBBR + TCPInfo (reverse)", 96 | attrs: []syscall.NetlinkRouteAttr{ 97 | { 98 | Attr: syscall.RtAttr{ 99 | Len: 20, 100 | Type: INET_DIAG_BBRINFO, 101 | }, 102 | Value: []byte{ 103 | 100, 0, 0, 0, 0, 0, 0, 0, 104 | 111, 0, 0, 0, 105 | 222, 0, 0, 0, 106 | 123, 0, 0, 0, 107 | }, 108 | }, 109 | { 110 | Attr: syscall.RtAttr{ 111 | Len: 232, 112 | Type: INET_DIAG_INFO, 113 | }, 114 | Value: tcpInfoData, 115 | }, 116 | }, 117 | expected: &InetDiagTCPInfoResp{ 118 | TCPInfo: tcpInfo, 119 | TCPBBRInfo: &TCPBBRInfo{ 120 | BBRBW: 100, 121 | BBRMinRTT: 111, 122 | BBRPacingGain: 222, 123 | BBRCwndGain: 123, 124 | }, 125 | }, 126 | }, 127 | } 128 | 129 | for _, test := range tests { 130 | res, err := attrsToInetDiagTCPInfoResp(test.attrs, nil) 131 | if err != nil && !test.wantFail { 132 | t.Errorf("Unexpected failure for test %q", test.name) 133 | continue 134 | } 135 | 136 | if err == nil && test.wantFail { 137 | t.Errorf("Unexpected success for test %q", test.name) 138 | continue 139 | } 140 | 141 | if !reflect.DeepEqual(test.expected, res) { 142 | t.Errorf("Unexpected failure for test %q", test.name) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /socket_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "net" 10 | "os/user" 11 | "strconv" 12 | "syscall" 13 | "testing" 14 | ) 15 | 16 | func TestSocketGet(t *testing.T) { 17 | defer setUpNetlinkTestWithLoopback(t)() 18 | 19 | type Addr struct { 20 | IP net.IP 21 | Port int 22 | } 23 | 24 | getAddr := func(a net.Addr) Addr { 25 | var addr Addr 26 | switch v := a.(type) { 27 | case *net.UDPAddr: 28 | addr.IP = v.IP 29 | addr.Port = v.Port 30 | case *net.TCPAddr: 31 | addr.IP = v.IP 32 | addr.Port = v.Port 33 | } 34 | return addr 35 | } 36 | 37 | checkSocket := func(t *testing.T, local, remote net.Addr) { 38 | socket, err := SocketGet(local, remote) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | localAddr, remoteAddr := getAddr(local), getAddr(remote) 44 | 45 | if got, want := socket.ID.Source, localAddr.IP; !got.Equal(want) { 46 | t.Fatalf("local ip = %v, want %v", got, want) 47 | } 48 | if got, want := socket.ID.Destination, remoteAddr.IP; !got.Equal(want) { 49 | t.Fatalf("remote ip = %v, want %v", got, want) 50 | } 51 | if got, want := int(socket.ID.SourcePort), localAddr.Port; got != want { 52 | t.Fatalf("local port = %d, want %d", got, want) 53 | } 54 | if got, want := int(socket.ID.DestinationPort), remoteAddr.Port; got != want { 55 | t.Fatalf("remote port = %d, want %d", got, want) 56 | } 57 | u, err := user.Current() 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | if got, want := strconv.Itoa(int(socket.UID)), u.Uid; got != want { 62 | t.Fatalf("UID = %s, want %s", got, want) 63 | } 64 | } 65 | 66 | for _, v := range [...]string{"tcp4", "tcp6"} { 67 | addr, err := net.ResolveTCPAddr(v, "localhost:0") 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | l, err := net.ListenTCP(v, addr) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | defer l.Close() 76 | 77 | conn, err := net.Dial(l.Addr().Network(), l.Addr().String()) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | defer conn.Close() 82 | 83 | checkSocket(t, conn.LocalAddr(), conn.RemoteAddr()) 84 | } 85 | 86 | for _, v := range [...]string{"udp4", "udp6"} { 87 | addr, err := net.ResolveUDPAddr(v, "localhost:0") 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | l, err := net.ListenUDP(v, addr) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | defer l.Close() 96 | conn, err := net.Dial(l.LocalAddr().Network(), l.LocalAddr().String()) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | defer conn.Close() 101 | 102 | checkSocket(t, conn.LocalAddr(), conn.RemoteAddr()) 103 | } 104 | } 105 | 106 | func TestSocketDestroy(t *testing.T) { 107 | defer setUpNetlinkTestWithLoopback(t)() 108 | 109 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 110 | if err != nil { 111 | log.Fatal(err) 112 | } 113 | l, err := net.ListenTCP("tcp", addr) 114 | if err != nil { 115 | log.Fatal(err) 116 | } 117 | defer l.Close() 118 | 119 | conn, err := net.Dial(l.Addr().Network(), l.Addr().String()) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | defer conn.Close() 124 | 125 | localAddr := conn.LocalAddr().(*net.TCPAddr) 126 | remoteAddr := conn.RemoteAddr().(*net.TCPAddr) 127 | err = SocketDestroy(localAddr, remoteAddr) 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | } 132 | 133 | func TestSocketDiagTCPInfo(t *testing.T) { 134 | Family4 := uint8(syscall.AF_INET) 135 | Family6 := uint8(syscall.AF_INET6) 136 | families := []uint8{Family4, Family6} 137 | for _, wantFamily := range families { 138 | res, err := SocketDiagTCPInfo(wantFamily) 139 | if err != nil { 140 | t.Fatal(err) 141 | } 142 | for _, i := range res { 143 | gotFamily := i.InetDiagMsg.Family 144 | if gotFamily != wantFamily { 145 | t.Fatalf("Socket family = %d, want %d", gotFamily, wantFamily) 146 | } 147 | } 148 | } 149 | } 150 | 151 | func TestSocketDiagUDPnfo(t *testing.T) { 152 | for _, want := range []uint8{syscall.AF_INET, syscall.AF_INET6} { 153 | result, err := SocketDiagUDPInfo(want) 154 | if err != nil { 155 | t.Fatal(err) 156 | } 157 | 158 | for _, r := range result { 159 | if got := r.InetDiagMsg.Family; got != want { 160 | t.Fatalf("protocol family = %v, want %v", got, want) 161 | } 162 | } 163 | } 164 | } 165 | 166 | func TestUnixSocketDiagInfo(t *testing.T) { 167 | want := syscall.AF_UNIX 168 | result, err := UnixSocketDiagInfo() 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | 173 | for i, r := range result { 174 | fmt.Println(r.DiagMsg) 175 | if got := r.DiagMsg.Family; got != uint8(want) { 176 | t.Fatalf("%d: protocol family = %v, want %v", i, got, want) 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /socket_xdp_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "syscall" 7 | 8 | "github.com/vishvananda/netlink/nl" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | const ( 13 | sizeofXDPSocketRequest = 1 + 1 + 2 + 4 + 4 + 2*4 14 | sizeofXDPSocket = 0x10 15 | ) 16 | 17 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L12 18 | type xdpSocketRequest struct { 19 | Family uint8 20 | Protocol uint8 21 | pad uint16 22 | Ino uint32 23 | Show uint32 24 | Cookie [2]uint32 25 | } 26 | 27 | func (r *xdpSocketRequest) Serialize() []byte { 28 | b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)} 29 | b.Write(r.Family) 30 | b.Write(r.Protocol) 31 | native.PutUint16(b.Next(2), r.pad) 32 | native.PutUint32(b.Next(4), r.Ino) 33 | native.PutUint32(b.Next(4), r.Show) 34 | native.PutUint32(b.Next(4), r.Cookie[0]) 35 | native.PutUint32(b.Next(4), r.Cookie[1]) 36 | return b.Bytes 37 | } 38 | 39 | func (r *xdpSocketRequest) Len() int { return sizeofXDPSocketRequest } 40 | 41 | func (s *XDPSocket) deserialize(b []byte) error { 42 | if len(b) < sizeofXDPSocket { 43 | return fmt.Errorf("XDP socket data short read (%d); want %d", len(b), sizeofXDPSocket) 44 | } 45 | rb := readBuffer{Bytes: b} 46 | s.Family = rb.Read() 47 | s.Type = rb.Read() 48 | s.pad = native.Uint16(rb.Next(2)) 49 | s.Ino = native.Uint32(rb.Next(4)) 50 | s.Cookie[0] = native.Uint32(rb.Next(4)) 51 | s.Cookie[1] = native.Uint32(rb.Next(4)) 52 | return nil 53 | } 54 | 55 | // SocketXDPGetInfo returns the XDP socket identified by its inode number and/or 56 | // socket cookie. Specify the cookie as SOCK_ANY_COOKIE if 57 | // 58 | // If the returned error is [ErrDumpInterrupted], the caller should retry. 59 | func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) { 60 | // We have a problem here: dumping AF_XDP sockets currently does not support 61 | // filtering. We thus need to dump all XSKs and then only filter afterwards 62 | // :( 63 | xsks, err := SocketDiagXDP() 64 | if err != nil { 65 | return nil, err 66 | } 67 | checkCookie := cookie != SOCK_ANY_COOKIE && cookie != 0 68 | crumblingCookie := [2]uint32{uint32(cookie), uint32(cookie >> 32)} 69 | checkIno := ino != 0 70 | var xskinfo *XDPDiagInfoResp 71 | for _, xsk := range xsks { 72 | if checkIno && xsk.XDPDiagMsg.Ino != ino { 73 | continue 74 | } 75 | if checkCookie && xsk.XDPDiagMsg.Cookie != crumblingCookie { 76 | continue 77 | } 78 | if xskinfo != nil { 79 | return nil, errors.New("multiple matching XDP sockets") 80 | } 81 | xskinfo = xsk 82 | } 83 | if xskinfo == nil { 84 | return nil, errors.New("no matching XDP socket") 85 | } 86 | return xskinfo, nil 87 | } 88 | 89 | // SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets. 90 | // 91 | // If the returned error is [ErrDumpInterrupted], results may be inconsistent 92 | // or incomplete. 93 | func SocketDiagXDP() ([]*XDPDiagInfoResp, error) { 94 | var result []*XDPDiagInfoResp 95 | err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error { 96 | sockInfo := &XDPSocket{} 97 | if err := sockInfo.deserialize(m.Data); err != nil { 98 | return err 99 | } 100 | attrs, err := nl.ParseRouteAttr(m.Data[sizeofXDPSocket:]) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | res, err := attrsToXDPDiagInfoResp(attrs, sockInfo) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | result = append(result, res) 111 | return nil 112 | }) 113 | if err != nil && !errors.Is(err, ErrDumpInterrupted) { 114 | return nil, err 115 | } 116 | return result, err 117 | } 118 | 119 | // socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets. 120 | func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error { 121 | s, err := nl.Subscribe(unix.NETLINK_INET_DIAG) 122 | if err != nil { 123 | return err 124 | } 125 | defer s.Close() 126 | 127 | req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) 128 | req.AddData(&xdpSocketRequest{ 129 | Family: unix.AF_XDP, 130 | Show: XDP_SHOW_INFO | XDP_SHOW_RING_CFG | XDP_SHOW_UMEM | XDP_SHOW_STATS, 131 | }) 132 | if err := s.Send(req); err != nil { 133 | return err 134 | } 135 | 136 | dumpIntr := false 137 | loop: 138 | for { 139 | msgs, from, err := s.Receive() 140 | if err != nil { 141 | return err 142 | } 143 | if from.Pid != nl.PidKernel { 144 | return fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) 145 | } 146 | if len(msgs) == 0 { 147 | return errors.New("no message nor error from netlink") 148 | } 149 | 150 | for _, m := range msgs { 151 | if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 { 152 | dumpIntr = true 153 | } 154 | switch m.Header.Type { 155 | case unix.NLMSG_DONE: 156 | break loop 157 | case unix.NLMSG_ERROR: 158 | error := int32(native.Uint32(m.Data[0:4])) 159 | return syscall.Errno(-error) 160 | } 161 | if err := receiver(m); err != nil { 162 | return err 163 | } 164 | } 165 | } 166 | if dumpIntr { 167 | return ErrDumpInterrupted 168 | } 169 | return nil 170 | } 171 | 172 | func attrsToXDPDiagInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *XDPSocket) (*XDPDiagInfoResp, error) { 173 | resp := &XDPDiagInfoResp{ 174 | XDPDiagMsg: sockInfo, 175 | XDPInfo: &XDPInfo{}, 176 | } 177 | for _, a := range attrs { 178 | switch a.Attr.Type { 179 | case XDP_DIAG_INFO: 180 | resp.XDPInfo.Ifindex = native.Uint32(a.Value[0:4]) 181 | resp.XDPInfo.QueueID = native.Uint32(a.Value[4:8]) 182 | case XDP_DIAG_UID: 183 | resp.XDPInfo.UID = native.Uint32(a.Value[0:4]) 184 | case XDP_DIAG_RX_RING: 185 | resp.XDPInfo.RxRingEntries = native.Uint32(a.Value[0:4]) 186 | case XDP_DIAG_TX_RING: 187 | resp.XDPInfo.TxRingEntries = native.Uint32(a.Value[0:4]) 188 | case XDP_DIAG_UMEM_FILL_RING: 189 | resp.XDPInfo.UmemFillRingEntries = native.Uint32(a.Value[0:4]) 190 | case XDP_DIAG_UMEM_COMPLETION_RING: 191 | resp.XDPInfo.UmemCompletionRingEntries = native.Uint32(a.Value[0:4]) 192 | case XDP_DIAG_UMEM: 193 | umem := &XDPDiagUmem{} 194 | if err := umem.deserialize(a.Value); err != nil { 195 | return nil, err 196 | } 197 | resp.XDPInfo.Umem = umem 198 | case XDP_DIAG_STATS: 199 | stats := &XDPDiagStats{} 200 | if err := stats.deserialize(a.Value); err != nil { 201 | return nil, err 202 | } 203 | resp.XDPInfo.Stats = stats 204 | } 205 | } 206 | return resp, nil 207 | } 208 | -------------------------------------------------------------------------------- /socket_xdp_linux_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package netlink 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | func TestSocketXDPGetInfo(t *testing.T) { 14 | xdpsockfd, err := unix.Socket(unix.AF_XDP, unix.SOCK_RAW, 0) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | defer unix.Close(xdpsockfd) 19 | 20 | wantFamily := unix.AF_XDP 21 | 22 | var xdpsockstat unix.Stat_t 23 | err = unix.Fstat(xdpsockfd, &xdpsockstat) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | wantIno := xdpsockstat.Ino 28 | 29 | result, err := SocketXDPGetInfo(uint32(wantIno), SOCK_ANY_COOKIE) 30 | if err != nil { 31 | if os.IsNotExist(err) { 32 | t.Skip("kernel lacks support for AF_XDP socket diagnosis") 33 | } 34 | t.Fatal(err) 35 | } 36 | 37 | if got := result.XDPDiagMsg.Family; got != uint8(wantFamily) { 38 | t.Fatalf("protocol family = %v, want %v", got, wantFamily) 39 | } 40 | if got := result.XDPDiagMsg.Ino; got != uint32(wantIno) { 41 | t.Fatalf("protocol ino = %v, want %v", got, wantIno) 42 | } 43 | if result.XDPInfo == nil { 44 | t.Fatalf("want non-nil XDPInfo, got nil") 45 | } 46 | if got := result.XDPInfo.Ifindex; got != 0 { 47 | t.Fatalf("ifindex = %v, want 0", got) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | // TCP States 4 | const ( 5 | TCP_ESTABLISHED = iota + 0x01 6 | TCP_SYN_SENT 7 | TCP_SYN_RECV 8 | TCP_FIN_WAIT1 9 | TCP_FIN_WAIT2 10 | TCP_TIME_WAIT 11 | TCP_CLOSE 12 | TCP_CLOSE_WAIT 13 | TCP_LAST_ACK 14 | TCP_LISTEN 15 | TCP_CLOSING 16 | TCP_NEW_SYN_REC 17 | TCP_MAX_STATES 18 | ) 19 | 20 | type TCPInfo struct { 21 | State uint8 22 | Ca_state uint8 23 | Retransmits uint8 24 | Probes uint8 25 | Backoff uint8 26 | Options uint8 27 | Snd_wscale uint8 // no uint4 28 | Rcv_wscale uint8 29 | Delivery_rate_app_limited uint8 30 | Fastopen_client_fail uint8 31 | Rto uint32 32 | Ato uint32 33 | Snd_mss uint32 34 | Rcv_mss uint32 35 | Unacked uint32 36 | Sacked uint32 37 | Lost uint32 38 | Retrans uint32 39 | Fackets uint32 40 | Last_data_sent uint32 41 | Last_ack_sent uint32 42 | Last_data_recv uint32 43 | Last_ack_recv uint32 44 | Pmtu uint32 45 | Rcv_ssthresh uint32 46 | Rtt uint32 47 | Rttvar uint32 48 | Snd_ssthresh uint32 49 | Snd_cwnd uint32 50 | Advmss uint32 51 | Reordering uint32 52 | Rcv_rtt uint32 53 | Rcv_space uint32 54 | Total_retrans uint32 55 | Pacing_rate uint64 56 | Max_pacing_rate uint64 57 | Bytes_acked uint64 /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ 58 | Bytes_received uint64 /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ 59 | Segs_out uint32 /* RFC4898 tcpEStatsPerfSegsOut */ 60 | Segs_in uint32 /* RFC4898 tcpEStatsPerfSegsIn */ 61 | Notsent_bytes uint32 62 | Min_rtt uint32 63 | Data_segs_in uint32 /* RFC4898 tcpEStatsDataSegsIn */ 64 | Data_segs_out uint32 /* RFC4898 tcpEStatsDataSegsOut */ 65 | Delivery_rate uint64 66 | Busy_time uint64 /* Time (usec) busy sending data */ 67 | Rwnd_limited uint64 /* Time (usec) limited by receive window */ 68 | Sndbuf_limited uint64 /* Time (usec) limited by send buffer */ 69 | Delivered uint32 70 | Delivered_ce uint32 71 | Bytes_sent uint64 /* RFC4898 tcpEStatsPerfHCDataOctetsOut */ 72 | Bytes_retrans uint64 /* RFC4898 tcpEStatsPerfOctetsRetrans */ 73 | Dsack_dups uint32 /* RFC4898 tcpEStatsStackDSACKDups */ 74 | Reord_seen uint32 /* reordering events seen */ 75 | Rcv_ooopack uint32 /* Out-of-order packets received */ 76 | Snd_wnd uint32 /* peer's advertised receive window after * scaling (bytes) */ 77 | } 78 | 79 | type TCPBBRInfo struct { 80 | BBRBW uint64 81 | BBRMinRTT uint32 82 | BBRPacingGain uint32 83 | BBRCwndGain uint32 84 | } 85 | 86 | // According to https://man7.org/linux/man-pages/man7/sock_diag.7.html 87 | type MemInfo struct { 88 | RMem uint32 89 | WMem uint32 90 | FMem uint32 91 | TMem uint32 92 | } 93 | -------------------------------------------------------------------------------- /tcp_linux_test.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | tcpInfoData []byte 10 | tcpInfo *TCPInfo 11 | ) 12 | 13 | func init() { 14 | tcpInfoData = []byte{ 15 | 1, 0, 0, 0, 0, 7, 120, 1, 96, 216, 3, 0, 64, 16 | 156, 0, 0, 120, 5, 0, 0, 64, 3, 0, 0, 0, 0, 17 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | 0, 0, 0, 236, 216, 0, 0, 0, 0, 0, 0, 56, 216, 19 | 0, 0, 144, 39, 0, 0, 220, 5, 0, 0, 88, 250, 20 | 0, 0, 79, 190, 0, 0, 7, 5, 0, 0, 255, 255, 21 | 255, 127, 10, 0, 0, 0, 168, 5, 0, 0, 3, 0, 0, 22 | 0, 0, 0, 0, 0, 144, 56, 0, 0, 0, 0, 0, 0, 1, 197, 23 | 8, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 24 | 255, 255, 157, 42, 0, 0, 0, 0, 0, 0, 148, 26, 0, 25 | 0, 0, 0, 0, 0, 181, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 26 | 0, 93, 180, 0, 0, 61, 0, 0, 0, 89, 0, 0, 0, 47, 216, 27 | 1, 0, 0, 0, 0, 0, 32, 65, 23, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 29 | 0, 0, 0, 0, 0, 156, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | 0, 195, 1, 0, 32 | } 33 | tcpInfo = &TCPInfo{ 34 | State: 1, 35 | Options: 7, 36 | Snd_wscale: 7, 37 | Rcv_wscale: 8, 38 | Rto: 252000, 39 | Ato: 40000, 40 | Snd_mss: 1400, 41 | Rcv_mss: 832, 42 | Last_data_sent: 55532, 43 | Last_data_recv: 55352, 44 | Last_ack_recv: 10128, 45 | Pmtu: 1500, 46 | Rcv_ssthresh: 64088, 47 | Rtt: 48719, 48 | Rttvar: 1287, 49 | Snd_ssthresh: 2147483647, 50 | Snd_cwnd: 10, 51 | Advmss: 1448, 52 | Reordering: 3, 53 | Rcv_space: 14480, 54 | Pacing_rate: 574721, 55 | Max_pacing_rate: 18446744073709551615, 56 | Bytes_acked: 10909, 57 | Bytes_received: 6804, 58 | Segs_out: 181, 59 | Segs_in: 95, 60 | Min_rtt: 46173, 61 | Data_segs_in: 61, 62 | Data_segs_out: 89, 63 | Delivery_rate: 120879, 64 | Busy_time: 1524000, 65 | Delivered: 90, 66 | Bytes_sent: 10908, 67 | Snd_wnd: 115456, 68 | } 69 | } 70 | 71 | func TestTCPInfoDeserialize(t *testing.T) { 72 | tests := []struct { 73 | name string 74 | input []byte 75 | expected *TCPInfo 76 | wantFail bool 77 | }{ 78 | { 79 | name: "Valid data", 80 | input: tcpInfoData, 81 | expected: tcpInfo, 82 | }, 83 | } 84 | 85 | for _, test := range tests { 86 | tcpbbr := &TCPInfo{} 87 | err := tcpbbr.deserialize(test.input) 88 | if err != nil && !test.wantFail { 89 | t.Errorf("Unexpected failure for test %q", test.name) 90 | continue 91 | } 92 | 93 | if err != nil && test.wantFail { 94 | continue 95 | } 96 | 97 | if !reflect.DeepEqual(test.expected, tcpbbr) { 98 | t.Errorf("Unexpected failure for test %q", test.name) 99 | } 100 | } 101 | } 102 | 103 | func TestTCPBBRInfoDeserialize(t *testing.T) { 104 | tests := []struct { 105 | name string 106 | input []byte 107 | expected *TCPBBRInfo 108 | wantFail bool 109 | }{ 110 | { 111 | name: "Valid data", 112 | input: []byte{ 113 | 100, 0, 0, 0, 0, 0, 0, 0, 114 | 111, 0, 0, 0, 115 | 222, 0, 0, 0, 116 | 123, 0, 0, 0, 117 | }, 118 | expected: &TCPBBRInfo{ 119 | BBRBW: 100, 120 | BBRMinRTT: 111, 121 | BBRPacingGain: 222, 122 | BBRCwndGain: 123, 123 | }, 124 | }, 125 | { 126 | name: "Invalid length", 127 | input: []byte{ 128 | 100, 0, 0, 0, 0, 0, 0, 0, 129 | 111, 0, 0, 0, 130 | 222, 0, 0, 0, 131 | 123, 0, 0, 132 | }, 133 | wantFail: true, 134 | }, 135 | } 136 | 137 | for _, test := range tests { 138 | tcpbbr := &TCPBBRInfo{} 139 | err := tcpbbr.deserialize(test.input) 140 | if err != nil && !test.wantFail { 141 | t.Errorf("Unexpected failure for test %q", test.name) 142 | continue 143 | } 144 | 145 | if err != nil && test.wantFail { 146 | continue 147 | } 148 | 149 | if !reflect.DeepEqual(test.expected, tcpbbr) { 150 | t.Errorf("Unexpected failure for test %q", test.name) 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /testdata/ipset_list_result: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishvananda/netlink/17daef607c6442d47b0565343cf8a69f985a4cb7/testdata/ipset_list_result -------------------------------------------------------------------------------- /testdata/ipset_protocol_result: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /unix_diag.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | // According to linux/include/uapi/linux/unix_diag.h 4 | const ( 5 | UNIX_DIAG_NAME = iota 6 | UNIX_DIAG_VFS 7 | UNIX_DIAG_PEER 8 | UNIX_DIAG_ICONS 9 | UNIX_DIAG_RQLEN 10 | UNIX_DIAG_MEMINFO 11 | UNIX_DIAG_SHUTDOWN 12 | UNIX_DIAG_UID 13 | UNIX_DIAG_MAX 14 | ) 15 | 16 | type UnixDiagInfoResp struct { 17 | DiagMsg *UnixSocket 18 | Name *string 19 | Peer *uint32 20 | Queue *QueueInfo 21 | Shutdown *uint8 22 | } 23 | 24 | type QueueInfo struct { 25 | RQueue uint32 26 | WQueue uint32 27 | } 28 | -------------------------------------------------------------------------------- /xdp_diag.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import "github.com/vishvananda/netlink/nl" 4 | 5 | const SOCK_ANY_COOKIE = uint64(nl.TCPDIAG_NOCOOKIE)<<32 + uint64(nl.TCPDIAG_NOCOOKIE) 6 | 7 | // XDP diagnosis show flag constants to request particular information elements. 8 | const ( 9 | XDP_SHOW_INFO = 1 << iota 10 | XDP_SHOW_RING_CFG 11 | XDP_SHOW_UMEM 12 | XDP_SHOW_MEMINFO 13 | XDP_SHOW_STATS 14 | ) 15 | 16 | // XDP diag element constants 17 | const ( 18 | XDP_DIAG_NONE = iota 19 | XDP_DIAG_INFO // when using XDP_SHOW_INFO 20 | XDP_DIAG_UID // when using XDP_SHOW_INFO 21 | XDP_DIAG_RX_RING // when using XDP_SHOW_RING_CFG 22 | XDP_DIAG_TX_RING // when using XDP_SHOW_RING_CFG 23 | XDP_DIAG_UMEM // when using XDP_SHOW_UMEM 24 | XDP_DIAG_UMEM_FILL_RING // when using XDP_SHOW_UMEM 25 | XDP_DIAG_UMEM_COMPLETION_RING // when using XDP_SHOW_UMEM 26 | XDP_DIAG_MEMINFO // when using XDP_SHOW_MEMINFO 27 | XDP_DIAG_STATS // when using XDP_SHOW_STATS 28 | ) 29 | 30 | // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21 31 | type XDPDiagInfoResp struct { 32 | XDPDiagMsg *XDPSocket 33 | XDPInfo *XDPInfo 34 | } 35 | -------------------------------------------------------------------------------- /xdp_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | xdrDiagUmemLen = 8 + 8*4 10 | xdrDiagStatsLen = 6 * 8 11 | ) 12 | 13 | func (x *XDPDiagUmem) deserialize(b []byte) error { 14 | if len(b) < xdrDiagUmemLen { 15 | return fmt.Errorf("XDP umem diagnosis data short read (%d); want %d", len(b), xdrDiagUmemLen) 16 | } 17 | 18 | rb := bytes.NewBuffer(b) 19 | x.Size = native.Uint64(rb.Next(8)) 20 | x.ID = native.Uint32(rb.Next(4)) 21 | x.NumPages = native.Uint32(rb.Next(4)) 22 | x.ChunkSize = native.Uint32(rb.Next(4)) 23 | x.Headroom = native.Uint32(rb.Next(4)) 24 | x.Ifindex = native.Uint32(rb.Next(4)) 25 | x.QueueID = native.Uint32(rb.Next(4)) 26 | x.Flags = native.Uint32(rb.Next(4)) 27 | x.Refs = native.Uint32(rb.Next(4)) 28 | 29 | return nil 30 | } 31 | 32 | func (x *XDPDiagStats) deserialize(b []byte) error { 33 | if len(b) < xdrDiagStatsLen { 34 | return fmt.Errorf("XDP diagnosis statistics short read (%d); want %d", len(b), xdrDiagStatsLen) 35 | } 36 | 37 | rb := bytes.NewBuffer(b) 38 | x.RxDropped = native.Uint64(rb.Next(8)) 39 | x.RxInvalid = native.Uint64(rb.Next(8)) 40 | x.RxFull = native.Uint64(rb.Next(8)) 41 | x.FillRingEmpty = native.Uint64(rb.Next(8)) 42 | x.TxInvalid = native.Uint64(rb.Next(8)) 43 | x.TxRingEmpty = native.Uint64(rb.Next(8)) 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /xfrm_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | // Proto is an enum representing an ipsec protocol. 10 | type Proto uint8 11 | 12 | const ( 13 | XFRM_PROTO_ROUTE2 Proto = unix.IPPROTO_ROUTING 14 | XFRM_PROTO_ESP Proto = unix.IPPROTO_ESP 15 | XFRM_PROTO_AH Proto = unix.IPPROTO_AH 16 | XFRM_PROTO_HAO Proto = unix.IPPROTO_DSTOPTS 17 | XFRM_PROTO_COMP Proto = unix.IPPROTO_COMP 18 | XFRM_PROTO_IPSEC_ANY Proto = unix.IPPROTO_RAW 19 | ) 20 | 21 | func (p Proto) String() string { 22 | switch p { 23 | case XFRM_PROTO_ROUTE2: 24 | return "route2" 25 | case XFRM_PROTO_ESP: 26 | return "esp" 27 | case XFRM_PROTO_AH: 28 | return "ah" 29 | case XFRM_PROTO_HAO: 30 | return "hao" 31 | case XFRM_PROTO_COMP: 32 | return "comp" 33 | case XFRM_PROTO_IPSEC_ANY: 34 | return "ipsec-any" 35 | } 36 | return fmt.Sprintf("%d", p) 37 | } 38 | 39 | // Mode is an enum representing an ipsec transport. 40 | type Mode uint8 41 | 42 | const ( 43 | XFRM_MODE_TRANSPORT Mode = iota 44 | XFRM_MODE_TUNNEL 45 | XFRM_MODE_ROUTEOPTIMIZATION 46 | XFRM_MODE_IN_TRIGGER 47 | XFRM_MODE_BEET 48 | XFRM_MODE_MAX 49 | ) 50 | 51 | // SADir is an enum representing an ipsec template direction. 52 | type SADir uint8 53 | 54 | const ( 55 | XFRM_SA_DIR_IN SADir = iota + 1 56 | XFRM_SA_DIR_OUT 57 | ) 58 | 59 | func (m Mode) String() string { 60 | switch m { 61 | case XFRM_MODE_TRANSPORT: 62 | return "transport" 63 | case XFRM_MODE_TUNNEL: 64 | return "tunnel" 65 | case XFRM_MODE_ROUTEOPTIMIZATION: 66 | return "ro" 67 | case XFRM_MODE_IN_TRIGGER: 68 | return "in_trigger" 69 | case XFRM_MODE_BEET: 70 | return "beet" 71 | } 72 | return fmt.Sprintf("%d", m) 73 | } 74 | 75 | // XfrmMark represents the mark associated to the state or policy 76 | type XfrmMark struct { 77 | Value uint32 78 | Mask uint32 79 | } 80 | 81 | func (m *XfrmMark) String() string { 82 | return fmt.Sprintf("(0x%x,0x%x)", m.Value, m.Mask) 83 | } 84 | -------------------------------------------------------------------------------- /xfrm_monitor_linux.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vishvananda/netlink/nl" 7 | "github.com/vishvananda/netns" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | type XfrmMsg interface { 12 | Type() nl.XfrmMsgType 13 | } 14 | 15 | type XfrmMsgExpire struct { 16 | XfrmState *XfrmState 17 | Hard bool 18 | } 19 | 20 | func (ue *XfrmMsgExpire) Type() nl.XfrmMsgType { 21 | return nl.XFRM_MSG_EXPIRE 22 | } 23 | 24 | func parseXfrmMsgExpire(b []byte) *XfrmMsgExpire { 25 | var e XfrmMsgExpire 26 | 27 | msg := nl.DeserializeXfrmUserExpire(b) 28 | e.XfrmState = xfrmStateFromXfrmUsersaInfo(&msg.XfrmUsersaInfo) 29 | e.Hard = msg.Hard == 1 30 | 31 | return &e 32 | } 33 | 34 | func XfrmMonitor(ch chan<- XfrmMsg, done <-chan struct{}, errorChan chan<- error, 35 | types ...nl.XfrmMsgType) error { 36 | 37 | groups, err := xfrmMcastGroups(types) 38 | if err != nil { 39 | return nil 40 | } 41 | s, err := nl.SubscribeAt(netns.None(), netns.None(), unix.NETLINK_XFRM, groups...) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | if done != nil { 47 | go func() { 48 | <-done 49 | s.Close() 50 | }() 51 | 52 | } 53 | 54 | go func() { 55 | defer close(ch) 56 | for { 57 | msgs, from, err := s.Receive() 58 | if err != nil { 59 | errorChan <- err 60 | return 61 | } 62 | if from.Pid != nl.PidKernel { 63 | errorChan <- fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) 64 | return 65 | } 66 | for _, m := range msgs { 67 | switch m.Header.Type { 68 | case nl.XFRM_MSG_EXPIRE: 69 | ch <- parseXfrmMsgExpire(m.Data) 70 | default: 71 | errorChan <- fmt.Errorf("unsupported msg type: %x", m.Header.Type) 72 | } 73 | } 74 | } 75 | }() 76 | 77 | return nil 78 | } 79 | 80 | func xfrmMcastGroups(types []nl.XfrmMsgType) ([]uint, error) { 81 | groups := make([]uint, 0) 82 | 83 | if len(types) == 0 { 84 | return nil, fmt.Errorf("no xfrm msg type specified") 85 | } 86 | 87 | for _, t := range types { 88 | var group uint 89 | 90 | switch t { 91 | case nl.XFRM_MSG_EXPIRE: 92 | group = nl.XFRMNLGRP_EXPIRE 93 | default: 94 | return nil, fmt.Errorf("unsupported group: %x", t) 95 | } 96 | 97 | groups = append(groups, group) 98 | } 99 | 100 | return groups, nil 101 | } 102 | -------------------------------------------------------------------------------- /xfrm_monitor_linux_test.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/vishvananda/netlink/nl" 8 | ) 9 | 10 | func TestXfrmMonitorExpire(t *testing.T) { 11 | if os.Getenv("CI") == "true" { 12 | t.Skipf("Flaky in CI: Intermittently causes 10 minute timeout") 13 | } 14 | defer setUpNetlinkTest(t)() 15 | 16 | ch := make(chan XfrmMsg) 17 | done := make(chan struct{}) 18 | defer close(done) 19 | errChan := make(chan error) 20 | if err := XfrmMonitor(ch, nil, errChan, nl.XFRM_MSG_EXPIRE); err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | // Program state with limits 25 | state := getBaseState() 26 | state.Limits.TimeHard = 2 27 | state.Limits.TimeSoft = 1 28 | if err := XfrmStateAdd(state); err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | hardFound := false 33 | softFound := false 34 | 35 | msg := (<-ch).(*XfrmMsgExpire) 36 | if msg.XfrmState.Spi != state.Spi { 37 | t.Fatal("Received unexpected msg, spi does not match") 38 | } 39 | hardFound = msg.Hard || hardFound 40 | softFound = !msg.Hard || softFound 41 | 42 | msg = (<-ch).(*XfrmMsgExpire) 43 | if msg.XfrmState.Spi != state.Spi { 44 | t.Fatal("Received unexpected msg, spi does not match") 45 | } 46 | hardFound = msg.Hard || hardFound 47 | softFound = !msg.Hard || softFound 48 | 49 | if !hardFound || !softFound { 50 | t.Fatal("Missing expire msg: hard found:", hardFound, "soft found:", softFound) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /xfrm_unspecified.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package netlink 5 | 6 | type XfrmPolicy struct{} 7 | type XfrmState struct{} 8 | --------------------------------------------------------------------------------