pax_global_header00006660000000000000000000000064146621627700014525gustar00rootroot0000000000000052 comment=6f5713947556a0288c5cb71f036f9e91924ebcaa netlink-1.3.0/000077500000000000000000000000001466216277000131725ustar00rootroot00000000000000netlink-1.3.0/.github/000077500000000000000000000000001466216277000145325ustar00rootroot00000000000000netlink-1.3.0/.github/scripts/000077500000000000000000000000001466216277000162215ustar00rootroot00000000000000netlink-1.3.0/.github/scripts/modprobe.sh000077500000000000000000000003661466216277000203740ustar00rootroot00000000000000#!/usr/bin/env bash sudo modprobe ip_gre sudo modprobe nf_conntrack sudo modprobe nf_conntrack_netlink # these modules not available # sudo modprobe nf_conntrack_ipv4 # sudo modprobe nf_conntrack_ipv6 sudo modprobe sch_hfsc sudo modprobe sch_sfq netlink-1.3.0/.github/workflows/000077500000000000000000000000001466216277000165675ustar00rootroot00000000000000netlink-1.3.0/.github/workflows/main.yml000066400000000000000000000015011466216277000202330ustar00rootroot00000000000000name: Main on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17 - name: Kernel Modules run: ./.github/scripts/modprobe.sh shell: bash - name: Test run: sudo -E env PATH=$PATH go test -v ./ ./nl build-macos: # netlink is Linux-only, but this ensures that netlink builds without error # on macOS, which helps catch missing build tags. runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17 - name: Build run: go build ./... - name: Test run: go test ./... netlink-1.3.0/.gitignore000066400000000000000000000000201466216277000151520ustar00rootroot00000000000000.idea/ .vscode/ netlink-1.3.0/CHANGELOG.md000066400000000000000000000000731466216277000150030ustar00rootroot00000000000000# Changelog ## 1.0.0 (2018-03-15) Initial release taggingnetlink-1.3.0/LICENSE000066400000000000000000000250541466216277000142050ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2014 Vishvananda Ishaya. Copyright 2014 Docker, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. netlink-1.3.0/Makefile000066400000000000000000000013661466216277000146400ustar00rootroot00000000000000DIRS := \ . \ nl DEPS = \ github.com/vishvananda/netns \ golang.org/x/sys/unix uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) testdirs = $(call uniq,$(foreach d,$(1),$(dir $(wildcard $(d)/*_test.go)))) goroot = $(addprefix ../../../,$(1)) unroot = $(subst ../../../,,$(1)) fmt = $(addprefix fmt-,$(1)) all: test $(call goroot,$(DEPS)): go get $(call unroot,$@) .PHONY: $(call testdirs,$(DIRS)) $(call testdirs,$(DIRS)): go test -test.exec sudo -test.parallel 4 -timeout 60s -test.v github.com/vishvananda/netlink/$@ $(call fmt,$(call testdirs,$(DIRS))): ! gofmt -l $(subst fmt-,,$@)/*.go | grep -q . .PHONY: fmt fmt: $(call fmt,$(call testdirs,$(DIRS))) test: fmt $(call goroot,$(DEPS)) $(call testdirs,$(DIRS)) netlink-1.3.0/README.md000066400000000000000000000057141466216277000144600ustar00rootroot00000000000000# netlink - netlink library for go # ![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) The netlink package provides a simple netlink library for go. Netlink is the interface a user-space program in linux uses to communicate with the kernel. It can be used to add and remove interfaces, set ip addresses and routes, and configure ipsec. Netlink communication requires elevated privileges, so in most cases this code needs to be run as root. Since low-level netlink messages are inscrutable at best, the library attempts to provide an api that is loosely modeled on the CLI provided by iproute2. Actions like `ip link add` will be accomplished via a similarly named function like AddLink(). This library began its life as a fork of the netlink functionality in [docker/libcontainer](https://github.com/docker/libcontainer) but was heavily rewritten to improve testability, performance, and to add new functionality like ipsec xfrm handling. ## Local Build and Test ## You can use go get command: go get github.com/vishvananda/netlink Testing dependencies: go get github.com/vishvananda/netns Testing (requires root): sudo -E go test github.com/vishvananda/netlink ## Examples ## Add a new bridge and add eth1 into it: ```go package main import ( "fmt" "github.com/vishvananda/netlink" ) func main() { la := netlink.NewLinkAttrs() la.Name = "foo" mybridge := &netlink.Bridge{LinkAttrs: la} err := netlink.LinkAdd(mybridge) if err != nil { fmt.Printf("could not add %s: %v\n", la.Name, err) } eth1, _ := netlink.LinkByName("eth1") netlink.LinkSetMaster(eth1, mybridge) } ``` Note `NewLinkAttrs` constructor, it sets default values in structure. For now it sets only `TxQLen` to `-1`, so kernel will set default by itself. If you're using simple initialization(`LinkAttrs{Name: "foo"}`) `TxQLen` will be set to `0` unless you specify it like `LinkAttrs{Name: "foo", TxQLen: 1000}`. Add a new ip address to loopback: ```go package main import ( "github.com/vishvananda/netlink" ) func main() { lo, _ := netlink.LinkByName("lo") addr, _ := netlink.ParseAddr("169.254.169.254/32") netlink.AddrAdd(lo, addr) } ``` ## Future Work ## Many pieces of netlink are not yet fully supported in the high-level interface. Aspects of virtually all of the high-level objects don't exist. Many of the underlying primitives are there, so its a matter of putting the right fields into the high-level objects and making sure that they are serialized and deserialized correctly in the Add and List methods. There are also a few pieces of low level netlink functionality that still need to be implemented. Routing rules are not in place and some of the more advanced link types. Hopefully there is decent structure and testing in place to make these fairly straightforward to add. netlink-1.3.0/addr.go000066400000000000000000000024341466216277000144360ustar00rootroot00000000000000package netlink import ( "fmt" "net" "strings" ) // Addr represents an IP address from netlink. Netlink ip addresses // include a mask, so it stores the address as a net.IPNet. type Addr struct { *net.IPNet Label string Flags int Scope int Peer *net.IPNet Broadcast net.IP PreferedLft int ValidLft int LinkIndex int } // String returns $ip/$netmask $label func (a Addr) String() string { return strings.TrimSpace(fmt.Sprintf("%s %s", a.IPNet, a.Label)) } // ParseAddr parses the string representation of an address in the // form $ip/$netmask $label. The label portion is optional func ParseAddr(s string) (*Addr, error) { label := "" parts := strings.Split(s, " ") if len(parts) > 1 { s = parts[0] label = parts[1] } m, err := ParseIPNet(s) if err != nil { return nil, err } return &Addr{IPNet: m, Label: label}, nil } // Equal returns true if both Addrs have the same net.IPNet value. func (a Addr) Equal(x Addr) bool { sizea, _ := a.Mask.Size() sizeb, _ := x.Mask.Size() // ignore label for comparison return a.IP.Equal(x.IP) && sizea == sizeb } func (a Addr) PeerEqual(x Addr) bool { sizea, _ := a.Peer.Mask.Size() sizeb, _ := x.Peer.Mask.Size() // ignore label for comparison return a.Peer.IP.Equal(x.Peer.IP) && sizea == sizeb } netlink-1.3.0/addr_linux.go000066400000000000000000000273261466216277000156640ustar00rootroot00000000000000package netlink import ( "fmt" "net" "strings" "syscall" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) // AddrAdd will add an IP address to a link device. // // Equivalent to: `ip addr add $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func AddrAdd(link Link, addr *Addr) error { return pkgHandle.AddrAdd(link, addr) } // AddrAdd will add an IP address to a link device. // // Equivalent to: `ip addr add $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func (h *Handle) AddrAdd(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) return h.addrHandle(link, addr, req) } // AddrReplace will replace (or, if not present, add) an IP address on a link device. // // Equivalent to: `ip addr replace $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func AddrReplace(link Link, addr *Addr) error { return pkgHandle.AddrReplace(link, addr) } // AddrReplace will replace (or, if not present, add) an IP address on a link device. // // Equivalent to: `ip addr replace $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func (h *Handle) AddrReplace(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_NEWADDR, unix.NLM_F_CREATE|unix.NLM_F_REPLACE|unix.NLM_F_ACK) return h.addrHandle(link, addr, req) } // AddrDel will delete an IP address from a link device. // // Equivalent to: `ip addr del $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func AddrDel(link Link, addr *Addr) error { return pkgHandle.AddrDel(link, addr) } // AddrDel will delete an IP address from a link device. // Equivalent to: `ip addr del $addr dev $link` // // If `addr` is an IPv4 address and the broadcast address is not given, it // will be automatically computed based on the IP mask if /30 or larger. func (h *Handle) AddrDel(link Link, addr *Addr) error { req := h.newNetlinkRequest(unix.RTM_DELADDR, unix.NLM_F_ACK) return h.addrHandle(link, addr, req) } func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error { family := nl.GetIPFamily(addr.IP) msg := nl.NewIfAddrmsg(family) msg.Scope = uint8(addr.Scope) if link == nil { msg.Index = uint32(addr.LinkIndex) } else { base := link.Attrs() if addr.Label != "" && !strings.HasPrefix(addr.Label, base.Name) { return fmt.Errorf("label must begin with interface name") } h.ensureIndex(base) msg.Index = uint32(base.Index) } mask := addr.Mask if addr.Peer != nil { mask = addr.Peer.Mask } prefixlen, masklen := mask.Size() msg.Prefixlen = uint8(prefixlen) req.AddData(msg) var localAddrData []byte if family == FAMILY_V4 { localAddrData = addr.IP.To4() } else { localAddrData = addr.IP.To16() } localData := nl.NewRtAttr(unix.IFA_LOCAL, localAddrData) req.AddData(localData) var peerAddrData []byte if addr.Peer != nil { if family == FAMILY_V4 { peerAddrData = addr.Peer.IP.To4() } else { peerAddrData = addr.Peer.IP.To16() } } else { peerAddrData = localAddrData } addressData := nl.NewRtAttr(unix.IFA_ADDRESS, peerAddrData) req.AddData(addressData) if addr.Flags != 0 { if addr.Flags <= 0xff { msg.IfAddrmsg.Flags = uint8(addr.Flags) } else { b := make([]byte, 4) native.PutUint32(b, uint32(addr.Flags)) flagsData := nl.NewRtAttr(unix.IFA_FLAGS, b) req.AddData(flagsData) } } if family == FAMILY_V4 { // Automatically set the broadcast address if it is unset and the // subnet is large enough to sensibly have one (/30 or larger). // See: RFC 3021 if addr.Broadcast == nil && prefixlen < 31 { calcBroadcast := make(net.IP, masklen/8) for i := range localAddrData { calcBroadcast[i] = localAddrData[i] | ^mask[i] } addr.Broadcast = calcBroadcast } if addr.Broadcast != nil { req.AddData(nl.NewRtAttr(unix.IFA_BROADCAST, addr.Broadcast)) } if addr.Label != "" { labelData := nl.NewRtAttr(unix.IFA_LABEL, nl.ZeroTerminated(addr.Label)) req.AddData(labelData) } } // 0 is the default value for these attributes. However, 0 means "expired", while the least-surprising default // value should be "forever". To compensate for that, only add the attributes if at least one of the values is // non-zero, which means the caller has explicitly set them if addr.ValidLft > 0 || addr.PreferedLft > 0 { cachedata := nl.IfaCacheInfo{unix.IfaCacheinfo{ Valid: uint32(addr.ValidLft), Prefered: uint32(addr.PreferedLft), }} req.AddData(nl.NewRtAttr(unix.IFA_CACHEINFO, cachedata.Serialize())) } _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // AddrList gets a list of IP addresses in the system. // Equivalent to: `ip addr show`. // The list can be filtered by link and ip family. func AddrList(link Link, family int) ([]Addr, error) { return pkgHandle.AddrList(link, family) } // AddrList gets a list of IP addresses in the system. // Equivalent to: `ip addr show`. // The list can be filtered by link and ip family. func (h *Handle) AddrList(link Link, family int) ([]Addr, error) { req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP) msg := nl.NewIfAddrmsg(family) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR) if err != nil { return nil, err } indexFilter := 0 if link != nil { base := link.Attrs() h.ensureIndex(base) indexFilter = base.Index } var res []Addr for _, m := range msgs { addr, msgFamily, err := parseAddr(m) if err != nil { return res, err } if link != nil && addr.LinkIndex != indexFilter { // Ignore messages from other interfaces continue } if family != FAMILY_ALL && msgFamily != family { continue } res = append(res, addr) } return res, nil } func parseAddr(m []byte) (addr Addr, family int, err error) { msg := nl.DeserializeIfAddrmsg(m) family = -1 addr.LinkIndex = -1 attrs, err1 := nl.ParseRouteAttr(m[msg.Len():]) if err1 != nil { err = err1 return } family = int(msg.Family) addr.LinkIndex = int(msg.Index) var local, dst *net.IPNet for _, attr := range attrs { switch attr.Attr.Type { case unix.IFA_ADDRESS: dst = &net.IPNet{ IP: attr.Value, Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)), } case unix.IFA_LOCAL: // iproute2 manual: // If a peer address is specified, the local address // cannot have a prefix length. The network prefix is // associated with the peer rather than with the local // address. n := 8 * len(attr.Value) local = &net.IPNet{ IP: attr.Value, Mask: net.CIDRMask(n, n), } case unix.IFA_BROADCAST: addr.Broadcast = attr.Value case unix.IFA_LABEL: addr.Label = string(attr.Value[:len(attr.Value)-1]) case unix.IFA_FLAGS: addr.Flags = int(native.Uint32(attr.Value[0:4])) case unix.IFA_CACHEINFO: ci := nl.DeserializeIfaCacheInfo(attr.Value) addr.PreferedLft = int(ci.Prefered) addr.ValidLft = int(ci.Valid) } } // libnl addr.c comment: // IPv6 sends the local address as IFA_ADDRESS with no // IFA_LOCAL, IPv4 sends both IFA_LOCAL and IFA_ADDRESS // with IFA_ADDRESS being the peer address if they differ // // But obviously, as there are IPv6 PtP addresses, too, // IFA_LOCAL should also be handled for IPv6. if local != nil { if family == FAMILY_V4 && dst != nil && local.IP.Equal(dst.IP) { addr.IPNet = dst } else { addr.IPNet = local addr.Peer = dst } } else { addr.IPNet = dst } addr.Scope = int(msg.Scope) return } type AddrUpdate struct { LinkAddress net.IPNet LinkIndex int Flags int Scope int PreferedLft int ValidLft int NewAddr bool // true=added false=deleted } // AddrSubscribe takes a chan down which notifications will be sent // when addresses change. Close the 'done' chan to stop subscription. func AddrSubscribe(ch chan<- AddrUpdate, done <-chan struct{}) error { return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false) } // AddrSubscribeAt works like AddrSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func AddrSubscribeAt(ns netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error { return addrSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false) } // AddrSubscribeOptions contains a set of options to use with // AddrSubscribeWithOptions. type AddrSubscribeOptions struct { Namespace *netns.NsHandle ErrorCallback func(error) ListExisting bool ReceiveBufferSize int ReceiveBufferForceSize bool ReceiveTimeout *unix.Timeval } // AddrSubscribeWithOptions work like AddrSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, options AddrSubscribeOptions) error { if options.Namespace == nil { none := netns.None() options.Namespace = &none } return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize) } func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int, rcvTimeout *unix.Timeval, rcvBufForce bool) error { s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_IFADDR, unix.RTNLGRP_IPV6_IFADDR) if err != nil { return err } if rcvTimeout != nil { if err := s.SetReceiveTimeout(rcvTimeout); err != nil { return err } } if rcvbuf != 0 { err = s.SetReceiveBufferSize(rcvbuf, rcvBufForce) if err != nil { return err } } if done != nil { go func() { <-done s.Close() }() } if listExisting { req := pkgHandle.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP) infmsg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(infmsg) if err := s.Send(req); err != nil { return err } } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { if cberr != nil { cberr(fmt.Errorf("Receive failed: %v", err)) } return } if from.Pid != nl.PidKernel { if cberr != nil { cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)) } continue } for _, m := range msgs { if m.Header.Type == unix.NLMSG_DONE { continue } if m.Header.Type == unix.NLMSG_ERROR { error := int32(native.Uint32(m.Data[0:4])) if error == 0 { continue } if cberr != nil { cberr(fmt.Errorf("error message: %v", syscall.Errno(-error))) } continue } msgType := m.Header.Type if msgType != unix.RTM_NEWADDR && msgType != unix.RTM_DELADDR { if cberr != nil { cberr(fmt.Errorf("bad message type: %d", msgType)) } continue } addr, _, err := parseAddr(m.Data) if err != nil { if cberr != nil { cberr(fmt.Errorf("could not parse address: %v", err)) } continue } ch <- AddrUpdate{LinkAddress: *addr.IPNet, LinkIndex: addr.LinkIndex, NewAddr: msgType == unix.RTM_NEWADDR, Flags: addr.Flags, Scope: addr.Scope, PreferedLft: addr.PreferedLft, ValidLft: addr.ValidLft} } } }() return nil } netlink-1.3.0/addr_test.go000066400000000000000000000154071466216277000155010ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "net" "os" "testing" "time" "golang.org/x/sys/unix" ) func TestAddrAdd(t *testing.T) { DoTestAddr(t, AddrAdd) } func TestAddrReplace(t *testing.T) { DoTestAddr(t, AddrReplace) } func DoTestAddr(t *testing.T, FunctionUndertest func(Link, *Addr) error) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI with: addr_test.go:*: Address flags not set properly, got=128, expected=132") } // TODO: IFA_F_PERMANENT does not seem to be set by default on older kernels? // TODO: IFA_F_OPTIMISTIC failing in CI. should we just skip that one check? var address = &net.IPNet{IP: net.IPv4(127, 0, 0, 2), Mask: net.CIDRMask(32, 32)} var peer = &net.IPNet{IP: net.IPv4(127, 0, 0, 3), Mask: net.CIDRMask(24, 32)} var addrTests = []struct { addr *Addr expected *Addr }{ { &Addr{IPNet: address}, &Addr{IPNet: address, Label: "lo", Scope: unix.RT_SCOPE_UNIVERSE, Flags: unix.IFA_F_PERMANENT}, }, { &Addr{IPNet: address, Label: "local"}, &Addr{IPNet: address, Label: "local", Scope: unix.RT_SCOPE_UNIVERSE, Flags: unix.IFA_F_PERMANENT}, }, { &Addr{IPNet: address, Flags: unix.IFA_F_OPTIMISTIC}, &Addr{IPNet: address, Label: "lo", Flags: unix.IFA_F_OPTIMISTIC | unix.IFA_F_PERMANENT, Scope: unix.RT_SCOPE_UNIVERSE}, }, { &Addr{IPNet: address, Flags: unix.IFA_F_OPTIMISTIC | unix.IFA_F_DADFAILED}, &Addr{IPNet: address, Label: "lo", Flags: unix.IFA_F_OPTIMISTIC | unix.IFA_F_DADFAILED | unix.IFA_F_PERMANENT, Scope: unix.RT_SCOPE_UNIVERSE}, }, { &Addr{IPNet: address, Scope: unix.RT_SCOPE_NOWHERE}, &Addr{IPNet: address, Label: "lo", Flags: unix.IFA_F_PERMANENT, Scope: unix.RT_SCOPE_NOWHERE}, }, { &Addr{IPNet: address, Peer: peer}, &Addr{IPNet: address, Peer: peer, Label: "lo", Scope: unix.RT_SCOPE_UNIVERSE, Flags: unix.IFA_F_PERMANENT}, }, } tearDown := setUpNetlinkTest(t) defer tearDown() link, err := LinkByName("lo") if err != nil { t.Fatal(err) } for _, tt := range addrTests { if err = FunctionUndertest(link, tt.addr); err != nil { t.Fatal(err) } addrs, err := AddrList(link, FAMILY_ALL) if err != nil { t.Fatal(err) } if len(addrs) != 1 { t.Fatal("Address not added properly") } if !addrs[0].Equal(*tt.expected) { t.Fatalf("Address ip no set properly, got=%s, expected=%s", addrs[0], tt.expected) } if addrs[0].Label != tt.expected.Label { t.Fatalf("Address label not set properly, got=%s, expected=%s", addrs[0].Label, tt.expected.Label) } if addrs[0].Flags != tt.expected.Flags { t.Fatalf("Address flags not set properly, got=%d, expected=%d", addrs[0].Flags, tt.expected.Flags) } if addrs[0].Scope != tt.expected.Scope { t.Fatalf("Address scope not set properly, got=%d, expected=%d", addrs[0].Scope, tt.expected.Scope) } if ifindex := link.Attrs().Index; ifindex != addrs[0].LinkIndex { t.Fatalf("Address ifindex not set properly, got=%d, expected=%d", addrs[0].LinkIndex, ifindex) } if tt.expected.Peer != nil { if !addrs[0].PeerEqual(*tt.expected) { t.Fatalf("Peer Address ip no set properly, got=%s, expected=%s", addrs[0].Peer, tt.expected.Peer) } } // Pass FAMILY_V4, we should get the same results as FAMILY_ALL addrs, err = AddrList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(addrs) != 1 { t.Fatal("Address not added properly") } // Pass a wrong family number, we should get nil list addrs, err = AddrList(link, 0x8) if err != nil { t.Fatal(err) } if len(addrs) != 0 { t.Fatal("Address not expected") } if err = AddrDel(link, tt.addr); err != nil { t.Fatal(err) } addrs, err = AddrList(link, FAMILY_ALL) if err != nil { t.Fatal(err) } if len(addrs) != 0 { t.Fatal("Address not removed properly") } } } func TestAddrAddReplace(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() for _, nilLink := range []bool{false, true} { var address = &net.IPNet{IP: net.IPv4(127, 0, 0, 2), Mask: net.CIDRMask(24, 32)} var addr = &Addr{IPNet: address} link, err := LinkByName("lo") if err != nil { t.Fatal(err) } if nilLink { addr.LinkIndex = link.Attrs().Index link = nil } err = AddrAdd(link, addr) if err != nil { t.Fatal(err) } addrs, err := AddrList(link, FAMILY_ALL) if err != nil { t.Fatal(err) } if len(addrs) != 1 { t.Fatal("Address not added properly") } err = AddrAdd(link, addr) if err == nil { t.Fatal("Re-adding address should fail (but succeeded unexpectedly).") } err = AddrReplace(link, addr) if err != nil { t.Fatal("Replacing address failed.") } addrs, err = AddrList(link, FAMILY_ALL) if err != nil { t.Fatal(err) } if len(addrs) != 1 { t.Fatal("Address not added properly") } if err = AddrDel(link, addr); err != nil { t.Fatal(err) } addrs, err = AddrList(link, FAMILY_ALL) if err != nil { t.Fatal(err) } if len(addrs) != 0 { t.Fatal("Address not removed properly") } } } func expectAddrUpdate(ch <-chan AddrUpdate, add bool, dst net.IP) bool { for { timeout := time.After(time.Minute) select { case update := <-ch: if update.NewAddr == add && update.LinkAddress.IP.Equal(dst) { return true } case <-timeout: return false } } } func TestAddrSubscribeWithOptions(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan AddrUpdate) done := make(chan struct{}) defer close(done) var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := AddrSubscribeWithOptions(ch, done, AddrSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, }); err != nil { t.Fatal(err) } // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } ip := net.IPv4(127, 0, 0, 1) if !expectAddrUpdate(ch, true, ip) { t.Fatal("Add update not received as expected") } } func TestAddrSubscribeListExisting(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan AddrUpdate) done := make(chan struct{}) defer close(done) // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := AddrSubscribeWithOptions(ch, done, AddrSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, ListExisting: true, }); err != nil { t.Fatal(err) } ip := net.IPv4(127, 0, 0, 1) if !expectAddrUpdate(ch, true, ip) { t.Fatal("Add update not received as expected") } } netlink-1.3.0/bpf_linux.go000066400000000000000000000033131466216277000155070ustar00rootroot00000000000000package netlink import ( "unsafe" "golang.org/x/sys/unix" ) type BpfProgType uint32 const ( BPF_PROG_TYPE_UNSPEC BpfProgType = iota BPF_PROG_TYPE_SOCKET_FILTER BPF_PROG_TYPE_KPROBE BPF_PROG_TYPE_SCHED_CLS BPF_PROG_TYPE_SCHED_ACT BPF_PROG_TYPE_TRACEPOINT BPF_PROG_TYPE_XDP BPF_PROG_TYPE_PERF_EVENT BPF_PROG_TYPE_CGROUP_SKB BPF_PROG_TYPE_CGROUP_SOCK BPF_PROG_TYPE_LWT_IN BPF_PROG_TYPE_LWT_OUT BPF_PROG_TYPE_LWT_XMIT BPF_PROG_TYPE_SOCK_OPS BPF_PROG_TYPE_SK_SKB BPF_PROG_TYPE_CGROUP_DEVICE BPF_PROG_TYPE_SK_MSG BPF_PROG_TYPE_RAW_TRACEPOINT BPF_PROG_TYPE_CGROUP_SOCK_ADDR BPF_PROG_TYPE_LWT_SEG6LOCAL BPF_PROG_TYPE_LIRC_MODE2 BPF_PROG_TYPE_SK_REUSEPORT BPF_PROG_TYPE_FLOW_DISSECTOR BPF_PROG_TYPE_CGROUP_SYSCTL BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE BPF_PROG_TYPE_CGROUP_SOCKOPT BPF_PROG_TYPE_TRACING BPF_PROG_TYPE_STRUCT_OPS BPF_PROG_TYPE_EXT BPF_PROG_TYPE_LSM BPF_PROG_TYPE_SK_LOOKUP ) type BPFAttr struct { ProgType uint32 InsnCnt uint32 Insns uintptr License uintptr LogLevel uint32 LogSize uint32 LogBuf uintptr KernVersion uint32 } // loadSimpleBpf loads a trivial bpf program for testing purposes. func loadSimpleBpf(progType BpfProgType, ret uint32) (int, error) { insns := []uint64{ 0x00000000000000b7 | (uint64(ret) << 32), 0x0000000000000095, } license := []byte{'A', 'S', 'L', '2', '\x00'} attr := BPFAttr{ ProgType: uint32(progType), InsnCnt: uint32(len(insns)), Insns: uintptr(unsafe.Pointer(&insns[0])), License: uintptr(unsafe.Pointer(&license[0])), } fd, _, errno := unix.Syscall(unix.SYS_BPF, 5, /* bpf cmd */ uintptr(unsafe.Pointer(&attr)), unsafe.Sizeof(attr)) if errno != 0 { return 0, errno } return int(fd), nil } netlink-1.3.0/bridge_linux.go000066400000000000000000000123751466216277000162040ustar00rootroot00000000000000package netlink import ( "fmt" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // BridgeVlanList gets a map of device id to bridge vlan infos. // Equivalent to: `bridge vlan show` func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { return pkgHandle.BridgeVlanList() } // BridgeVlanList gets a map of device id to bridge vlan infos. // Equivalent to: `bridge vlan show` func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) req.AddData(msg) req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN)))) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) if err != nil { return nil, err } ret := make(map[int32][]*nl.BridgeVlanInfo) for _, m := range msgs { msg := nl.DeserializeIfInfomsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } for _, attr := range attrs { switch attr.Attr.Type { case unix.IFLA_AF_SPEC: //nested attr nestAttrs, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, fmt.Errorf("failed to parse nested attr %v", err) } for _, nestAttr := range nestAttrs { switch nestAttr.Attr.Type { case nl.IFLA_BRIDGE_VLAN_INFO: vlanInfo := nl.DeserializeBridgeVlanInfo(nestAttr.Value) ret[msg.Index] = append(ret[msg.Index], vlanInfo) } } } } } return ret, nil } // BridgeVlanAdd adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error { return pkgHandle.BridgeVlanAdd(link, vid, pvid, untagged, self, master) } // BridgeVlanAdd adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error { return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, 0, pvid, untagged, self, master) } // BridgeVlanAddRange adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func BridgeVlanAddRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { return pkgHandle.BridgeVlanAddRange(link, vid, vidEnd, pvid, untagged, self, master) } // BridgeVlanAddRange adds a new vlan filter entry // Equivalent to: `bridge vlan add dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanAddRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { return h.bridgeVlanModify(unix.RTM_SETLINK, link, vid, vidEnd, pvid, untagged, self, master) } // BridgeVlanDel adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error { return pkgHandle.BridgeVlanDel(link, vid, pvid, untagged, self, master) } // BridgeVlanDel adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error { return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, 0, pvid, untagged, self, master) } // BridgeVlanDelRange adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func BridgeVlanDelRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { return pkgHandle.BridgeVlanDelRange(link, vid, vidEnd, pvid, untagged, self, master) } // BridgeVlanDelRange adds a new vlan filter entry // Equivalent to: `bridge vlan del dev DEV vid VID-VIDEND [ pvid ] [ untagged ] [ self ] [ master ]` func (h *Handle) BridgeVlanDelRange(link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { return h.bridgeVlanModify(unix.RTM_DELLINK, link, vid, vidEnd, pvid, untagged, self, master) } func (h *Handle) bridgeVlanModify(cmd int, link Link, vid, vidEnd uint16, pvid, untagged, self, master bool) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(cmd, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) msg.Index = int32(base.Index) req.AddData(msg) br := nl.NewRtAttr(unix.IFLA_AF_SPEC, nil) var flags uint16 if self { flags |= nl.BRIDGE_FLAGS_SELF } if master { flags |= nl.BRIDGE_FLAGS_MASTER } if flags > 0 { br.AddRtAttr(nl.IFLA_BRIDGE_FLAGS, nl.Uint16Attr(flags)) } vlanInfo := &nl.BridgeVlanInfo{Vid: vid} if pvid { vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_PVID } if untagged { vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_UNTAGGED } if vidEnd != 0 { vlanEndInfo := &nl.BridgeVlanInfo{Vid: vidEnd} vlanEndInfo.Flags = vlanInfo.Flags vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_BEGIN br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) vlanEndInfo.Flags |= nl.BRIDGE_VLAN_INFO_RANGE_END br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanEndInfo.Serialize()) } else { br.AddRtAttr(nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize()) } req.AddData(br) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } netlink-1.3.0/bridge_linux_test.go000066400000000000000000000060601466216277000172350ustar00rootroot00000000000000package netlink import ( "fmt" "io/ioutil" "testing" ) func TestBridgeVlan(t *testing.T) { minKernelRequired(t, 3, 10) tearDown := setUpNetlinkTest(t) defer tearDown() if err := remountSysfs(); err != nil { t.Fatal(err) } bridgeName := "foo" bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} if err := LinkAdd(bridge); err != nil { t.Fatal(err) } if err := ioutil.WriteFile(fmt.Sprintf("/sys/devices/virtual/net/%s/bridge/vlan_filtering", bridgeName), []byte("1"), 0644); err != nil { t.Fatal(err) } if vlanMap, err := BridgeVlanList(); err != nil { t.Fatal(err) } else { if len(vlanMap) != 1 { t.Fatal() } if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok { t.Fatal("vlanMap should include foo port vlan info") } else { if len(vInfo) != 1 { t.Fatal() } else { if !vInfo[0].EngressUntag() || !vInfo[0].PortVID() || vInfo[0].Vid != 1 { t.Fatalf("bridge vlan show get wrong return %s", vInfo[0].String()) } } } } dummy := &Dummy{LinkAttrs: LinkAttrs{Name: "dum1"}} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } if err := LinkSetMaster(dummy, bridge); err != nil { t.Fatal(err) } if err := BridgeVlanAdd(dummy, 2, false, false, false, false); err != nil { t.Fatal(err) } if err := BridgeVlanAdd(dummy, 3, true, true, false, false); err != nil { t.Fatal(err) } if err := BridgeVlanAddRange(dummy, 4, 6, false, false, false, false); err != nil { t.Fatal(err) } if vlanMap, err := BridgeVlanList(); err != nil { t.Fatal(err) } else { if len(vlanMap) != 2 { t.Fatal() } if vInfo, ok := vlanMap[int32(bridge.Index)]; !ok { t.Fatal("vlanMap should include foo port vlan info") } else { if fmt.Sprintf("%v", vInfo) != "[{Flags:6 Vid:1}]" { t.Fatalf("unexpected result %v", vInfo) } } if vInfo, ok := vlanMap[int32(dummy.Index)]; !ok { t.Fatal("vlanMap should include dum1 port vlan info") } else { 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}]" { t.Fatalf("unexpected result %v", vInfo) } } } } func TestBridgeGroupFwdMask(t *testing.T) { minKernelRequired(t, 4, 15) //minimal release for per-port group_fwd_mask tearDown := setUpNetlinkTest(t) defer tearDown() if err := remountSysfs(); err != nil { t.Fatal(err) } bridgeName := "foo" var mask uint16 = 0xfff0 bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}, GroupFwdMask: &mask} if err := LinkAdd(bridge); err != nil { t.Fatal(err) } brlink, err := LinkByName(bridgeName) if err != nil { t.Fatal(err) } if *(brlink.(*Bridge).GroupFwdMask) != mask { t.Fatalf("created bridge has group_fwd_mask value %x, different from expected %x", *(brlink.(*Bridge).GroupFwdMask), mask) } dummyName := "dm1" dummy := &Dummy{LinkAttrs: LinkAttrs{Name: dummyName, MasterIndex: brlink.Attrs().Index}} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } dmLink, err := LinkByName(dummyName) if err != nil { t.Fatal(err) } if err = LinkSetBRSlaveGroupFwdMask(dmLink, mask); err != nil { t.Fatal(err) } } netlink-1.3.0/chain.go000066400000000000000000000005251466216277000146050ustar00rootroot00000000000000package netlink import ( "fmt" ) // Chain contains the attributes of a Chain type Chain struct { Parent uint32 Chain uint32 } func (c Chain) String() string { return fmt.Sprintf("{Parent: %d, Chain: %d}", c.Parent, c.Chain) } func NewChain(parent uint32, chain uint32) Chain { return Chain{ Parent: parent, Chain: chain, } } netlink-1.3.0/chain_linux.go000066400000000000000000000051471466216277000160310ustar00rootroot00000000000000package netlink import ( "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // ChainDel will delete a chain from the system. func ChainDel(link Link, chain Chain) error { // Equivalent to: `tc chain del $chain` return pkgHandle.ChainDel(link, chain) } // ChainDel will delete a chain from the system. // Equivalent to: `tc chain del $chain` func (h *Handle) ChainDel(link Link, chain Chain) error { return h.chainModify(unix.RTM_DELCHAIN, 0, link, chain) } // ChainAdd will add a chain to the system. // Equivalent to: `tc chain add` func ChainAdd(link Link, chain Chain) error { return pkgHandle.ChainAdd(link, chain) } // ChainAdd will add a chain to the system. // Equivalent to: `tc chain add` func (h *Handle) ChainAdd(link Link, chain Chain) error { return h.chainModify( unix.RTM_NEWCHAIN, unix.NLM_F_CREATE|unix.NLM_F_EXCL, link, chain) } func (h *Handle) chainModify(cmd, flags int, link Link, chain Chain) error { req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) index := int32(0) if link != nil { base := link.Attrs() h.ensureIndex(base) index = int32(base.Index) } msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: index, Parent: chain.Parent, } req.AddData(msg) req.AddData(nl.NewRtAttr(nl.TCA_CHAIN, nl.Uint32Attr(chain.Chain))) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // ChainList gets a list of chains in the system. // Equivalent to: `tc chain list`. // The list can be filtered by link. func ChainList(link Link, parent uint32) ([]Chain, error) { return pkgHandle.ChainList(link, parent) } // ChainList gets a list of chains in the system. // Equivalent to: `tc chain list`. // The list can be filtered by link. func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP) index := int32(0) if link != nil { base := link.Attrs() h.ensureIndex(base) index = int32(base.Index) } msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: index, Parent: parent, } req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN) if err != nil { return nil, err } var res []Chain for _, m := range msgs { msg := nl.DeserializeTcMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } // skip chains from other interfaces if link != nil && msg.Ifindex != index { continue } var chain Chain for _, attr := range attrs { switch attr.Attr.Type { case nl.TCA_CHAIN: chain.Chain = native.Uint32(attr.Value) chain.Parent = parent } } res = append(res, chain) } return res, nil } netlink-1.3.0/chain_test.go000066400000000000000000000027741466216277000156540ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "testing" ) func TestChainAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := &Ingress{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0xffff, 0), Parent: HANDLE_INGRESS, }, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } _, ok := qdiscs[0].(*Ingress) if !ok { t.Fatal("Qdisc is the wrong type") } chainVal := new(uint32) *chainVal = 20 chain := NewChain(HANDLE_INGRESS, *chainVal) err = ChainAdd(link, chain) if err != nil { t.Fatal(err) } chains, err := ChainList(link, HANDLE_INGRESS) if err != nil { t.Fatal(err) } if len(chains) != 1 { t.Fatal("Failed to add chain") } if chains[0].Chain != *chainVal { t.Fatal("Incorrect chain added") } if chains[0].Parent != HANDLE_INGRESS { t.Fatal("Incorrect chain parent") } if err := ChainDel(link, chain); err != nil { t.Fatal(err) } chains, err = ChainList(link, HANDLE_INGRESS) if err != nil { t.Fatal(err) } if len(chains) != 0 { t.Fatal("Failed to remove chain") } } netlink-1.3.0/class.go000066400000000000000000000155661466216277000146430ustar00rootroot00000000000000package netlink import ( "fmt" ) // Class interfaces for all classes type Class interface { Attrs() *ClassAttrs Type() string } // Generic networking statistics for netlink users. // This file contains "gnet_" prefixed structs and relevant functions. // See Documentation/networking/getn_stats.txt in Linux source code for more details. // GnetStatsBasic Ref: struct gnet_stats_basic { ... } type GnetStatsBasic struct { Bytes uint64 // number of seen bytes Packets uint32 // number of seen packets } // GnetStatsRateEst Ref: struct gnet_stats_rate_est { ... } type GnetStatsRateEst struct { Bps uint32 // current byte rate Pps uint32 // current packet rate } // GnetStatsRateEst64 Ref: struct gnet_stats_rate_est64 { ... } type GnetStatsRateEst64 struct { Bps uint64 // current byte rate Pps uint64 // current packet rate } // GnetStatsQueue Ref: struct gnet_stats_queue { ... } type GnetStatsQueue struct { Qlen uint32 // queue length Backlog uint32 // backlog size of queue Drops uint32 // number of dropped packets Requeues uint32 // number of requues Overlimits uint32 // number of enqueues over the limit } // ClassStatistics representation based on generic networking statistics for netlink. // See Documentation/networking/gen_stats.txt in Linux source code for more details. type ClassStatistics struct { Basic *GnetStatsBasic Queue *GnetStatsQueue RateEst *GnetStatsRateEst BasicHw *GnetStatsBasic // Hardward statistics added in kernel 4.20 } // NewClassStatistics Construct a ClassStatistics struct which fields are all initialized by 0. func NewClassStatistics() *ClassStatistics { return &ClassStatistics{ Basic: &GnetStatsBasic{}, Queue: &GnetStatsQueue{}, RateEst: &GnetStatsRateEst{}, BasicHw: &GnetStatsBasic{}, } } // ClassAttrs represents a netlink class. A filter is associated with a link, // has a handle and a parent. The root filter of a device should have a // parent == HANDLE_ROOT. type ClassAttrs struct { LinkIndex int Handle uint32 Parent uint32 Leaf uint32 Statistics *ClassStatistics } func (q ClassAttrs) String() string { return fmt.Sprintf("{LinkIndex: %d, Handle: %s, Parent: %s, Leaf: %d}", q.LinkIndex, HandleStr(q.Handle), HandleStr(q.Parent), q.Leaf) } // HtbClassAttrs stores the attributes of HTB class type HtbClassAttrs struct { // TODO handle all attributes Rate uint64 Ceil uint64 Buffer uint32 Cbuffer uint32 Quantum uint32 Level uint32 Prio uint32 } func (q HtbClassAttrs) String() string { return fmt.Sprintf("{Rate: %d, Ceil: %d, Buffer: %d, Cbuffer: %d}", q.Rate, q.Ceil, q.Buffer, q.Cbuffer) } // HtbClass represents an Htb class type HtbClass struct { ClassAttrs Rate uint64 Ceil uint64 Buffer uint32 Cbuffer uint32 Quantum uint32 Level uint32 Prio uint32 } func (q HtbClass) String() string { return fmt.Sprintf("{Rate: %d, Ceil: %d, Buffer: %d, Cbuffer: %d}", q.Rate, q.Ceil, q.Buffer, q.Cbuffer) } // Attrs returns the class attributes func (q *HtbClass) Attrs() *ClassAttrs { return &q.ClassAttrs } // Type return the class type func (q *HtbClass) Type() string { return "htb" } // GenericClass classes represent types that are not currently understood // by this netlink library. type GenericClass struct { ClassAttrs ClassType string } // Attrs return the class attributes func (class *GenericClass) Attrs() *ClassAttrs { return &class.ClassAttrs } // Type return the class type func (class *GenericClass) Type() string { return class.ClassType } // ServiceCurve is a nondecreasing function of some time unit, returning the amount of service // (an allowed or allocated amount of bandwidth) at some specific point in time. The purpose of it // should be subconsciously obvious: if a class was allowed to transfer not less than the amount // specified by its service curve, then the service curve is not violated. type ServiceCurve struct { m1 uint32 d uint32 m2 uint32 } // Attrs return the parameters of the service curve func (c *ServiceCurve) Attrs() (uint32, uint32, uint32) { return c.m1, c.d, c.m2 } // Burst returns the burst rate (m1) of the curve func (c *ServiceCurve) Burst() uint32 { return c.m1 } // Delay return the delay (d) of the curve func (c *ServiceCurve) Delay() uint32 { return c.d } // Rate returns the rate (m2) of the curve func (c *ServiceCurve) Rate() uint32 { return c.m2 } // HfscClass is a representation of the HFSC class type HfscClass struct { ClassAttrs Rsc ServiceCurve Fsc ServiceCurve Usc ServiceCurve } // SetUsc sets the USC curve. The bandwidth (m1 and m2) is specified in bits and the delay in // seconds. func (hfsc *HfscClass) SetUsc(m1 uint32, d uint32, m2 uint32) { hfsc.Usc = ServiceCurve{m1: m1, d: d, m2: m2} } // SetFsc sets the Fsc curve. The bandwidth (m1 and m2) is specified in bits and the delay in // seconds. func (hfsc *HfscClass) SetFsc(m1 uint32, d uint32, m2 uint32) { hfsc.Fsc = ServiceCurve{m1: m1, d: d, m2: m2} } // SetRsc sets the Rsc curve. The bandwidth (m1 and m2) is specified in bits and the delay in // seconds. func (hfsc *HfscClass) SetRsc(m1 uint32, d uint32, m2 uint32) { hfsc.Rsc = ServiceCurve{m1: m1, d: d, m2: m2} } // SetSC implements the SC from the `tc` CLI. This function behaves the same as if one would set the // USC through the `tc` command-line tool. This means bandwidth (m1 and m2) is specified in bits and // the delay in ms. func (hfsc *HfscClass) SetSC(m1 uint32, d uint32, m2 uint32) { hfsc.SetRsc(m1, d, m2) hfsc.SetFsc(m1, d, m2) } // SetUL implements the UL from the `tc` CLI. This function behaves the same as if one would set the // USC through the `tc` command-line tool. This means bandwidth (m1 and m2) is specified in bits and // the delay in ms. func (hfsc *HfscClass) SetUL(m1 uint32, d uint32, m2 uint32) { hfsc.SetUsc(m1, d, m2) } // SetLS implements the LS from the `tc` CLI. This function behaves the same as if one would set the // USC through the `tc` command-line tool. This means bandwidth (m1 and m2) is specified in bits and // the delay in ms. func (hfsc *HfscClass) SetLS(m1 uint32, d uint32, m2 uint32) { hfsc.SetFsc(m1, d, m2) } // NewHfscClass returns a new HFSC struct with the set parameters func NewHfscClass(attrs ClassAttrs) *HfscClass { return &HfscClass{ ClassAttrs: attrs, Rsc: ServiceCurve{}, Fsc: ServiceCurve{}, Usc: ServiceCurve{}, } } // String() returns a string that contains the information and attributes of the HFSC class func (hfsc *HfscClass) String() string { return fmt.Sprintf( "{%s -- {RSC: {m1=%d d=%d m2=%d}} {FSC: {m1=%d d=%d m2=%d}} {USC: {m1=%d d=%d m2=%d}}}", hfsc.Attrs(), hfsc.Rsc.m1*8, hfsc.Rsc.d, hfsc.Rsc.m2*8, hfsc.Fsc.m1*8, hfsc.Fsc.d, hfsc.Fsc.m2*8, hfsc.Usc.m1*8, hfsc.Usc.d, hfsc.Usc.m2*8, ) } // Attrs return the Hfsc parameters func (hfsc *HfscClass) Attrs() *ClassAttrs { return &hfsc.ClassAttrs } // Type return the type of the class func (hfsc *HfscClass) Type() string { return "hfsc" } netlink-1.3.0/class_linux.go000066400000000000000000000260211466216277000160460ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "encoding/hex" "errors" "fmt" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // Internal tc_stats representation in Go struct. // This is for internal uses only to deserialize the payload of rtattr. // After the deserialization, this should be converted into the canonical stats // struct, ClassStatistics, in case of statistics of a class. // Ref: struct tc_stats { ... } type tcStats struct { Bytes uint64 // Number of enqueued bytes Packets uint32 // Number of enqueued packets Drops uint32 // Packets dropped because of lack of resources Overlimits uint32 // Number of throttle events when this flow goes out of allocated bandwidth Bps uint32 // Current flow byte rate Pps uint32 // Current flow packet rate Qlen uint32 Backlog uint32 } // NewHtbClass NOTE: function is in here because it uses other linux functions func NewHtbClass(attrs ClassAttrs, cattrs HtbClassAttrs) *HtbClass { mtu := 1600 rate := cattrs.Rate / 8 ceil := cattrs.Ceil / 8 buffer := cattrs.Buffer cbuffer := cattrs.Cbuffer if ceil == 0 { ceil = rate } if buffer == 0 { buffer = uint32(float64(rate)/Hz() + float64(mtu)) } buffer = Xmittime(rate, buffer) if cbuffer == 0 { cbuffer = uint32(float64(ceil)/Hz() + float64(mtu)) } cbuffer = Xmittime(ceil, cbuffer) return &HtbClass{ ClassAttrs: attrs, Rate: rate, Ceil: ceil, Buffer: buffer, Cbuffer: cbuffer, Level: 0, Prio: cattrs.Prio, Quantum: cattrs.Quantum, } } // ClassDel will delete a class from the system. // Equivalent to: `tc class del $class` func ClassDel(class Class) error { return pkgHandle.ClassDel(class) } // ClassDel will delete a class from the system. // Equivalent to: `tc class del $class` func (h *Handle) ClassDel(class Class) error { return h.classModify(unix.RTM_DELTCLASS, 0, class) } // ClassChange will change a class in place // Equivalent to: `tc class change $class` // The parent and handle MUST NOT be changed. func ClassChange(class Class) error { return pkgHandle.ClassChange(class) } // ClassChange will change a class in place // Equivalent to: `tc class change $class` // The parent and handle MUST NOT be changed. func (h *Handle) ClassChange(class Class) error { return h.classModify(unix.RTM_NEWTCLASS, 0, class) } // ClassReplace will replace a class to the system. // quivalent to: `tc class replace $class` // The handle MAY be changed. // If a class already exist with this parent/handle pair, the class is changed. // If a class does not already exist with this parent/handle, a new class is created. func ClassReplace(class Class) error { return pkgHandle.ClassReplace(class) } // ClassReplace will replace a class to the system. // quivalent to: `tc class replace $class` // The handle MAY be changed. // If a class already exist with this parent/handle pair, the class is changed. // If a class does not already exist with this parent/handle, a new class is created. func (h *Handle) ClassReplace(class Class) error { return h.classModify(unix.RTM_NEWTCLASS, unix.NLM_F_CREATE, class) } // ClassAdd will add a class to the system. // Equivalent to: `tc class add $class` func ClassAdd(class Class) error { return pkgHandle.ClassAdd(class) } // ClassAdd will add a class to the system. // Equivalent to: `tc class add $class` func (h *Handle) ClassAdd(class Class) error { return h.classModify( unix.RTM_NEWTCLASS, unix.NLM_F_CREATE|unix.NLM_F_EXCL, class, ) } func (h *Handle) classModify(cmd, flags int, class Class) error { req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) base := class.Attrs() msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: int32(base.LinkIndex), Handle: base.Handle, Parent: base.Parent, } req.AddData(msg) if cmd != unix.RTM_DELTCLASS { if err := classPayload(req, class); err != nil { return err } } _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func classPayload(req *nl.NetlinkRequest, class Class) error { req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(class.Type()))) options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) switch class.Type() { case "htb": htb := class.(*HtbClass) opt := nl.TcHtbCopt{} opt.Buffer = htb.Buffer opt.Cbuffer = htb.Cbuffer opt.Quantum = htb.Quantum opt.Level = htb.Level opt.Prio = htb.Prio // TODO: Handle Debug properly. For now default to 0 /* Calculate {R,C}Tab and set Rate and Ceil */ cellLog := -1 ccellLog := -1 linklayer := nl.LINKLAYER_ETHERNET mtu := 1600 var rtab [256]uint32 var ctab [256]uint32 tcrate := nl.TcRateSpec{Rate: uint32(htb.Rate)} if CalcRtable(&tcrate, rtab[:], cellLog, uint32(mtu), linklayer) < 0 { return errors.New("HTB: failed to calculate rate table") } opt.Rate = tcrate tcceil := nl.TcRateSpec{Rate: uint32(htb.Ceil)} if CalcRtable(&tcceil, ctab[:], ccellLog, uint32(mtu), linklayer) < 0 { return errors.New("HTB: failed to calculate ceil rate table") } opt.Ceil = tcceil options.AddRtAttr(nl.TCA_HTB_PARMS, opt.Serialize()) options.AddRtAttr(nl.TCA_HTB_RTAB, SerializeRtab(rtab)) options.AddRtAttr(nl.TCA_HTB_CTAB, SerializeRtab(ctab)) if htb.Rate >= uint64(1<<32) { options.AddRtAttr(nl.TCA_HTB_RATE64, nl.Uint64Attr(htb.Rate)) } if htb.Ceil >= uint64(1<<32) { options.AddRtAttr(nl.TCA_HTB_CEIL64, nl.Uint64Attr(htb.Ceil)) } case "hfsc": hfsc := class.(*HfscClass) opt := nl.HfscCopt{} rm1, rd, rm2 := hfsc.Rsc.Attrs() opt.Rsc.Set(rm1/8, rd, rm2/8) fm1, fd, fm2 := hfsc.Fsc.Attrs() opt.Fsc.Set(fm1/8, fd, fm2/8) um1, ud, um2 := hfsc.Usc.Attrs() opt.Usc.Set(um1/8, ud, um2/8) options.AddRtAttr(nl.TCA_HFSC_RSC, nl.SerializeHfscCurve(&opt.Rsc)) options.AddRtAttr(nl.TCA_HFSC_FSC, nl.SerializeHfscCurve(&opt.Fsc)) options.AddRtAttr(nl.TCA_HFSC_USC, nl.SerializeHfscCurve(&opt.Usc)) } req.AddData(options) return nil } // ClassList gets a list of classes in the system. // Equivalent to: `tc class show`. // Generally returns nothing if link and parent are not specified. func ClassList(link Link, parent uint32) ([]Class, error) { return pkgHandle.ClassList(link, parent) } // ClassList gets a list of classes in the system. // Equivalent to: `tc class show`. // Generally returns nothing if link and parent are not specified. func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Parent: parent, } if link != nil { base := link.Attrs() h.ensureIndex(base) msg.Ifindex = int32(base.Index) } req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) if err != nil { return nil, err } var res []Class for _, m := range msgs { msg := nl.DeserializeTcMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } base := ClassAttrs{ LinkIndex: int(msg.Ifindex), Handle: msg.Handle, Parent: msg.Parent, Statistics: nil, } var class Class classType := "" for _, attr := range attrs { switch attr.Attr.Type { case nl.TCA_KIND: classType = string(attr.Value[:len(attr.Value)-1]) switch classType { case "htb": class = &HtbClass{} case "hfsc": class = &HfscClass{} default: class = &GenericClass{ClassType: classType} } case nl.TCA_OPTIONS: switch classType { case "htb": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } _, err = parseHtbClassData(class, data) if err != nil { return nil, err } case "hfsc": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } _, err = parseHfscClassData(class, data) if err != nil { return nil, err } } // For backward compatibility. case nl.TCA_STATS: base.Statistics, err = parseTcStats(attr.Value) if err != nil { return nil, err } case nl.TCA_STATS2: base.Statistics, err = parseTcStats2(attr.Value) if err != nil { return nil, err } } } *class.Attrs() = base res = append(res, class) } return res, nil } func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { htb := class.(*HtbClass) detailed := false for _, datum := range data { switch datum.Attr.Type { case nl.TCA_HTB_PARMS: opt := nl.DeserializeTcHtbCopt(datum.Value) htb.Rate = uint64(opt.Rate.Rate) htb.Ceil = uint64(opt.Ceil.Rate) htb.Buffer = opt.Buffer htb.Cbuffer = opt.Cbuffer htb.Quantum = opt.Quantum htb.Level = opt.Level htb.Prio = opt.Prio case nl.TCA_HTB_RATE64: htb.Rate = native.Uint64(datum.Value[0:8]) case nl.TCA_HTB_CEIL64: htb.Ceil = native.Uint64(datum.Value[0:8]) } } return detailed, nil } func parseHfscClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { hfsc := class.(*HfscClass) detailed := false for _, datum := range data { m1, d, m2 := nl.DeserializeHfscCurve(datum.Value).Attrs() switch datum.Attr.Type { case nl.TCA_HFSC_RSC: hfsc.Rsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} case nl.TCA_HFSC_FSC: hfsc.Fsc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} case nl.TCA_HFSC_USC: hfsc.Usc = ServiceCurve{m1: m1 * 8, d: d, m2: m2 * 8} } } return detailed, nil } func parseTcStats(data []byte) (*ClassStatistics, error) { buf := &bytes.Buffer{} buf.Write(data) tcStats := &tcStats{} if err := binary.Read(buf, native, tcStats); err != nil { return nil, err } stats := NewClassStatistics() stats.Basic.Bytes = tcStats.Bytes stats.Basic.Packets = tcStats.Packets stats.Queue.Qlen = tcStats.Qlen stats.Queue.Backlog = tcStats.Backlog stats.Queue.Drops = tcStats.Drops stats.Queue.Overlimits = tcStats.Overlimits stats.RateEst.Bps = tcStats.Bps stats.RateEst.Pps = tcStats.Pps return stats, nil } func parseGnetStats(data []byte, gnetStats interface{}) error { buf := &bytes.Buffer{} buf.Write(data) return binary.Read(buf, native, gnetStats) } func parseTcStats2(data []byte) (*ClassStatistics, error) { rtAttrs, err := nl.ParseRouteAttr(data) if err != nil { return nil, err } stats := NewClassStatistics() for _, datum := range rtAttrs { switch datum.Attr.Type { case nl.TCA_STATS_BASIC: if err := parseGnetStats(datum.Value, stats.Basic); err != nil { return nil, fmt.Errorf("Failed to parse ClassStatistics.Basic with: %v\n%s", err, hex.Dump(datum.Value)) } case nl.TCA_STATS_QUEUE: if err := parseGnetStats(datum.Value, stats.Queue); err != nil { return nil, fmt.Errorf("Failed to parse ClassStatistics.Queue with: %v\n%s", err, hex.Dump(datum.Value)) } case nl.TCA_STATS_RATE_EST: if err := parseGnetStats(datum.Value, stats.RateEst); err != nil { return nil, fmt.Errorf("Failed to parse ClassStatistics.RateEst with: %v\n%s", err, hex.Dump(datum.Value)) } case nl.TCA_STATS_BASIC_HW: if err := parseGnetStats(datum.Value, stats.BasicHw); err != nil { return nil, fmt.Errorf("Failed to parse ClassStatistics.BasicHw with: %v\n%s", err, hex.Dump(datum.Value)) } } } return stats, nil } netlink-1.3.0/class_test.go000066400000000000000000000360221466216277000156700ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "reflect" "testing" ) func SafeQdiscList(link Link) ([]Qdisc, error) { qdiscs, err := QdiscList(link) if err != nil { return nil, err } result := []Qdisc{} for _, qdisc := range qdiscs { // fmt.Printf("%+v\n", qdisc) // filter default qdisc created by kernel when custom one deleted attrs := qdisc.Attrs() if attrs.Handle == HANDLE_NONE && attrs.Parent == HANDLE_ROOT { continue } result = append(result, qdisc) } return result, nil } func SafeClassList(link Link, handle uint32) ([]Class, error) { classes, err := ClassList(link, handle) if err != nil { return nil, err } result := []Class{} for ind := range classes { double := false for _, class2 := range classes[ind+1:] { if classes[ind].Attrs().Handle == class2.Attrs().Handle { double = true } } if !double { result = append(result, classes[ind]) } } return result, nil } func testClassStats(this, that *ClassStatistics, t *testing.T) { ok := reflect.DeepEqual(this, that) if !ok { t.Fatalf("%#v is expected but it actually was %#v", that, this) } } func TestClassAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0xffff, 0), Parent: HANDLE_ROOT, } qdisc := NewHtb(attrs) if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } _, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } classattrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(0xffff, 0), Handle: MakeHandle(0xffff, 2), } htbclassattrs := HtbClassAttrs{ Rate: 1234000, Cbuffer: 1690, Prio: 2, Quantum: 1000, } class := NewHtbClass(classattrs, htbclassattrs) if err := ClassAdd(class); err != nil { t.Fatal(err) } classes, err := SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatal("Failed to add class") } htb, ok := classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } if htb.Rate != class.Rate { t.Fatal("Rate doesn't match") } if htb.Ceil != class.Ceil { t.Fatal("Ceil doesn't match") } if htb.Buffer != class.Buffer { t.Fatal("Buffer doesn't match") } if htb.Cbuffer != class.Cbuffer { t.Fatal("Cbuffer doesn't match") } if htb.Prio != class.Prio { t.Fatal("Prio doesn't match") } if htb.Quantum != class.Quantum { t.Fatal("Quantum doesn't match") } testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t) qattrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0x2, 0), Parent: MakeHandle(0xffff, 2), } nattrs := NetemQdiscAttrs{ Latency: 20000, Loss: 23.4, Duplicate: 14.3, LossCorr: 8.34, Jitter: 1000, DelayCorr: 12.3, ReorderProb: 23.4, CorruptProb: 10.0, CorruptCorr: 10, Rate64: 10 * 1024 * 1024, } qdiscnetem := NewNetem(qattrs, nattrs) if err := QdiscAdd(qdiscnetem); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatal("Failed to add qdisc") } _, ok = qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } netem, ok := qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Compare the record we got from the list with the one we created if netem.Loss != qdiscnetem.Loss { t.Fatal("Loss does not match") } if netem.Latency != qdiscnetem.Latency { t.Fatal("Latency does not match") } if netem.CorruptProb != qdiscnetem.CorruptProb { t.Fatal("CorruptProb does not match") } if netem.Jitter != qdiscnetem.Jitter { t.Fatal("Jitter does not match") } if netem.LossCorr != qdiscnetem.LossCorr { t.Fatal("Loss does not match") } if netem.DuplicateCorr != qdiscnetem.DuplicateCorr { t.Fatal("DuplicateCorr does not match") } if netem.Rate64 != qdiscnetem.Rate64 { t.Fatalf("Rate64 does not match. Expected %d, got %d", netem.Rate64, qdiscnetem.Rate64) } // Deletion // automatically removes netem qdisc if err := ClassDel(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 0 { t.Fatal("Failed to remove class") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestHtbClassAddHtbClassChangeDel(t *testing.T) { /** This test first set up a interface ans set up a Htb qdisc A HTB class is attach to it and a Netem qdisc is attached to that class Next, we test changing the HTB class in place and confirming the Netem is still attached. We also check that invoting ClassChange with a non-existing class will fail. Finally, we test ClassReplace. We confirm it correctly behave like ClassChange when the parent/handle pair exists and that it will create a new class if the handle is modified. */ tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0xffff, 0), Parent: HANDLE_ROOT, } qdisc := NewHtb(attrs) if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } _, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } classattrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(0xffff, 0), Handle: MakeHandle(0xffff, 2), } htbclassattrs := HtbClassAttrs{ Rate: uint64(1<<32) + 10, Ceil: uint64(1<<32) + 20, Cbuffer: 1690, } class := NewHtbClass(classattrs, htbclassattrs) if err := ClassAdd(class); err != nil { t.Fatal(err) } classes, err := SafeClassList(link, 0) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatal("Failed to add class") } htb, ok := classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t) qattrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(0x2, 0), Parent: MakeHandle(0xffff, 2), } nattrs := NetemQdiscAttrs{ Latency: 20000, Loss: 23.4, Duplicate: 14.3, LossCorr: 8.34, Jitter: 1000, DelayCorr: 12.3, ReorderProb: 23.4, CorruptProb: 10.0, CorruptCorr: 10, } qdiscnetem := NewNetem(qattrs, nattrs) if err := QdiscAdd(qdiscnetem); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatal("Failed to add qdisc") } _, ok = qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Change // For change to work, the handle and parent cannot be changed. // First, test it fails if we change the Handle. oldHandle := classattrs.Handle classattrs.Handle = MakeHandle(0xffff, 3) class = NewHtbClass(classattrs, htbclassattrs) if err := ClassChange(class); err == nil { t.Fatal("ClassChange should not work when using a different handle.") } // It should work with the same handle classattrs.Handle = oldHandle htbclassattrs.Rate = 4321000 class = NewHtbClass(classattrs, htbclassattrs) if err := ClassChange(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatalf( "1 class expected, %d found", len(classes), ) } htb, ok = classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // Check that we still have the netem child qdisc qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 2 { t.Fatalf("2 qdisc expected, %d found", len(qdiscs)) } _, ok = qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } _, ok = qdiscs[1].(*Netem) if !ok { t.Fatal("Qdisc is the wrong type") } // Replace // First replace by keeping the same handle, class will be changed. // Then, replace by providing a new handle, n new class will be created. // Replace acting as Change class = NewHtbClass(classattrs, htbclassattrs) if err := ClassReplace(class); err != nil { t.Fatal("Failed to replace class that is existing.") } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 1 { t.Fatalf( "1 class expected, %d found", len(classes), ) } htb, ok = classes[0].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // It should work with the same handle classattrs.Handle = MakeHandle(0xffff, 3) class = NewHtbClass(classattrs, htbclassattrs) if err := ClassReplace(class); err != nil { t.Fatal(err) } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 2 { t.Fatalf( "2 classes expected, %d found", len(classes), ) } htb, ok = classes[1].(*HtbClass) if !ok { t.Fatal("Class is the wrong type") } // Verify that the rate value has changed. if htb.Rate != class.Rate { t.Fatal("Rate did not get changed while changing the class.") } // Deletion for _, class := range classes { if err := ClassDel(class); err != nil { t.Fatal(err) } } classes, err = SafeClassList(link, MakeHandle(0xffff, 0)) if err != nil { t.Fatal(err) } if len(classes) != 0 { t.Fatal("Failed to remove class") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestClassHfsc(t *testing.T) { // New network namespace for tests tearDown := setUpNetlinkTestWithKModule(t, "sch_hfsc") defer tearDown() // Set up testing link and check if succeeded if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } // Adding HFSC qdisc qdiscAttrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } hfscQdisc := NewHfsc(qdiscAttrs) hfscQdisc.Defcls = 2 err = QdiscAdd(hfscQdisc) if err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } _, ok := qdiscs[0].(*Hfsc) if !ok { t.Fatal("Qdisc is the wrong type") } // Adding some HFSC classes classAttrs := ClassAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(1, 0), Handle: MakeHandle(1, 1), } hfscClass := NewHfscClass(classAttrs) hfscClass.SetLS(5e6, 10, 5e6) err = ClassAdd(hfscClass) if err != nil { t.Fatal(err) } hfscClass2 := hfscClass hfscClass2.SetLS(0, 0, 0) hfscClass2.Attrs().Parent = MakeHandle(1, 1) hfscClass2.Attrs().Handle = MakeHandle(1, 2) hfscClass2.SetRsc(0, 0, 2e6) err = ClassAdd(hfscClass2) if err != nil { t.Fatal(err) } hfscClass3 := hfscClass hfscClass3.SetLS(0, 0, 0) hfscClass3.Attrs().Parent = MakeHandle(1, 1) hfscClass3.Attrs().Handle = MakeHandle(1, 3) err = ClassAdd(hfscClass3) if err != nil { t.Fatal(err) } // Check the classes classes, err := SafeClassList(link, MakeHandle(1, 0)) if err != nil { t.Fatal(err) } if len(classes) != 4 { t.Fatal("Failed to add classes") } for _, c := range classes { class, ok := c.(*HfscClass) if !ok { t.Fatal("Wrong type of class") } if class.ClassAttrs.Handle == hfscClass.ClassAttrs.Handle { if class.Fsc != hfscClass.Fsc { t.Fatal("HfscClass FSC don't match") } if class.Usc != hfscClass.Usc { t.Fatal("HfscClass USC don't match") } if class.Rsc != hfscClass.Rsc { t.Fatal("HfscClass RSC don't match") } } if class.ClassAttrs.Handle == hfscClass2.ClassAttrs.Handle { if class.Fsc != hfscClass2.Fsc { t.Fatal("HfscClass2 FSC don't match") } if class.Usc != hfscClass2.Usc { t.Fatal("HfscClass2 USC don't match") } if class.Rsc != hfscClass2.Rsc { t.Fatal("HfscClass2 RSC don't match") } } if class.ClassAttrs.Handle == hfscClass3.ClassAttrs.Handle { if class.Fsc != hfscClass3.Fsc { t.Fatal("HfscClass3 FSC don't match") } if class.Usc != hfscClass3.Usc { t.Fatal("HfscClass3 USC don't match") } if class.Rsc != hfscClass3.Rsc { t.Fatal("HfscClass3 RSC don't match") } } } // Terminating the leafs with fq_codel qdiscs fqcodelAttrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Parent: MakeHandle(1, 2), Handle: MakeHandle(2, 0), } fqcodel1 := NewFqCodel(fqcodelAttrs) fqcodel1.ECN = 0 fqcodel1.Limit = 1200 fqcodel1.Flows = 65535 fqcodel1.Target = 5 err = QdiscAdd(fqcodel1) if err != nil { t.Fatal(err) } fqcodel2 := fqcodel1 fqcodel2.Attrs().Handle = MakeHandle(3, 0) fqcodel2.Attrs().Parent = MakeHandle(1, 3) err = QdiscAdd(fqcodel2) if err != nil { t.Fatal(err) } // Check the amount of qdiscs qdiscs, _ = SafeQdiscList(link) if len(qdiscs) != 3 { t.Fatal("Failed to add qdisc") } for _, q := range qdiscs[1:] { _, ok = q.(*FqCodel) if !ok { t.Fatal("Qdisc is the wrong type") } } // removing a class if err := ClassDel(hfscClass3); err != nil { t.Fatal(err) } // Check the classes classes, err = SafeClassList(link, MakeHandle(1, 0)) if err != nil { t.Fatal(err) } if len(classes) != 3 { t.Fatal("Failed to delete classes") } // Check qdisc qdiscs, _ = SafeQdiscList(link) if len(qdiscs) != 2 { t.Fatal("Failed to delete qdisc") } // Changing a class hfscClass2.SetRsc(0, 0, 0) hfscClass2.SetSC(5e6, 100, 1e6) hfscClass2.SetUL(6e6, 50, 2e6) hfscClass2.Attrs().Handle = MakeHandle(1, 8) if err := ClassChange(hfscClass2); err == nil { t.Fatal("Class change shouldn't work with a different handle") } hfscClass2.Attrs().Handle = MakeHandle(1, 2) if err := ClassChange(hfscClass2); err != nil { t.Fatal(err) } // Replacing a class // If the handle doesn't exist, create it hfscClass2.SetSC(6e6, 100, 2e6) hfscClass2.SetUL(8e6, 500, 4e6) hfscClass2.Attrs().Handle = MakeHandle(1, 8) if err := ClassReplace(hfscClass2); err != nil { t.Fatal(err) } // If the handle exists, replace it hfscClass.SetLS(5e6, 200, 1e6) if err := ClassChange(hfscClass); err != nil { t.Fatal(err) } } netlink-1.3.0/cmd/000077500000000000000000000000001466216277000137355ustar00rootroot00000000000000netlink-1.3.0/cmd/ipset-test/000077500000000000000000000000001466216277000160365ustar00rootroot00000000000000netlink-1.3.0/cmd/ipset-test/main.go000066400000000000000000000070621466216277000173160ustar00rootroot00000000000000//go:build linux // +build linux package main import ( "flag" "fmt" "log" "net" "os" "sort" "github.com/vishvananda/netlink" ) type command struct { Function func([]string) Description string ArgCount int } var ( commands = map[string]command{ "protocol": {cmdProtocol, "prints the protocol version", 0}, "create": {cmdCreate, "creates a new ipset", 2}, "destroy": {cmdDestroy, "creates a new ipset", 1}, "list": {cmdList, "list specific ipset", 1}, "listall": {cmdListAll, "list all ipsets", 0}, "add": {cmdAddDel(netlink.IpsetAdd), "add entry", 2}, "del": {cmdAddDel(netlink.IpsetDel), "delete entry", 2}, "test": {cmdTest, "test whether an entry is in a set or not", 2}, } timeoutVal *uint32 timeout = flag.Int("timeout", -1, "timeout, negative means omit the argument") comment = flag.String("comment", "", "comment") withComments = flag.Bool("with-comments", false, "create set with comment support") withCounters = flag.Bool("with-counters", false, "create set with counters support") withSkbinfo = flag.Bool("with-skbinfo", false, "create set with skbinfo support") replace = flag.Bool("replace", false, "replace existing set/entry") ) func main() { flag.Parse() args := flag.Args() if len(args) < 1 { printUsage() os.Exit(1) } if *timeout >= 0 { v := uint32(*timeout) timeoutVal = &v } log.SetFlags(log.Lshortfile) cmdName := args[0] args = args[1:] cmd, exist := commands[cmdName] if !exist { fmt.Printf("Unknown command '%s'\n\n", cmdName) printUsage() os.Exit(1) } if cmd.ArgCount != len(args) { fmt.Printf("Invalid number of arguments. expected=%d given=%d\n", cmd.ArgCount, len(args)) os.Exit(1) } cmd.Function(args) } func printUsage() { fmt.Printf("Usage: %s COMMAND [args] [-flags]\n\n", os.Args[0]) names := make([]string, 0, len(commands)) for name := range commands { names = append(names, name) } sort.Strings(names) fmt.Println("Available commands:") for _, name := range names { fmt.Printf(" %-15v %s\n", name, commands[name].Description) } fmt.Println("\nAvailable flags:") flag.PrintDefaults() } func cmdProtocol(_ []string) { protocol, minProto, err := netlink.IpsetProtocol() check(err) log.Println("Protocol:", protocol, "min:", minProto) } func cmdCreate(args []string) { err := netlink.IpsetCreate(args[0], args[1], netlink.IpsetCreateOptions{ Replace: *replace, Timeout: timeoutVal, Comments: *withComments, Counters: *withCounters, Skbinfo: *withSkbinfo, }) check(err) } func cmdDestroy(args []string) { check(netlink.IpsetDestroy(args[0])) } func cmdList(args []string) { result, err := netlink.IpsetList(args[0]) check(err) log.Printf("%+v", result) } func cmdListAll(args []string) { result, err := netlink.IpsetListAll() check(err) for _, ipset := range result { log.Printf("%+v", ipset) } } func cmdAddDel(f func(string, *netlink.IPSetEntry) error) func([]string) { return func(args []string) { setName := args[0] element := args[1] mac, _ := net.ParseMAC(element) entry := netlink.IPSetEntry{ Timeout: timeoutVal, MAC: mac, Comment: *comment, Replace: *replace, } check(f(setName, &entry)) } } func cmdTest(args []string) { setName := args[0] element := args[1] ip := net.ParseIP(element) entry := &netlink.IPSetEntry{ Timeout: timeoutVal, IP: ip, Comment: *comment, Replace: *replace, } exist, err := netlink.IpsetTest(setName, entry) check(err) log.Printf("existence: %t\n", exist) } // panic on error func check(err error) { if err != nil { panic(err) } } netlink-1.3.0/conntrack_linux.go000066400000000000000000000750151466216277000167320ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "errors" "fmt" "net" "time" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // ConntrackTableType Conntrack table for the netlink operation type ConntrackTableType uint8 const ( // ConntrackTable Conntrack table // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK 1 ConntrackTable = 1 // ConntrackExpectTable Conntrack expect table // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK_EXP 2 ConntrackExpectTable = 2 ) const ( // backward compatibility with golang 1.6 which does not have io.SeekCurrent seekCurrent = 1 ) // InetFamily Family type type InetFamily uint8 // -L [table] [options] List conntrack or expectation table // -G [table] parameters Get conntrack or expectation // -I [table] parameters Create a conntrack or expectation // -U [table] parameters Update a conntrack // -E [table] [options] Show events // -C [table] Show counter // -S Show statistics // ConntrackTableList returns the flow list of a table of a specific family // conntrack -L [table] [options] List conntrack or expectation table func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { return pkgHandle.ConntrackTableList(table, family) } // ConntrackTableFlush flushes all the flows of a specified table // conntrack -F [table] Flush table // The flush operation applies to all the family types func ConntrackTableFlush(table ConntrackTableType) error { return pkgHandle.ConntrackTableFlush(table) } // ConntrackCreate creates a new conntrack flow in the desired table // conntrack -I [table] Create a conntrack or expectation func ConntrackCreate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error { return pkgHandle.ConntrackCreate(table, family, flow) } // ConntrackUpdate updates an existing conntrack flow in the desired table using the handle // conntrack -U [table] Update a conntrack func ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error { return pkgHandle.ConntrackUpdate(table, family, flow) } // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter // conntrack -D [table] parameters Delete conntrack or expectation // // Deprecated: use [ConntrackDeleteFilter] instead. func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) { return pkgHandle.ConntrackDeleteFilters(table, family, filter) } // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters // conntrack -D [table] parameters Delete conntrack or expectation func ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { return pkgHandle.ConntrackDeleteFilters(table, family, filters...) } // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed // conntrack -L [table] [options] List conntrack or expectation table func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { res, err := h.dumpConntrackTable(table, family) if err != nil { return nil, err } // Deserialize all the flows var result []*ConntrackFlow for _, dataRaw := range res { result = append(result, parseRawData(dataRaw)) } return result, nil } // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed // conntrack -F [table] Flush table // The flush operation applies to all the family types func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error { req := h.newConntrackRequest(table, unix.AF_INET, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK) _, err := req.Execute(unix.NETLINK_NETFILTER, 0) return err } // ConntrackCreate creates a new conntrack flow in the desired table using the handle // conntrack -I [table] Create a conntrack or expectation func (h *Handle) ConntrackCreate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error { req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_NEW, unix.NLM_F_ACK|unix.NLM_F_CREATE) attr, err := flow.toNlData() if err != nil { return err } for _, a := range attr { req.AddData(a) } _, err = req.Execute(unix.NETLINK_NETFILTER, 0) return err } // ConntrackUpdate updates an existing conntrack flow in the desired table using the handle // conntrack -U [table] Update a conntrack func (h *Handle) ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error { req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_NEW, unix.NLM_F_ACK|unix.NLM_F_REPLACE) attr, err := flow.toNlData() if err != nil { return err } for _, a := range attr { req.AddData(a) } _, err = req.Execute(unix.NETLINK_NETFILTER, 0) return err } // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed // conntrack -D [table] parameters Delete conntrack or expectation // // Deprecated: use [Handle.ConntrackDeleteFilters] instead. func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) { return h.ConntrackDeleteFilters(table, family, filter) } // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters using the netlink handle passed // conntrack -D [table] parameters Delete conntrack or expectation func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { res, err := h.dumpConntrackTable(table, family) if err != nil { return 0, err } var matched uint for _, dataRaw := range res { flow := parseRawData(dataRaw) for _, filter := range filters { if match := filter.MatchConntrackFlow(flow); match { req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK) // skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already req2.AddRawData(dataRaw[4:]) req2.Execute(unix.NETLINK_NETFILTER, 0) matched++ // flow is already deleted, no need to match on other filters and continue to the next flow. break } } } return matched, nil } func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest { // Create the Netlink request object req := h.newNetlinkRequest((int(table)<<8)|operation, flags) // Add the netfilter header msg := &nl.Nfgenmsg{ NfgenFamily: uint8(family), Version: nl.NFNETLINK_V0, ResId: 0, } req.AddData(msg) return req } func (h *Handle) dumpConntrackTable(table ConntrackTableType, family InetFamily) ([][]byte, error) { req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_GET, unix.NLM_F_DUMP) return req.Execute(unix.NETLINK_NETFILTER, 0) } // ProtoInfo wraps an L4-protocol structure - roughly corresponds to the // __nfct_protoinfo union found in libnetfilter_conntrack/include/internal/object.h. // Currently, only protocol names, and TCP state is supported. type ProtoInfo interface { Protocol() string } // ProtoInfoTCP corresponds to the `tcp` struct of the __nfct_protoinfo union. // Only TCP state is currently supported. type ProtoInfoTCP struct { State uint8 } // Protocol returns "tcp". func (*ProtoInfoTCP) Protocol() string {return "tcp"} func (p *ProtoInfoTCP) toNlData() ([]*nl.RtAttr, error) { ctProtoInfo := nl.NewRtAttr(unix.NLA_F_NESTED | nl.CTA_PROTOINFO, []byte{}) ctProtoInfoTCP := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_PROTOINFO_TCP, []byte{}) ctProtoInfoTCPState := nl.NewRtAttr(nl.CTA_PROTOINFO_TCP_STATE, nl.Uint8Attr(p.State)) ctProtoInfoTCP.AddChild(ctProtoInfoTCPState) ctProtoInfo.AddChild(ctProtoInfoTCP) return []*nl.RtAttr{ctProtoInfo}, nil } // ProtoInfoSCTP only supports the protocol name. type ProtoInfoSCTP struct {} // Protocol returns "sctp". func (*ProtoInfoSCTP) Protocol() string {return "sctp"} // ProtoInfoDCCP only supports the protocol name. type ProtoInfoDCCP struct {} // Protocol returns "dccp". func (*ProtoInfoDCCP) Protocol() string {return "dccp"} // The full conntrack flow structure is very complicated and can be found in the file: // http://git.netfilter.org/libnetfilter_conntrack/tree/include/internal/object.h // For the time being, the structure below allows to parse and extract the base information of a flow type IPTuple struct { Bytes uint64 DstIP net.IP DstPort uint16 Packets uint64 Protocol uint8 SrcIP net.IP SrcPort uint16 } // toNlData generates the inner fields of a nested tuple netlink datastructure // does not generate the "nested"-flagged outer message. func (t *IPTuple) toNlData(family uint8) ([]*nl.RtAttr, error) { var srcIPsFlag, dstIPsFlag int if family == nl.FAMILY_V4 { srcIPsFlag = nl.CTA_IP_V4_SRC dstIPsFlag = nl.CTA_IP_V4_DST } else if family == nl.FAMILY_V6 { srcIPsFlag = nl.CTA_IP_V6_SRC dstIPsFlag = nl.CTA_IP_V6_DST } else { return []*nl.RtAttr{}, fmt.Errorf("couldn't generate netlink message for tuple due to unrecognized FamilyType '%d'", family) } ctTupleIP := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_IP, nil) ctTupleIPSrc := nl.NewRtAttr(srcIPsFlag, t.SrcIP) ctTupleIP.AddChild(ctTupleIPSrc) ctTupleIPDst := nl.NewRtAttr(dstIPsFlag, t.DstIP) ctTupleIP.AddChild(ctTupleIPDst) ctTupleProto := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_PROTO, nil) ctTupleProtoNum := nl.NewRtAttr(nl.CTA_PROTO_NUM, []byte{t.Protocol}) ctTupleProto.AddChild(ctTupleProtoNum) ctTupleProtoSrcPort := nl.NewRtAttr(nl.CTA_PROTO_SRC_PORT, nl.BEUint16Attr(t.SrcPort)) ctTupleProto.AddChild(ctTupleProtoSrcPort) ctTupleProtoDstPort := nl.NewRtAttr(nl.CTA_PROTO_DST_PORT, nl.BEUint16Attr(t.DstPort)) ctTupleProto.AddChild(ctTupleProtoDstPort, ) return []*nl.RtAttr{ctTupleIP, ctTupleProto}, nil } type ConntrackFlow struct { FamilyType uint8 Forward IPTuple Reverse IPTuple Mark uint32 Zone uint16 TimeStart uint64 TimeStop uint64 TimeOut uint32 Labels []byte ProtoInfo ProtoInfo } func (s *ConntrackFlow) String() string { // conntrack cmd output: // udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 packets=5 bytes=532 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 packets=10 bytes=1078 mark=0 labels=0x00000000050012ac4202010000000000 zone=100 // start=2019-07-26 01:26:21.557800506 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=30(sec) start := time.Unix(0, int64(s.TimeStart)) stop := time.Unix(0, int64(s.TimeStop)) timeout := int32(s.TimeOut) res := fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d packets=%d bytes=%d\tsrc=%s dst=%s sport=%d dport=%d packets=%d bytes=%d mark=0x%x ", nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol, s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort, s.Forward.Packets, s.Forward.Bytes, s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort, s.Reverse.Packets, s.Reverse.Bytes, s.Mark) if len(s.Labels) > 0 { res += fmt.Sprintf("labels=0x%x ", s.Labels) } if s.Zone != 0 { res += fmt.Sprintf("zone=%d ", s.Zone) } res += fmt.Sprintf("start=%v stop=%v timeout=%d(sec)", start, stop, timeout) return res } // toNlData generates netlink messages representing the flow. func (s *ConntrackFlow) toNlData() ([]*nl.RtAttr, error) { var payload []*nl.RtAttr // The message structure is built as follows: // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // CTA_TUPLE_ORIG ctTupleOrig := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_ORIG, nil) forwardFlowAttrs, err := s.Forward.toNlData(s.FamilyType) if err != nil { return nil, fmt.Errorf("couldn't generate netlink data for conntrack forward flow: %w", err) } for _, a := range forwardFlowAttrs { ctTupleOrig.AddChild(a) } // CTA_TUPLE_REPLY ctTupleReply := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_REPLY, nil) reverseFlowAttrs, err := s.Reverse.toNlData(s.FamilyType) if err != nil { return nil, fmt.Errorf("couldn't generate netlink data for conntrack reverse flow: %w", err) } for _, a := range reverseFlowAttrs { ctTupleReply.AddChild(a) } ctMark := nl.NewRtAttr(nl.CTA_MARK, nl.BEUint32Attr(s.Mark)) ctTimeout := nl.NewRtAttr(nl.CTA_TIMEOUT, nl.BEUint32Attr(s.TimeOut)) payload = append(payload, ctTupleOrig, ctTupleReply, ctMark, ctTimeout) if s.ProtoInfo != nil { switch p := s.ProtoInfo.(type) { case *ProtoInfoTCP: attrs, err := p.toNlData() if err != nil { return nil, fmt.Errorf("couldn't generate netlink data for conntrack flow's TCP protoinfo: %w", err) } payload = append(payload, attrs...) default: return nil, errors.New("couldn't generate netlink data for conntrack: field 'ProtoInfo' only supports TCP or nil") } } return payload, nil } // This method parse the ip tuple structure // The message structure is the following: // // // // // func parseIpTuple(reader *bytes.Reader, tpl *IPTuple) uint8 { for i := 0; i < 2; i++ { _, t, _, v := parseNfAttrTLV(reader) switch t { case nl.CTA_IP_V4_SRC, nl.CTA_IP_V6_SRC: tpl.SrcIP = v case nl.CTA_IP_V4_DST, nl.CTA_IP_V6_DST: tpl.DstIP = v } } // Get total length of nested protocol-specific info. _, _, protoInfoTotalLen := parseNfAttrTL(reader) _, t, l, v := parseNfAttrTLV(reader) // Track the number of bytes read. protoInfoBytesRead := uint16(nl.SizeofNfattr) + l if t == nl.CTA_PROTO_NUM { tpl.Protocol = uint8(v[0]) } // We only parse TCP & UDP headers. Skip the others. if tpl.Protocol != unix.IPPROTO_TCP && tpl.Protocol != unix.IPPROTO_UDP { // skip the rest bytesRemaining := protoInfoTotalLen - protoInfoBytesRead reader.Seek(int64(bytesRemaining), seekCurrent) return tpl.Protocol } // Skip 3 bytes of padding reader.Seek(3, seekCurrent) protoInfoBytesRead += 3 for i := 0; i < 2; i++ { _, t, _ := parseNfAttrTL(reader) protoInfoBytesRead += uint16(nl.SizeofNfattr) switch t { case nl.CTA_PROTO_SRC_PORT: parseBERaw16(reader, &tpl.SrcPort) protoInfoBytesRead += 2 case nl.CTA_PROTO_DST_PORT: parseBERaw16(reader, &tpl.DstPort) protoInfoBytesRead += 2 } // Skip 2 bytes of padding reader.Seek(2, seekCurrent) protoInfoBytesRead += 2 } // Skip any remaining/unknown parts of the message bytesRemaining := protoInfoTotalLen - protoInfoBytesRead reader.Seek(int64(bytesRemaining), seekCurrent) return tpl.Protocol } func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) { isNested, attrType, len = parseNfAttrTL(r) value = make([]byte, len) binary.Read(r, binary.BigEndian, &value) return isNested, attrType, len, value } func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) { binary.Read(r, nl.NativeEndian(), &len) len -= nl.SizeofNfattr binary.Read(r, nl.NativeEndian(), &attrType) isNested = (attrType & nl.NLA_F_NESTED) == nl.NLA_F_NESTED attrType = attrType & (nl.NLA_F_NESTED - 1) return isNested, attrType, len } // skipNfAttrValue seeks `r` past attr of length `len`. // Maintains buffer alignment. // Returns length of the seek performed. func skipNfAttrValue(r *bytes.Reader, len uint16) uint16 { len = (len + nl.NLA_ALIGNTO - 1) & ^(nl.NLA_ALIGNTO - 1) r.Seek(int64(len), seekCurrent) return len } func parseBERaw16(r *bytes.Reader, v *uint16) { binary.Read(r, binary.BigEndian, v) } func parseBERaw32(r *bytes.Reader, v *uint32) { binary.Read(r, binary.BigEndian, v) } func parseBERaw64(r *bytes.Reader, v *uint64) { binary.Read(r, binary.BigEndian, v) } func parseRaw32(r *bytes.Reader, v *uint32) { binary.Read(r, nl.NativeEndian(), v) } func parseByteAndPacketCounters(r *bytes.Reader) (bytes, packets uint64) { for i := 0; i < 2; i++ { switch _, t, _ := parseNfAttrTL(r); t { case nl.CTA_COUNTERS_BYTES: parseBERaw64(r, &bytes) case nl.CTA_COUNTERS_PACKETS: parseBERaw64(r, &packets) default: return } } return } // when the flow is alive, only the timestamp_start is returned in structure func parseTimeStamp(r *bytes.Reader, readSize uint16) (tstart, tstop uint64) { var numTimeStamps int oneItem := nl.SizeofNfattr + 8 // 4 bytes attr header + 8 bytes timestamp if readSize == uint16(oneItem) { numTimeStamps = 1 } else if readSize == 2*uint16(oneItem) { numTimeStamps = 2 } else { return } for i := 0; i < numTimeStamps; i++ { switch _, t, _ := parseNfAttrTL(r); t { case nl.CTA_TIMESTAMP_START: parseBERaw64(r, &tstart) case nl.CTA_TIMESTAMP_STOP: parseBERaw64(r, &tstop) default: return } } return } func parseProtoInfoTCPState(r *bytes.Reader) (s uint8) { binary.Read(r, binary.BigEndian, &s) r.Seek(nl.SizeofNfattr - 1, seekCurrent) return s } // parseProtoInfoTCP reads the entire nested protoinfo structure, but only parses the state attr. func parseProtoInfoTCP(r *bytes.Reader, attrLen uint16) (*ProtoInfoTCP) { p := new(ProtoInfoTCP) bytesRead := 0 for bytesRead < int(attrLen) { _, t, l := parseNfAttrTL(r) bytesRead += nl.SizeofNfattr switch t { case nl.CTA_PROTOINFO_TCP_STATE: p.State = parseProtoInfoTCPState(r) bytesRead += nl.SizeofNfattr default: bytesRead += int(skipNfAttrValue(r, l)) } } return p } func parseProtoInfo(r *bytes.Reader, attrLen uint16) (p ProtoInfo) { bytesRead := 0 for bytesRead < int(attrLen) { _, t, l := parseNfAttrTL(r) bytesRead += nl.SizeofNfattr switch t { case nl.CTA_PROTOINFO_TCP: p = parseProtoInfoTCP(r, l) bytesRead += int(l) // No inner fields of DCCP / SCTP currently supported. case nl.CTA_PROTOINFO_DCCP: p = new(ProtoInfoDCCP) skipped := skipNfAttrValue(r, l) bytesRead += int(skipped) case nl.CTA_PROTOINFO_SCTP: p = new(ProtoInfoSCTP) skipped := skipNfAttrValue(r, l) bytesRead += int(skipped) default: skipped := skipNfAttrValue(r, l) bytesRead += int(skipped) } } return p } func parseTimeOut(r *bytes.Reader) (ttimeout uint32) { parseBERaw32(r, &ttimeout) return } func parseConnectionMark(r *bytes.Reader) (mark uint32) { parseBERaw32(r, &mark) return } func parseConnectionLabels(r *bytes.Reader) (label []byte) { label = make([]byte, 16) // netfilter defines 128 bit labels value binary.Read(r, nl.NativeEndian(), &label) return } func parseConnectionZone(r *bytes.Reader) (zone uint16) { parseBERaw16(r, &zone) r.Seek(2, seekCurrent) return } func parseRawData(data []byte) *ConntrackFlow { s := &ConntrackFlow{} // First there is the Nfgenmsg header // consume only the family field reader := bytes.NewReader(data) binary.Read(reader, nl.NativeEndian(), &s.FamilyType) // skip rest of the Netfilter header reader.Seek(3, seekCurrent) // The message structure is the following: // 4 bytes // 4 bytes // flow information of the forward flow // 4 bytes // 4 bytes // flow information of the reverse flow for reader.Len() > 0 { if nested, t, l := parseNfAttrTL(reader); nested { switch t { case nl.CTA_TUPLE_ORIG: if nested, t, l = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP { parseIpTuple(reader, &s.Forward) } case nl.CTA_TUPLE_REPLY: if nested, t, l = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP { parseIpTuple(reader, &s.Reverse) } else { // Header not recognized skip it skipNfAttrValue(reader, l) } case nl.CTA_COUNTERS_ORIG: s.Forward.Bytes, s.Forward.Packets = parseByteAndPacketCounters(reader) case nl.CTA_COUNTERS_REPLY: s.Reverse.Bytes, s.Reverse.Packets = parseByteAndPacketCounters(reader) case nl.CTA_TIMESTAMP: s.TimeStart, s.TimeStop = parseTimeStamp(reader, l) case nl.CTA_PROTOINFO: s.ProtoInfo = parseProtoInfo(reader, l) default: skipNfAttrValue(reader, l) } } else { switch t { case nl.CTA_MARK: s.Mark = parseConnectionMark(reader) case nl.CTA_LABELS: s.Labels = parseConnectionLabels(reader) case nl.CTA_TIMEOUT: s.TimeOut = parseTimeOut(reader) case nl.CTA_ID, nl.CTA_STATUS, nl.CTA_USE: skipNfAttrValue(reader, l) case nl.CTA_ZONE: s.Zone = parseConnectionZone(reader) default: skipNfAttrValue(reader, l) } } } return s } // Conntrack parameters and options: // -n, --src-nat ip source NAT ip // -g, --dst-nat ip destination NAT ip // -j, --any-nat ip source or destination NAT ip // -m, --mark mark Set mark // -c, --secmark secmark Set selinux secmark // -e, --event-mask eventmask Event mask, eg. NEW,DESTROY // -z, --zero Zero counters while listing // -o, --output type[,...] Output format, eg. xml // -l, --label label[,...] conntrack labels // Common parameters and options: // -s, --src, --orig-src ip Source address from original direction // -d, --dst, --orig-dst ip Destination address from original direction // -r, --reply-src ip Source address from reply direction // -q, --reply-dst ip Destination address from reply direction // -p, --protonum proto Layer 4 Protocol, eg. 'tcp' // -f, --family proto Layer 3 Protocol, eg. 'ipv6' // -t, --timeout timeout Set timeout // -u, --status status Set status, eg. ASSURED // -w, --zone value Set conntrack zone // --orig-zone value Set zone for original direction // --reply-zone value Set zone for reply direction // -b, --buffer-size Netlink socket buffer size // --mask-src ip Source mask address // --mask-dst ip Destination mask address // Layer 4 Protocol common parameters and options: // TCP, UDP, SCTP, UDPLite and DCCP // --sport, --orig-port-src port Source port in original direction // --dport, --orig-port-dst port Destination port in original direction // Filter types type ConntrackFilterType uint8 const ( ConntrackOrigSrcIP = iota // -orig-src ip Source address from original direction ConntrackOrigDstIP // -orig-dst ip Destination address from original direction ConntrackReplySrcIP // --reply-src ip Reply Source IP ConntrackReplyDstIP // --reply-dst ip Reply Destination IP ConntrackReplyAnyIP // Match source or destination reply IP ConntrackOrigSrcPort // --orig-port-src port Source port in original direction ConntrackOrigDstPort // --orig-port-dst port Destination port in original direction ConntrackMatchLabels // --label label1,label2 Labels used in entry ConntrackUnmatchLabels // --label label1,label2 Labels not used in entry ConntrackNatSrcIP = ConntrackReplySrcIP // deprecated use instead ConntrackReplySrcIP ConntrackNatDstIP = ConntrackReplyDstIP // deprecated use instead ConntrackReplyDstIP ConntrackNatAnyIP = ConntrackReplyAnyIP // deprecated use instead ConntrackReplyAnyIP ) type CustomConntrackFilter interface { // MatchConntrackFlow applies the filter to the flow and returns true if the flow matches // the filter or false otherwise MatchConntrackFlow(flow *ConntrackFlow) bool } type ConntrackFilter struct { ipNetFilter map[ConntrackFilterType]*net.IPNet portFilter map[ConntrackFilterType]uint16 protoFilter uint8 labelFilter map[ConntrackFilterType][][]byte zoneFilter *uint16 } // AddIPNet adds a IP subnet to the conntrack filter func (f *ConntrackFilter) AddIPNet(tp ConntrackFilterType, ipNet *net.IPNet) error { if ipNet == nil { return fmt.Errorf("Filter attribute empty") } if f.ipNetFilter == nil { f.ipNetFilter = make(map[ConntrackFilterType]*net.IPNet) } if _, ok := f.ipNetFilter[tp]; ok { return errors.New("Filter attribute already present") } f.ipNetFilter[tp] = ipNet return nil } // AddIP adds an IP to the conntrack filter func (f *ConntrackFilter) AddIP(tp ConntrackFilterType, ip net.IP) error { if ip == nil { return fmt.Errorf("Filter attribute empty") } return f.AddIPNet(tp, NewIPNet(ip)) } // AddPort adds a Port to the conntrack filter if the Layer 4 protocol allows it func (f *ConntrackFilter) AddPort(tp ConntrackFilterType, port uint16) error { switch f.protoFilter { // TCP, UDP, DCCP, SCTP, UDPLite case 6, 17, 33, 132, 136: default: return fmt.Errorf("Filter attribute not available without a valid Layer 4 protocol: %d", f.protoFilter) } if f.portFilter == nil { f.portFilter = make(map[ConntrackFilterType]uint16) } if _, ok := f.portFilter[tp]; ok { return errors.New("Filter attribute already present") } f.portFilter[tp] = port return nil } // AddProtocol adds the Layer 4 protocol to the conntrack filter func (f *ConntrackFilter) AddProtocol(proto uint8) error { if f.protoFilter != 0 { return errors.New("Filter attribute already present") } f.protoFilter = proto return nil } // AddLabels adds the provided list (zero or more) of labels to the conntrack filter // ConntrackFilterType here can be either: // 1. ConntrackMatchLabels: This matches every flow that has a label value (len(flow.Labels) > 0) // against the list of provided labels. If `flow.Labels` contains ALL the provided labels // it is considered a match. This can be used when you want to match flows that contain // one or more labels. // 2. ConntrackUnmatchLabels: This matches every flow that has a label value (len(flow.Labels) > 0) // against the list of provided labels. If `flow.Labels` does NOT contain ALL the provided labels // it is considered a match. This can be used when you want to match flows that don't contain // one or more labels. func (f *ConntrackFilter) AddLabels(tp ConntrackFilterType, labels [][]byte) error { if len(labels) == 0 { return errors.New("Invalid length for provided labels") } if f.labelFilter == nil { f.labelFilter = make(map[ConntrackFilterType][][]byte) } if _, ok := f.labelFilter[tp]; ok { return errors.New("Filter attribute already present") } f.labelFilter[tp] = labels return nil } // AddZone adds a zone to the conntrack filter func (f *ConntrackFilter) AddZone(zone uint16) error { if f.zoneFilter != nil { return errors.New("Filter attribute already present") } f.zoneFilter = &zone return nil } // MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter // false otherwise func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool { if len(f.ipNetFilter) == 0 && len(f.portFilter) == 0 && f.protoFilter == 0 && len(f.labelFilter) == 0 && f.zoneFilter == nil { // empty filter always not match return false } // -p, --protonum proto Layer 4 Protocol, eg. 'tcp' if f.protoFilter != 0 && flow.Forward.Protocol != f.protoFilter { // different Layer 4 protocol always not match return false } // Conntrack zone filter if f.zoneFilter != nil && *f.zoneFilter != flow.Zone { return false } match := true // IP conntrack filter if len(f.ipNetFilter) > 0 { // -orig-src ip Source address from original direction if elem, found := f.ipNetFilter[ConntrackOrigSrcIP]; found { match = match && elem.Contains(flow.Forward.SrcIP) } // -orig-dst ip Destination address from original direction if elem, found := f.ipNetFilter[ConntrackOrigDstIP]; match && found { match = match && elem.Contains(flow.Forward.DstIP) } // -src-nat ip Source NAT ip if elem, found := f.ipNetFilter[ConntrackReplySrcIP]; match && found { match = match && elem.Contains(flow.Reverse.SrcIP) } // -dst-nat ip Destination NAT ip if elem, found := f.ipNetFilter[ConntrackReplyDstIP]; match && found { match = match && elem.Contains(flow.Reverse.DstIP) } // Match source or destination reply IP if elem, found := f.ipNetFilter[ConntrackReplyAnyIP]; match && found { match = match && (elem.Contains(flow.Reverse.SrcIP) || elem.Contains(flow.Reverse.DstIP)) } } // Layer 4 Port filter if len(f.portFilter) > 0 { // -orig-port-src port Source port from original direction if elem, found := f.portFilter[ConntrackOrigSrcPort]; match && found { match = match && elem == flow.Forward.SrcPort } // -orig-port-dst port Destination port from original direction if elem, found := f.portFilter[ConntrackOrigDstPort]; match && found { match = match && elem == flow.Forward.DstPort } } // Label filter if len(f.labelFilter) > 0 { if len(flow.Labels) > 0 { // --label label1,label2 in conn entry; // every label passed should be contained in flow.Labels for a match to be true if elem, found := f.labelFilter[ConntrackMatchLabels]; match && found { for _, label := range elem { match = match && (bytes.Contains(flow.Labels, label)) } } // --label label1,label2 in conn entry; // every label passed should be not contained in flow.Labels for a match to be true if elem, found := f.labelFilter[ConntrackUnmatchLabels]; match && found { for _, label := range elem { match = match && !(bytes.Contains(flow.Labels, label)) } } } else { // flow doesn't contain labels, so it doesn't contain or notContain any provided matches match = false } } return match } var _ CustomConntrackFilter = (*ConntrackFilter)(nil) netlink-1.3.0/conntrack_test.go000066400000000000000000001304571466216277000165540ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "encoding/binary" "fmt" "net" "os" "runtime" "testing" "time" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) func CheckErrorFail(t *testing.T, err error) { if err != nil { t.Fatalf("Fatal Error: %s", err) } } func CheckError(t *testing.T, err error) { if err != nil { t.Errorf("Error: %s", err) } } func udpFlowCreateProg(t *testing.T, flows, srcPort int, dstIP string, dstPort int) { for i := 0; i < flows; i++ { ServerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", dstIP, dstPort)) CheckError(t, err) LocalAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", srcPort+i)) CheckError(t, err) Conn, err := net.DialUDP("udp", LocalAddr, ServerAddr) CheckError(t, err) Conn.Write([]byte("Hello World")) Conn.Close() } } func nsCreateAndEnter(t *testing.T) (*netns.NsHandle, *netns.NsHandle, *Handle) { // Lock the OS Thread so we don't accidentally switch namespaces runtime.LockOSThread() // Save the current network namespace origns, _ := netns.Get() ns, err := netns.New() CheckErrorFail(t, err) h, err := NewHandleAt(ns) CheckErrorFail(t, err) // Enter the new namespace netns.Set(ns) // Bing up loopback link, _ := h.LinkByName("lo") h.LinkSetUp(link) return &origns, &ns, h } func applyFilter(flowList []ConntrackFlow, ipv4Filter *ConntrackFilter, ipv6Filter *ConntrackFilter) (ipv4Match, ipv6Match uint) { for _, flow := range flowList { if ipv4Filter.MatchConntrackFlow(&flow) == true { ipv4Match++ } if ipv6Filter.MatchConntrackFlow(&flow) == true { ipv6Match++ } } return ipv4Match, ipv6Match } // TestConntrackSocket test the opening of a NETFILTER family socket func TestConntrackSocket(t *testing.T) { skipUnlessRoot(t) setUpNetlinkTestWithKModule(t, "nf_conntrack") setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") h, err := NewHandle(unix.NETLINK_NETFILTER) CheckErrorFail(t, err) if h.SupportsNetlinkFamily(unix.NETLINK_NETFILTER) != true { t.Fatal("ERROR not supporting the NETFILTER family") } } // TestConntrackTableList test the conntrack table list // Creates some flows and checks that they are correctly fetched from the conntrack table func TestConntrackTableList(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI: Flow creation fails") } skipUnlessRoot(t) k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4") setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv6") } setUpNetlinkTestWithKModule(t, "nf_conntrack") setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") // Creates a new namespace and bring up the loopback interface origns, ns, h := nsCreateAndEnter(t) defer netns.Set(*origns) defer origns.Close() defer ns.Close() defer runtime.UnlockOSThread() setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_acct", "1") setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_timestamp", "1") setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_udp_timeout", "45") // Flush the table to start fresh err = h.ConntrackTableFlush(ConntrackTable) CheckErrorFail(t, err) // Create 5 udp startTime := time.Now() udpFlowCreateProg(t, 5, 2000, "127.0.0.10", 3000) // Fetch the conntrack table flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) CheckErrorFail(t, err) // Check that it is able to find the 5 flows created var found int for _, flow := range flows { if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && flow.Forward.DstPort == 3000 && (flow.Forward.SrcPort >= 2000 && flow.Forward.SrcPort <= 2005) { found++ flowStart := time.Unix(0, int64(flow.TimeStart)) if flowStart.Before(startTime) || flowStart.Sub(startTime) > time.Second { t.Error("Invalid conntrack entry start timestamp") } if flow.TimeStop != 0 { t.Error("Invalid conntrack entry stop timestamp") } // Expect at most one second to have already passed from the configured UDP timeout of 45secs. if flow.TimeOut < 44 || flow.TimeOut > 45 { t.Error("Invalid conntrack entry timeout") } } if flow.Forward.Bytes == 0 && flow.Forward.Packets == 0 && flow.Reverse.Bytes == 0 && flow.Reverse.Packets == 0 { t.Error("No traffic statistics are collected") } } if found != 5 { t.Fatalf("Found only %d flows over 5", found) } // Give a try also to the IPv6 version _, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET6) CheckErrorFail(t, err) // Switch back to the original namespace netns.Set(*origns) } // TestConntrackTableFlush test the conntrack table flushing // Creates some flows and then call the table flush func TestConntrackTableFlush(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI: Flow creation fails") } skipUnlessRoot(t) setUpNetlinkTestWithKModule(t, "nf_conntrack") setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4") } setUpNetlinkTestWithKModule(t, "nf_conntrack") // Creates a new namespace and bring up the loopback interface origns, ns, h := nsCreateAndEnter(t) defer netns.Set(*origns) defer origns.Close() defer ns.Close() defer runtime.UnlockOSThread() // Create 5 udp flows using netcat udpFlowCreateProg(t, 5, 3000, "127.0.0.10", 4000) // Fetch the conntrack table flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) CheckErrorFail(t, err) // Check that it is able to find the 5 flows created var found int for _, flow := range flows { if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && flow.Forward.DstPort == 4000 && (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) { found++ } } if found != 5 { t.Fatalf("Found only %d flows over 5", found) } // Flush the table err = h.ConntrackTableFlush(ConntrackTable) CheckErrorFail(t, err) // Fetch again the flows to validate the flush flows, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET) CheckErrorFail(t, err) // Check if it is still able to find the 5 flows created found = 0 for _, flow := range flows { if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && flow.Forward.DstPort == 4000 && (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) { found++ } } if found > 0 { t.Fatalf("Found %d flows, they should had been flushed", found) } // Switch back to the original namespace netns.Set(*origns) } // TestConntrackTableDelete tests the deletion with filter // Creates 2 group of flows then deletes only one group and validates the result func TestConntrackTableDelete(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI: Flow creation fails") } skipUnlessRoot(t) requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { requiredModules = append(requiredModules, "nf_conntrack_ipv4") } setUpNetlinkTestWithKModule(t, requiredModules...) // Creates a new namespace and bring up the loopback interface origns, ns, h := nsCreateAndEnter(t) defer netns.Set(*origns) defer origns.Close() defer ns.Close() defer runtime.UnlockOSThread() // Create 10 udp flows udpFlowCreateProg(t, 5, 5000, "127.0.0.10", 6000) udpFlowCreateProg(t, 5, 7000, "127.0.0.20", 8000) // Fetch the conntrack table flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) CheckErrorFail(t, err) // Check that it is able to find the 5 flows created for each group var groupA int var groupB int for _, flow := range flows { if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && flow.Forward.DstPort == 6000 && (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) { groupA++ } if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) && flow.Forward.DstPort == 8000 && (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) { groupB++ } } if groupA != 5 || groupB != 5 { t.Fatalf("Flow creation issue groupA:%d, groupB:%d", groupA, groupB) } // Create a filter to erase groupB flows filter := &ConntrackFilter{} filter.AddIP(ConntrackOrigDstIP, net.ParseIP("127.0.0.20")) filter.AddProtocol(17) filter.AddPort(ConntrackOrigDstPort, 8000) // Flush entries of groupB var deleted uint if deleted, err = h.ConntrackDeleteFilters(ConntrackTable, unix.AF_INET, filter); err != nil { t.Fatalf("Error during the erase: %s", err) } if deleted != 5 { t.Fatalf("Error deleted a wrong number of flows:%d instead of 5", deleted) } // Check again the table to verify that are gone flows, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET) CheckErrorFail(t, err) // Check if it is able to find the 5 flows of groupA but none of groupB groupA = 0 groupB = 0 for _, flow := range flows { if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && flow.Forward.DstPort == 6000 && (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) { groupA++ } if flow.Forward.Protocol == 17 && flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) && flow.Forward.DstPort == 8000 && (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) { groupB++ } } if groupA != 5 || groupB > 0 { t.Fatalf("Error during the erase groupA:%d, groupB:%d", groupA, groupB) } // Switch back to the original namespace netns.Set(*origns) } func TestConntrackFilter(t *testing.T) { var flowList []ConntrackFlow flowList = append(flowList, ConntrackFlow{ FamilyType: unix.AF_INET, Forward: IPTuple{ SrcIP: net.ParseIP("10.0.0.1"), DstIP: net.ParseIP("20.0.0.1"), SrcPort: 1000, DstPort: 2000, Protocol: 17, }, Reverse: IPTuple{ SrcIP: net.ParseIP("20.0.0.1"), DstIP: net.ParseIP("192.168.1.1"), SrcPort: 2000, DstPort: 1000, Protocol: 17, }, }, ConntrackFlow{ FamilyType: unix.AF_INET, Forward: IPTuple{ SrcIP: net.ParseIP("10.0.0.2"), DstIP: net.ParseIP("20.0.0.2"), SrcPort: 5000, DstPort: 6000, Protocol: 6, }, Reverse: IPTuple{ SrcIP: net.ParseIP("20.0.0.2"), DstIP: net.ParseIP("192.168.1.1"), SrcPort: 6000, DstPort: 5000, Protocol: 6, }, Labels: []byte{0, 0, 0, 0, 3, 4, 61, 141, 207, 170, 2, 0, 0, 0, 0, 0}, Zone: 200, }, ConntrackFlow{ FamilyType: unix.AF_INET6, Forward: IPTuple{ SrcIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"), DstIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"), SrcPort: 1000, DstPort: 2000, Protocol: 132, }, Reverse: IPTuple{ SrcIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"), DstIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"), SrcPort: 2000, DstPort: 1000, Protocol: 132, }, Zone: 200, }) // Empty filter v4Match, v6Match := applyFilter(flowList, &ConntrackFilter{}, &ConntrackFilter{}) if v4Match > 0 || v6Match > 0 { t.Fatalf("Error, empty filter cannot match, v4:%d, v6:%d", v4Match, v6Match) } // Filter errors // Adding same attribute should fail filter := &ConntrackFilter{} err := filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } if err := filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")); err == nil { t.Fatalf("Error, it should fail adding same attribute to the filter") } err = filter.AddProtocol(6) if err != nil { t.Fatalf("Unexpected error: %v", err) } if err := filter.AddProtocol(17); err == nil { t.Fatalf("Error, it should fail adding same attribute to the filter") } filter.AddPort(ConntrackOrigSrcPort, 80) if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { t.Fatalf("Error, it should fail adding same attribute to the filter") } // Can not add a Port filter without Layer 4 protocol filter = &ConntrackFilter{} if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { t.Fatalf("Error, it should fail adding a port filter without a protocol") } // Can not add a Port filter if the Layer 4 protocol does not support it filter = &ConntrackFilter{} err = filter.AddProtocol(47) if err != nil { t.Fatalf("Unexpected error: %v", err) } if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { t.Fatalf("Error, it should fail adding a port filter with a wrong protocol") } // Proto filter filterV4 := &ConntrackFilter{} err = filterV4.AddProtocol(6) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 := &ConntrackFilter{} err = filterV6.AddProtocol(132) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match for TCP:%d, UDP:%d", v4Match, v6Match) } // SrcIP filter filterV4 = &ConntrackFilter{} err = filterV4.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddIP(ConntrackOrigSrcIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // DstIp filter filterV4 = &ConntrackFilter{} err = filterV4.AddIP(ConntrackOrigDstIP, net.ParseIP("20.0.0.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddIP(ConntrackOrigDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // SrcIP for NAT filterV4 = &ConntrackFilter{} err = filterV4.AddIP(ConntrackReplySrcIP, net.ParseIP("20.0.0.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddIP(ConntrackReplySrcIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // DstIP for NAT filterV4 = &ConntrackFilter{} err = filterV4.AddIP(ConntrackReplyDstIP, net.ParseIP("192.168.1.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddIP(ConntrackReplyDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 0 { t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) } // AnyIp for Nat filterV4 = &ConntrackFilter{} err = filterV4.AddIP(ConntrackReplyAnyIP, net.ParseIP("192.168.1.1")) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddIP(ConntrackReplyAnyIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 1 { t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) } // SrcIPNet filter filterV4 = &ConntrackFilter{} ipNet, err := ParseIPNet("10.0.0.0/12") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddIPNet(ConntrackOrigSrcIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} ipNet, err = ParseIPNet("eeee:eeee:eeee:eeee::/64") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddIPNet(ConntrackOrigSrcIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // DstIpNet filter filterV4 = &ConntrackFilter{} ipNet, err = ParseIPNet("20.0.0.0/12") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddIPNet(ConntrackOrigDstIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddIPNet(ConntrackOrigDstIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // SrcIPNet for NAT filterV4 = &ConntrackFilter{} ipNet, err = ParseIPNet("20.0.0.0/12") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddIPNet(ConntrackReplySrcIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddIPNet(ConntrackReplySrcIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // DstIPNet for NAT filterV4 = &ConntrackFilter{} ipNet, err = ParseIPNet("192.168.0.0/12") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddIPNet(ConntrackReplyDstIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddIPNet(ConntrackReplyDstIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 0 { t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) } // AnyIpNet for Nat filterV4 = &ConntrackFilter{} ipNet, err = ParseIPNet("192.168.0.0/12") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddIPNet(ConntrackReplyAnyIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} ipNet, err = ParseIPNet("eeee:eeee:eeee:eeee::/64") if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddIPNet(ConntrackReplyAnyIP, ipNet) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 1 { t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) } // SrcPort filter filterV4 = &ConntrackFilter{} err = filterV4.AddProtocol(6) if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddPort(ConntrackOrigSrcPort, 5000) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddProtocol(132) if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddPort(ConntrackOrigSrcPort, 1000) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // DstPort filter filterV4 = &ConntrackFilter{} err = filterV4.AddProtocol(6) if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV4.AddPort(ConntrackOrigDstPort, 6000) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddProtocol(132) if err != nil { t.Fatalf("Unexpected error: %v", err) } err = filterV6.AddPort(ConntrackOrigDstPort, 2000) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 1 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } // Labels filter filterV4 = &ConntrackFilter{} var labels [][]byte labels = append(labels, []byte{3, 4, 61, 141, 207, 170}) labels = append(labels, []byte{0x2}) err = filterV4.AddLabels(ConntrackMatchLabels, labels) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} err = filterV6.AddLabels(ConntrackUnmatchLabels, labels) if err != nil { t.Fatalf("Unexpected error: %v", err) } v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 1 || v6Match != 0 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } filterV4 = &ConntrackFilter{} err = filterV4.AddZone(200) if err != nil { t.Fatalf("Unexpected error: %v", err) } filterV6 = &ConntrackFilter{} v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) if v4Match != 2 || v6Match != 0 { t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) } } func TestParseRawData(t *testing.T) { if nl.NativeEndian() == binary.BigEndian { t.Skip("testdata expect little-endian test executor") } os.Setenv("TZ", "") // print timestamps in UTC tests := []struct { testname string rawData []byte expConntrackFlow string }{ { testname: "UDP conntrack", rawData: []byte{ /* Nfgenmsg header */ 2, 0, 0, 0, /* >> nested CTA_TUPLE_ORIG */ 52, 0, 1, 128, /* >>>> nested CTA_TUPLE_IP */ 20, 0, 1, 128, /* >>>>>> CTA_IP_V4_SRC */ 8, 0, 1, 0, 192, 168, 0, 10, /* >>>>>> CTA_IP_V4_DST */ 8, 0, 2, 0, 192, 168, 0, 3, /* >>>>>> nested proto info */ 28, 0, 2, 128, /* >>>>>>>> CTA_PROTO_NUM */ 5, 0, 1, 0, 17, 0, 0, 0, /* >>>>>>>> CTA_PROTO_SRC_PORT */ 6, 0, 2, 0, 189, 1, 0, 0, /* >>>>>>>> CTA_PROTO_DST_PORT */ 6, 0, 3, 0, 0, 53, 0, 0, /* >> CTA_TUPLE_REPLY */ 52, 0, 2, 128, /* >>>> nested CTA_TUPLE_IP */ 20, 0, 1, 128, /* >>>>>> CTA_IP_V4_SRC */ 8, 0, 1, 0, 192, 168, 0, 3, /* >>>>>> CTA_IP_V4_DST */ 8, 0, 2, 0, 192, 168, 0, 10, /* >>>>>> nested proto info */ 28, 0, 2, 128, /* >>>>>>>> CTA_PROTO_NUM */ 5, 0, 1, 0, 17, 0, 0, 0, /* >>>>>>>> CTA_PROTO_SRC_PORT */ 6, 0, 2, 0, 0, 53, 0, 0, /* >>>>>>>> CTA_PROTO_DST_PORT */ 6, 0, 3, 0, 189, 1, 0, 0, /* >> CTA_STATUS */ 8, 0, 3, 0, 0, 0, 1, 138, /* >> CTA_MARK */ 8, 0, 8, 0, 0, 0, 0, 5, /* >> CTA_ID */ 8, 0, 12, 0, 81, 172, 253, 151, /* >> CTA_USE */ 8, 0, 11, 0, 0, 0, 0, 1, /* >> CTA_TIMEOUT */ 8, 0, 7, 0, 0, 0, 0, 32, /* >> nested CTA_COUNTERS_ORIG */ 28, 0, 9, 128, /* >>>> CTA_COUNTERS_PACKETS */ 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* >>>> CTA_COUNTERS_BYTES */ 12, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 55, /* >> nested CTA_COUNTERS_REPLY */ 28, 0, 10, 128, /* >>>> CTA_COUNTERS_PACKETS */ 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* >>>> CTA_COUNTERS_BYTES */ 12, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 71, /* >> nested CTA_TIMESTAMP */ 16, 0, 20, 128, /* >>>> CTA_TIMESTAMP_START */ 12, 0, 1, 0, 22, 134, 80, 142, 230, 127, 74, 166, /* >> CTA_LABELS */ 20, 0, 22, 0, 0, 0, 0, 0, 5, 0, 18, 172, 66, 2, 1, 0, 0, 0, 0, 0}, expConntrackFlow: "udp\t17 src=192.168.0.10 dst=192.168.0.3 sport=48385 dport=53 packets=1 bytes=55\t" + "src=192.168.0.3 dst=192.168.0.10 sport=53 dport=48385 packets=1 bytes=71 mark=0x5 labels=0x00000000050012ac4202010000000000 " + "start=2021-06-07 13:41:30.39632247 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=32(sec)", }, { testname: "TCP conntrack", rawData: []byte{ /* Nfgenmsg header */ 2, 0, 0, 0, /* >> nested CTA_TUPLE_ORIG */ 52, 0, 1, 128, /* >>>> nested CTA_TUPLE_IP */ 20, 0, 1, 128, /* >>>>>> CTA_IP_V4_SRC */ 8, 0, 1, 0, 192, 168, 0, 10, /* >>>>>> CTA_IP_V4_DST */ 8, 0, 2, 0, 192, 168, 77, 73, /* >>>>>> nested proto info */ 28, 0, 2, 128, /* >>>>>>>> CTA_PROTO_NUM */ 5, 0, 1, 0, 6, 0, 0, 0, /* >>>>>>>> CTA_PROTO_SRC_PORT */ 6, 0, 2, 0, 166, 129, 0, 0, /* >>>>>>>> CTA_PROTO_DST_PORT */ 6, 0, 3, 0, 13, 5, 0, 0, /* >> CTA_TUPLE_REPLY */ 52, 0, 2, 128, /* >>>> nested CTA_TUPLE_IP */ 20, 0, 1, 128, /* >>>>>> CTA_IP_V4_SRC */ 8, 0, 1, 0, 192, 168, 77, 73, /* >>>>>> CTA_IP_V4_DST */ 8, 0, 2, 0, 192, 168, 0, 10, /* >>>>>> nested proto info */ 28, 0, 2, 128, /* >>>>>>>> CTA_PROTO_NUM */ 5, 0, 1, 0, 6, 0, 0, 0, /* >>>>>>>> CTA_PROTO_SRC_PORT */ 6, 0, 2, 0, 13, 5, 0, 0, /* >>>>>>>> CTA_PROTO_DST_PORT */ 6, 0, 3, 0, 166, 129, 0, 0, /* >> CTA_STATUS */ 8, 0, 3, 0, 0, 0, 1, 142, /* >> CTA_MARK */ 8, 0, 8, 0, 0, 0, 0, 5, /* >> CTA_ID */ 8, 0, 12, 0, 177, 65, 179, 133, /* >> CTA_USE */ 8, 0, 11, 0, 0, 0, 0, 1, /* >> CTA_TIMEOUT */ 8, 0, 7, 0, 0, 0, 0, 152, /* >> CTA_PROTOINFO */ 48, 0, 4, 128, 44, 0, 1, 128, 5, 0, 1, 0, 8, 0, 0, 0, 5, 0, 2, 0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 0, 6, 0, 4, 0, 39, 0, 0, 0, 6, 0, 5, 0, 32, 0, 0, 0, /* >> nested CTA_COUNTERS_ORIG */ 28, 0, 9, 128, /* >>>> CTA_COUNTERS_PACKETS */ 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 11, /* >>>> CTA_COUNTERS_BYTES */ 12, 0, 2, 0, 0, 0, 0, 0, 0, 0, 7, 122, /* >> nested CTA_COUNTERS_REPLY */ 28, 0, 10, 128, /* >>>> CTA_COUNTERS_PACKETS */ 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 10, /* >>>> CTA_COUNTERS_BYTES */ 12, 0, 2, 0, 0, 0, 0, 0, 0, 0, 7, 66, /* >> CTA_ZONE */ 8, 0, 18, 0, 0, 100, 0, 0, /* >> nested CTA_TIMESTAMP */ 16, 0, 20, 128, /* >>>> CTA_TIMESTAMP_START */ 12, 0, 1, 0, 22, 134, 80, 175, 134, 10, 182, 221}, expConntrackFlow: "tcp\t6 src=192.168.0.10 dst=192.168.77.73 sport=42625 dport=3333 packets=11 bytes=1914\t" + "src=192.168.77.73 dst=192.168.0.10 sport=3333 dport=42625 packets=10 bytes=1858 mark=0x5 zone=100 " + "start=2021-06-07 13:43:50.511990493 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=152(sec)", }, } for _, test := range tests { t.Run(test.testname, func(t *testing.T) { conntrackFlow := parseRawData(test.rawData) if conntrackFlow.String() != test.expConntrackFlow { t.Errorf("expected conntrack flow:\n\t%q\ngot conntrack flow:\n\t%q", test.expConntrackFlow, conntrackFlow) } }) } } // TestConntrackUpdateV4 first tries to update a non-existant IPv4 conntrack and asserts that an error occurs. // It then creates a conntrack entry using and adjacent API method (ConntrackCreate), and attempts to update the value of the created conntrack. func TestConntrackUpdateV4(t *testing.T) { // Print timestamps in UTC os.Setenv("TZ", "") requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // Conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { requiredModules = append(requiredModules, "nf_conntrack_ipv4") } // Implicitly skips test if not root: nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) defer teardown() ns, err := netns.GetFromName(nsStr) if err != nil { t.Fatalf("couldn't get handle to generated namespace: %s", err) } h, err := NewHandleAt(ns, nl.FAMILY_V4) if err != nil { t.Fatalf("failed to create netlink handle: %s", err) } flow := ConntrackFlow{ FamilyType: FAMILY_V4, Forward: IPTuple{ SrcIP: net.IP{234,234,234,234}, DstIP: net.IP{123,123,123,123}, SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.IP{123,123,123,123}, DstIP: net.IP{234,234,234,234}, SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, // No point checking equivalence of timeout, but value must // be reasonable to allow for a potentially slow subsequent read. TimeOut: 100, Mark: 12, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_SYN_SENT2, }, } err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V4, &flow) if err == nil { t.Fatalf("expected an error to occur when trying to update a non-existant conntrack: %+v", flow) } err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V4, &flow) if err != nil { t.Fatalf("failed to insert conntrack: %s", err) } flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) if err != nil { t.Fatalf("failed to list conntracks following successful insert: %s", err) } filter := ConntrackFilter{ ipNetFilter: map[ConntrackFilterType]*net.IPNet{ ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), }, portFilter: map[ConntrackFilterType]uint16{ ConntrackOrigSrcPort: flow.Forward.SrcPort, ConntrackOrigDstPort: flow.Forward.DstPort, }, protoFilter:unix.IPPROTO_TCP, } var match *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { match = f break } } if match == nil { t.Fatalf("Didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) } checkFlowsEqual(t, &flow, match) checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) // Change the conntrack and update the kernel entry. flow.Mark = 10 flow.ProtoInfo = &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, } err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V4, &flow) if err != nil { t.Fatalf("failed to update conntrack with new mark: %s", err) } // Look for updated conntrack. flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) if err != nil { t.Fatalf("failed to list conntracks following successful update: %s", err) } var updatedMatch *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { updatedMatch = f break } } if updatedMatch == nil { t.Fatalf("Didn't find any matching conntrack entries for updated flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("Found entry in conntrack table matching updated flow: %+v labels=%+v", updatedMatch, updatedMatch.Labels) } checkFlowsEqual(t, &flow, updatedMatch) checkProtoInfosEqual(t, flow.ProtoInfo, updatedMatch.ProtoInfo) } // TestConntrackUpdateV6 first tries to update a non-existant IPv6 conntrack and asserts that an error occurs. // It then creates a conntrack entry using and adjacent API method (ConntrackCreate), and attempts to update the value of the created conntrack. func TestConntrackUpdateV6(t *testing.T) { // Print timestamps in UTC os.Setenv("TZ", "") requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // Conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { requiredModules = append(requiredModules, "nf_conntrack_ipv4") } // Implicitly skips test if not root: nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) defer teardown() ns, err := netns.GetFromName(nsStr) if err != nil { t.Fatalf("couldn't get handle to generated namespace: %s", err) } h, err := NewHandleAt(ns, nl.FAMILY_V6) if err != nil { t.Fatalf("failed to create netlink handle: %s", err) } flow := ConntrackFlow{ FamilyType: FAMILY_V6, Forward: IPTuple{ SrcIP: net.ParseIP("2001:db8::68"), DstIP: net.ParseIP("2001:db9::32"), SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.ParseIP("2001:db9::32"), DstIP: net.ParseIP("2001:db8::68"), SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, // No point checking equivalence of timeout, but value must // be reasonable to allow for a potentially slow subsequent read. TimeOut: 100, Mark: 12, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_SYN_SENT2, }, } err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V6, &flow) if err == nil { t.Fatalf("expected an error to occur when trying to update a non-existant conntrack: %+v", flow) } err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V6, &flow) if err != nil { t.Fatalf("failed to insert conntrack: %s", err) } flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) if err != nil { t.Fatalf("failed to list conntracks following successful insert: %s", err) } filter := ConntrackFilter{ ipNetFilter: map[ConntrackFilterType]*net.IPNet{ ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), }, portFilter: map[ConntrackFilterType]uint16{ ConntrackOrigSrcPort: flow.Forward.SrcPort, ConntrackOrigDstPort: flow.Forward.DstPort, }, protoFilter:unix.IPPROTO_TCP, } var match *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { match = f break } } if match == nil { t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) } checkFlowsEqual(t, &flow, match) checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) // Change the conntrack and update the kernel entry. flow.Mark = 10 flow.ProtoInfo = &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, } err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V6, &flow) if err != nil { t.Fatalf("failed to update conntrack with new mark: %s", err) } // Look for updated conntrack. flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) if err != nil { t.Fatalf("failed to list conntracks following successful update: %s", err) } var updatedMatch *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { updatedMatch = f break } } if updatedMatch == nil { t.Fatalf("didn't find any matching conntrack entries for updated flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("found entry in conntrack table matching updated flow: %+v labels=%+v", updatedMatch, updatedMatch.Labels) } checkFlowsEqual(t, &flow, updatedMatch) checkProtoInfosEqual(t, flow.ProtoInfo, updatedMatch.ProtoInfo) } func TestConntrackCreateV4(t *testing.T) { // Print timestamps in UTC os.Setenv("TZ", "") requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // Conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { requiredModules = append(requiredModules, "nf_conntrack_ipv4") } // Implicitly skips test if not root: nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) defer teardown() ns, err := netns.GetFromName(nsStr) if err != nil { t.Fatalf("couldn't get handle to generated namespace: %s", err) } h, err := NewHandleAt(ns, nl.FAMILY_V4) if err != nil { t.Fatalf("failed to create netlink handle: %s", err) } flow := ConntrackFlow{ FamilyType: FAMILY_V4, Forward: IPTuple{ SrcIP: net.IP{234,234,234,234}, DstIP: net.IP{123,123,123,123}, SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.IP{123,123,123,123}, DstIP: net.IP{234,234,234,234}, SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, // No point checking equivalence of timeout, but value must // be reasonable to allow for a potentially slow subsequent read. TimeOut: 100, Mark: 12, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, }, } err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V4, &flow) if err != nil { t.Fatalf("failed to insert conntrack: %s", err) } flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) if err != nil { t.Fatalf("failed to list conntracks following successful insert: %s", err) } filter := ConntrackFilter{ ipNetFilter: map[ConntrackFilterType]*net.IPNet{ ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), }, portFilter: map[ConntrackFilterType]uint16{ ConntrackOrigSrcPort: flow.Forward.SrcPort, ConntrackOrigDstPort: flow.Forward.DstPort, }, protoFilter:unix.IPPROTO_TCP, } var match *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { match = f break } } if match == nil { t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) } checkFlowsEqual(t, &flow, match) checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) } func TestConntrackCreateV6(t *testing.T) { // Print timestamps in UTC os.Setenv("TZ", "") requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} k, m, err := KernelVersion() if err != nil { t.Fatal(err) } // Conntrack l3proto was unified since 4.19 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f if k < 4 || k == 4 && m < 19 { requiredModules = append(requiredModules, "nf_conntrack_ipv4") } // Implicitly skips test if not root: nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) defer teardown() ns, err := netns.GetFromName(nsStr) if err != nil { t.Fatalf("couldn't get handle to generated namespace: %s", err) } h, err := NewHandleAt(ns, nl.FAMILY_V6) if err != nil { t.Fatalf("failed to create netlink handle: %s", err) } flow := ConntrackFlow{ FamilyType: FAMILY_V6, Forward: IPTuple{ SrcIP: net.ParseIP("2001:db8::68"), DstIP: net.ParseIP("2001:db9::32"), SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.ParseIP("2001:db9::32"), DstIP: net.ParseIP("2001:db8::68"), SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, // No point checking equivalence of timeout, but value must // be reasonable to allow for a potentially slow subsequent read. TimeOut: 100, Mark: 12, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, }, } err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V6, &flow) if err != nil { t.Fatalf("failed to insert conntrack: %s", err) } flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) if err != nil { t.Fatalf("failed to list conntracks following successful insert: %s", err) } filter := ConntrackFilter{ ipNetFilter: map[ConntrackFilterType]*net.IPNet{ ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), }, portFilter: map[ConntrackFilterType]uint16{ ConntrackOrigSrcPort: flow.Forward.SrcPort, ConntrackOrigDstPort: flow.Forward.DstPort, }, protoFilter:unix.IPPROTO_TCP, } var match *ConntrackFlow for _, f := range flows { if filter.MatchConntrackFlow(f) { match = f break } } if match == nil { t.Fatalf("Didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) } else { t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) } // Other fields are implicitly correct due to the filter/match logic. if match.Mark != flow.Mark { t.Logf("Matched kernel entry did not have correct mark. Kernel: %d, Expected: %d", flow.Mark, match.Mark) t.Fail() } checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) } // TestConntrackFlowToNlData generates a serialized representation of a // ConntrackFlow and runs the resulting bytes back through `parseRawData` to validate. func TestConntrackFlowToNlData(t *testing.T) { flowV4 := ConntrackFlow{ FamilyType: FAMILY_V4, Forward: IPTuple{ SrcIP: net.IP{234,234,234,234}, DstIP: net.IP{123,123,123,123}, SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.IP{123,123,123,123}, DstIP: net.IP{234,234,234,234}, SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, Mark: 5, TimeOut: 10, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, }, } flowV6 := ConntrackFlow { FamilyType: FAMILY_V6, Forward: IPTuple{ SrcIP: net.ParseIP("2001:db8::68"), DstIP: net.ParseIP("2001:db9::32"), SrcPort: 48385, DstPort: 53, Protocol: unix.IPPROTO_TCP, }, Reverse: IPTuple{ SrcIP: net.ParseIP("2001:db9::32"), DstIP: net.ParseIP("2001:db8::68"), SrcPort: 53, DstPort: 48385, Protocol: unix.IPPROTO_TCP, }, Mark: 5, TimeOut: 10, ProtoInfo: &ProtoInfoTCP{ State: nl.TCP_CONNTRACK_ESTABLISHED, }, } var bytesV4, bytesV6 []byte attrsV4, err := flowV4.toNlData() if err != nil { t.Fatalf("Error converting ConntrackFlow to netlink messages: %s", err) } // Mock nfgenmsg header bytesV4 = append(bytesV4, flowV4.FamilyType,0,0,0) for _, a := range attrsV4 { bytesV4 = append(bytesV4, a.Serialize()...) } attrsV6, err := flowV6.toNlData() if err != nil { t.Fatalf("Error converting ConntrackFlow to netlink messages: %s", err) } // Mock nfgenmsg header bytesV6 = append(bytesV6, flowV6.FamilyType,0,0,0) for _, a := range attrsV6 { bytesV6 = append(bytesV6, a.Serialize()...) } parsedFlowV4 := parseRawData(bytesV4) checkFlowsEqual(t, &flowV4, parsedFlowV4) checkProtoInfosEqual(t, flowV4.ProtoInfo, parsedFlowV4.ProtoInfo) parsedFlowV6 := parseRawData(bytesV6) checkFlowsEqual(t, &flowV6, parsedFlowV6) checkProtoInfosEqual(t, flowV6.ProtoInfo, parsedFlowV6.ProtoInfo) } func checkFlowsEqual(t *testing.T, f1, f2 *ConntrackFlow) { // No point checking timeout as it will differ between reads. // Timestart and timestop may also differ. if f1.FamilyType != f2.FamilyType { t.Logf("Conntrack flow FamilyTypes differ. Tuple1: %d, Tuple2: %d.\n", f1.FamilyType, f2.FamilyType) t.Fail() } if f1.Mark != f2.Mark { t.Logf("Conntrack flow Marks differ. Tuple1: %d, Tuple2: %d.\n", f1.Mark, f2.Mark) t.Fail() } if !tuplesEqual(f1.Forward, f2.Forward) { t.Logf("Forward tuples mismatch. Tuple1 forward flow: %+v, Tuple2 forward flow: %+v.\n", f1.Forward, f2.Forward) t.Fail() } if !tuplesEqual(f1.Reverse, f2.Reverse) { t.Logf("Reverse tuples mismatch. Tuple1 reverse flow: %+v, Tuple2 reverse flow: %+v.\n", f1.Reverse, f2.Reverse) t.Fail() } } func checkProtoInfosEqual(t *testing.T, p1, p2 ProtoInfo) { t.Logf("Checking protoinfo fields equal:\n\t p1: %+v\n\t p2: %+v", p1, p2) if !protoInfosEqual(p1, p2) { t.Logf("Protoinfo structs differ: P1: %+v, P2: %+v", p1, p2) t.Fail() } } func protoInfosEqual(p1, p2 ProtoInfo) bool { if p1 == nil { return p2 == nil } else if p2 != nil { return p1.Protocol() == p2.Protocol() } return false } func tuplesEqual(t1, t2 IPTuple) bool { if t1.Bytes != t2.Bytes { return false } if !t1.DstIP.Equal(t2.DstIP) { return false } if !t1.SrcIP.Equal(t2.SrcIP) { return false } if t1.DstPort != t2.DstPort { return false } if t1.SrcPort != t2.SrcPort { return false } if t1.Packets != t2.Packets { return false } if t1.Protocol != t2.Protocol { return false } return true } netlink-1.3.0/conntrack_unspecified.go000066400000000000000000000057341466216277000200720ustar00rootroot00000000000000// +build !linux package netlink // ConntrackTableType Conntrack table for the netlink operation type ConntrackTableType uint8 // InetFamily Family type type InetFamily uint8 // ConntrackFlow placeholder type ConntrackFlow struct{} // CustomConntrackFilter placeholder type CustomConntrackFilter struct{} // ConntrackFilter placeholder type ConntrackFilter struct{} // ConntrackTableList returns the flow list of a table of a specific family // conntrack -L [table] [options] List conntrack or expectation table func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { return nil, ErrNotImplemented } // ConntrackTableFlush flushes all the flows of a specified table // conntrack -F [table] Flush table // The flush operation applies to all the family types func ConntrackTableFlush(table ConntrackTableType) error { return ErrNotImplemented } // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter // conntrack -D [table] parameters Delete conntrack or expectation // // Deprecated: use [ConntrackDeleteFilter] instead. func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) { return 0, ErrNotImplemented } // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters // conntrack -D [table] parameters Delete conntrack or expectation func ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { return 0, ErrNotImplemented } // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed // conntrack -L [table] [options] List conntrack or expectation table func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { return nil, ErrNotImplemented } // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed // conntrack -F [table] Flush table // The flush operation applies to all the family types func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error { return ErrNotImplemented } // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed // conntrack -D [table] parameters Delete conntrack or expectation // // Deprecated: use [Handle.ConntrackDeleteFilters] instead. func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) { return 0, ErrNotImplemented } // ConntrackDeleteFilters deletes entries on the specified table matching any of the specified filters using the netlink handle passed // conntrack -D [table] parameters Delete conntrack or expectation func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters ...CustomConntrackFilter) (uint, error) { return 0, ErrNotImplemented } netlink-1.3.0/devlink_linux.go000066400000000000000000001014401466216277000163740ustar00rootroot00000000000000package netlink import ( "fmt" "net" "strings" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // DevlinkDevEswitchAttr represents device's eswitch attributes type DevlinkDevEswitchAttr struct { Mode string InlineMode string EncapMode string } // DevlinkDevAttrs represents device attributes type DevlinkDevAttrs struct { Eswitch DevlinkDevEswitchAttr } // DevlinkDevice represents device and its attributes type DevlinkDevice struct { BusName string DeviceName string Attrs DevlinkDevAttrs } // DevlinkPortFn represents port function and its attributes type DevlinkPortFn struct { HwAddr net.HardwareAddr State uint8 OpState uint8 } // DevlinkPortFnSetAttrs represents attributes to set type DevlinkPortFnSetAttrs struct { FnAttrs DevlinkPortFn HwAddrValid bool StateValid bool } // DevlinkPort represents port and its attributes type DevlinkPort struct { BusName string DeviceName string PortIndex uint32 PortType uint16 NetdeviceName string NetdevIfIndex uint32 RdmaDeviceName string PortFlavour uint16 Fn *DevlinkPortFn } type DevLinkPortAddAttrs struct { Controller uint32 SfNumber uint32 PortIndex uint32 PfNumber uint16 SfNumberValid bool PortIndexValid bool ControllerValid bool } // DevlinkDeviceInfo represents devlink info type DevlinkDeviceInfo struct { Driver string SerialNumber string BoardID string FwApp string FwAppBoundleID string FwAppName string FwBoundleID string FwMgmt string FwMgmtAPI string FwMgmtBuild string FwNetlist string FwNetlistBuild string FwPsidAPI string FwUndi string } // DevlinkResource represents a device resource type DevlinkResource struct { Name string ID uint64 Size uint64 SizeNew uint64 SizeMin uint64 SizeMax uint64 SizeGranularity uint64 PendingChange bool Unit uint8 SizeValid bool OCCValid bool OCCSize uint64 Parent *DevlinkResource Children []DevlinkResource } // parseAttributes parses provided Netlink Attributes and populates DevlinkResource, returns error if occured func (dlr *DevlinkResource) parseAttributes(attrs map[uint16]syscall.NetlinkRouteAttr) error { var attr syscall.NetlinkRouteAttr var ok bool // mandatory attributes attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_ID] if !ok { return fmt.Errorf("missing resource id") } dlr.ID = native.Uint64(attr.Value) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_NAME] if !ok { return fmt.Errorf("missing resource name") } dlr.Name = nl.BytesToString(attr.Value) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE] if !ok { return fmt.Errorf("missing resource size") } dlr.Size = native.Uint64(attr.Value) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_GRAN] if !ok { return fmt.Errorf("missing resource size granularity") } dlr.SizeGranularity = native.Uint64(attr.Value) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_UNIT] if !ok { return fmt.Errorf("missing resource unit") } dlr.Unit = uint8(attr.Value[0]) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_MIN] if !ok { return fmt.Errorf("missing resource size min") } dlr.SizeMin = native.Uint64(attr.Value) attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_MAX] if !ok { return fmt.Errorf("missing resource size max") } dlr.SizeMax = native.Uint64(attr.Value) // optional attributes attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_OCC] if ok { dlr.OCCSize = native.Uint64(attr.Value) dlr.OCCValid = true } attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_VALID] if ok { dlr.SizeValid = uint8(attr.Value[0]) != 0 } dlr.SizeNew = dlr.Size attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_SIZE_NEW] if ok { dlr.SizeNew = native.Uint64(attr.Value) } dlr.PendingChange = dlr.Size != dlr.SizeNew attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_LIST] if ok { // handle nested resoruces recursively subResources, err := nl.ParseRouteAttr(attr.Value) if err != nil { return err } for _, subresource := range subResources { resource := DevlinkResource{Parent: dlr} attrs, err := nl.ParseRouteAttrAsMap(subresource.Value) if err != nil { return err } err = resource.parseAttributes(attrs) if err != nil { return fmt.Errorf("failed to parse child resource, parent:%s. %w", dlr.Name, err) } dlr.Children = append(dlr.Children, resource) } } return nil } // DevlinkResources represents all devlink resources of a devlink device type DevlinkResources struct { Bus string Device string Resources []DevlinkResource } // parseAttributes parses provided Netlink Attributes and populates DevlinkResources, returns error if occured func (dlrs *DevlinkResources) parseAttributes(attrs map[uint16]syscall.NetlinkRouteAttr) error { var attr syscall.NetlinkRouteAttr var ok bool // Bus attr, ok = attrs[nl.DEVLINK_ATTR_BUS_NAME] if !ok { return fmt.Errorf("missing bus name") } dlrs.Bus = nl.BytesToString(attr.Value) // Device attr, ok = attrs[nl.DEVLINK_ATTR_DEV_NAME] if !ok { return fmt.Errorf("missing device name") } dlrs.Device = nl.BytesToString(attr.Value) // Resource List attr, ok = attrs[nl.DEVLINK_ATTR_RESOURCE_LIST] if !ok { return fmt.Errorf("missing resource list") } resourceAttrs, err := nl.ParseRouteAttr(attr.Value) if err != nil { return err } for _, resourceAttr := range resourceAttrs { resource := DevlinkResource{} attrs, err := nl.ParseRouteAttrAsMap(resourceAttr.Value) if err != nil { return err } err = resource.parseAttributes(attrs) if err != nil { return fmt.Errorf("failed to parse root resoruces, %w", err) } dlrs.Resources = append(dlrs.Resources, resource) } return nil } // DevlinkParam represents parameter of the device type DevlinkParam struct { Name string IsGeneric bool Type uint8 // possible values are in nl.DEVLINK_PARAM_TYPE_* constants Values []DevlinkParamValue } // DevlinkParamValue contains values of the parameter // Data field contains specific type which can be casted by unsing info from the DevlinkParam.Type field type DevlinkParamValue struct { rawData []byte Data interface{} CMODE uint8 // possible values are in nl.DEVLINK_PARAM_CMODE_* constants } // parseAttributes parses provided Netlink Attributes and populates DevlinkParam, returns error if occured func (dlp *DevlinkParam) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { var valuesList [][]syscall.NetlinkRouteAttr for _, attr := range attrs { switch attr.Attr.Type { case nl.DEVLINK_ATTR_PARAM: nattrs, err := nl.ParseRouteAttr(attr.Value) if err != nil { return err } for _, nattr := range nattrs { switch nattr.Attr.Type { case nl.DEVLINK_ATTR_PARAM_NAME: dlp.Name = nl.BytesToString(nattr.Value) case nl.DEVLINK_ATTR_PARAM_GENERIC: dlp.IsGeneric = true case nl.DEVLINK_ATTR_PARAM_TYPE: if len(nattr.Value) == 1 { dlp.Type = nattr.Value[0] } case nl.DEVLINK_ATTR_PARAM_VALUES_LIST: nnattrs, err := nl.ParseRouteAttr(nattr.Value) if err != nil { return err } valuesList = append(valuesList, nnattrs) } } } } for _, valAttr := range valuesList { v := DevlinkParamValue{} if err := v.parseAttributes(valAttr, dlp.Type); err != nil { return err } dlp.Values = append(dlp.Values, v) } return nil } func (dlpv *DevlinkParamValue) parseAttributes(attrs []syscall.NetlinkRouteAttr, paramType uint8) error { for _, attr := range attrs { nattrs, err := nl.ParseRouteAttr(attr.Value) if err != nil { return err } var rawData []byte for _, nattr := range nattrs { switch nattr.Attr.Type { case nl.DEVLINK_ATTR_PARAM_VALUE_DATA: rawData = nattr.Value case nl.DEVLINK_ATTR_PARAM_VALUE_CMODE: if len(nattr.Value) == 1 { dlpv.CMODE = nattr.Value[0] } } } switch paramType { case nl.DEVLINK_PARAM_TYPE_U8: dlpv.Data = uint8(0) if rawData != nil && len(rawData) == 1 { dlpv.Data = uint8(rawData[0]) } case nl.DEVLINK_PARAM_TYPE_U16: dlpv.Data = uint16(0) if rawData != nil { dlpv.Data = native.Uint16(rawData) } case nl.DEVLINK_PARAM_TYPE_U32: dlpv.Data = uint32(0) if rawData != nil { dlpv.Data = native.Uint32(rawData) } case nl.DEVLINK_PARAM_TYPE_STRING: dlpv.Data = "" if rawData != nil { dlpv.Data = nl.BytesToString(rawData) } case nl.DEVLINK_PARAM_TYPE_BOOL: dlpv.Data = rawData != nil } } return nil } func parseDevLinkDeviceList(msgs [][]byte) ([]*DevlinkDevice, error) { devices := make([]*DevlinkDevice, 0, len(msgs)) for _, m := range msgs { attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } dev := &DevlinkDevice{} if err = dev.parseAttributes(attrs); err != nil { return nil, err } devices = append(devices, dev) } return devices, nil } func eswitchStringToMode(modeName string) (uint16, error) { if modeName == "legacy" { return nl.DEVLINK_ESWITCH_MODE_LEGACY, nil } else if modeName == "switchdev" { return nl.DEVLINK_ESWITCH_MODE_SWITCHDEV, nil } else { return 0xffff, fmt.Errorf("invalid switchdev mode") } } func parseEswitchMode(mode uint16) string { var eswitchMode = map[uint16]string{ nl.DEVLINK_ESWITCH_MODE_LEGACY: "legacy", nl.DEVLINK_ESWITCH_MODE_SWITCHDEV: "switchdev", } if eswitchMode[mode] == "" { return "unknown" } else { return eswitchMode[mode] } } func parseEswitchInlineMode(inlinemode uint8) string { var eswitchInlineMode = map[uint8]string{ nl.DEVLINK_ESWITCH_INLINE_MODE_NONE: "none", nl.DEVLINK_ESWITCH_INLINE_MODE_LINK: "link", nl.DEVLINK_ESWITCH_INLINE_MODE_NETWORK: "network", nl.DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT: "transport", } if eswitchInlineMode[inlinemode] == "" { return "unknown" } else { return eswitchInlineMode[inlinemode] } } func parseEswitchEncapMode(encapmode uint8) string { var eswitchEncapMode = map[uint8]string{ nl.DEVLINK_ESWITCH_ENCAP_MODE_NONE: "disable", nl.DEVLINK_ESWITCH_ENCAP_MODE_BASIC: "enable", } if eswitchEncapMode[encapmode] == "" { return "unknown" } else { return eswitchEncapMode[encapmode] } } func (d *DevlinkDevice) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { for _, a := range attrs { switch a.Attr.Type { case nl.DEVLINK_ATTR_BUS_NAME: d.BusName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_DEV_NAME: d.DeviceName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_ESWITCH_MODE: d.Attrs.Eswitch.Mode = parseEswitchMode(native.Uint16(a.Value)) case nl.DEVLINK_ATTR_ESWITCH_INLINE_MODE: d.Attrs.Eswitch.InlineMode = parseEswitchInlineMode(uint8(a.Value[0])) case nl.DEVLINK_ATTR_ESWITCH_ENCAP_MODE: d.Attrs.Eswitch.EncapMode = parseEswitchEncapMode(uint8(a.Value[0])) } } return nil } func (dev *DevlinkDevice) parseEswitchAttrs(msgs [][]byte) { m := msgs[0] attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return } dev.parseAttributes(attrs) } func (h *Handle) getEswitchAttrs(family *GenlFamily, dev *DevlinkDevice) { msg := &nl.Genlmsg{ Command: nl.DEVLINK_CMD_ESWITCH_GET, Version: nl.GENL_DEVLINK_VERSION, } req := h.newNetlinkRequest(int(family.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK) req.AddData(msg) b := make([]byte, len(dev.BusName)+1) copy(b, dev.BusName) data := nl.NewRtAttr(nl.DEVLINK_ATTR_BUS_NAME, b) req.AddData(data) b = make([]byte, len(dev.DeviceName)+1) copy(b, dev.DeviceName) data = nl.NewRtAttr(nl.DEVLINK_ATTR_DEV_NAME, b) req.AddData(data) msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return } dev.parseEswitchAttrs(msgs) } // DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // otherwise returns an error code. func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) { f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) if err != nil { return nil, err } msg := &nl.Genlmsg{ Command: nl.DEVLINK_CMD_GET, Version: nl.GENL_DEVLINK_VERSION, } req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } devices, err := parseDevLinkDeviceList(msgs) if err != nil { return nil, err } for _, d := range devices { h.getEswitchAttrs(f, d) } return devices, nil } // DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // otherwise returns an error code. func DevLinkGetDeviceList() ([]*DevlinkDevice, error) { return pkgHandle.DevLinkGetDeviceList() } func parseDevlinkDevice(msgs [][]byte) (*DevlinkDevice, error) { m := msgs[0] attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } dev := &DevlinkDevice{} if err = dev.parseAttributes(attrs); err != nil { return nil, err } return dev, nil } func (h *Handle) createCmdReq(cmd uint8, bus string, device string) (*GenlFamily, *nl.NetlinkRequest, error) { f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) if err != nil { return nil, nil, err } msg := &nl.Genlmsg{ Command: cmd, Version: nl.GENL_DEVLINK_VERSION, } req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK) req.AddData(msg) b := make([]byte, len(bus)+1) copy(b, bus) data := nl.NewRtAttr(nl.DEVLINK_ATTR_BUS_NAME, b) req.AddData(data) b = make([]byte, len(device)+1) copy(b, device) data = nl.NewRtAttr(nl.DEVLINK_ATTR_DEV_NAME, b) req.AddData(data) return f, req, nil } // DevlinkGetDeviceByName provides a pointer to devlink device and nil error, // otherwise returns an error code. func (h *Handle) DevLinkGetDeviceByName(Bus string, Device string) (*DevlinkDevice, error) { f, req, err := h.createCmdReq(nl.DEVLINK_CMD_GET, Bus, Device) if err != nil { return nil, err } respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } dev, err := parseDevlinkDevice(respmsg) if err == nil { h.getEswitchAttrs(f, dev) } return dev, err } // DevlinkGetDeviceByName provides a pointer to devlink device and nil error, // otherwise returns an error code. func DevLinkGetDeviceByName(Bus string, Device string) (*DevlinkDevice, error) { return pkgHandle.DevLinkGetDeviceByName(Bus, Device) } // DevLinkSetEswitchMode sets eswitch mode if able to set successfully or // returns an error code. // Equivalent to: `devlink dev eswitch set $dev mode switchdev` // Equivalent to: `devlink dev eswitch set $dev mode legacy` func (h *Handle) DevLinkSetEswitchMode(Dev *DevlinkDevice, NewMode string) error { mode, err := eswitchStringToMode(NewMode) if err != nil { return err } _, req, err := h.createCmdReq(nl.DEVLINK_CMD_ESWITCH_SET, Dev.BusName, Dev.DeviceName) if err != nil { return err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_ESWITCH_MODE, nl.Uint16Attr(mode))) _, err = req.Execute(unix.NETLINK_GENERIC, 0) return err } // DevLinkSetEswitchMode sets eswitch mode if able to set successfully or // returns an error code. // Equivalent to: `devlink dev eswitch set $dev mode switchdev` // Equivalent to: `devlink dev eswitch set $dev mode legacy` func DevLinkSetEswitchMode(Dev *DevlinkDevice, NewMode string) error { return pkgHandle.DevLinkSetEswitchMode(Dev, NewMode) } func (port *DevlinkPort) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { for _, a := range attrs { switch a.Attr.Type { case nl.DEVLINK_ATTR_BUS_NAME: port.BusName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_DEV_NAME: port.DeviceName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_PORT_INDEX: port.PortIndex = native.Uint32(a.Value) case nl.DEVLINK_ATTR_PORT_TYPE: port.PortType = native.Uint16(a.Value) case nl.DEVLINK_ATTR_PORT_NETDEV_NAME: port.NetdeviceName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_PORT_NETDEV_IFINDEX: port.NetdevIfIndex = native.Uint32(a.Value) case nl.DEVLINK_ATTR_PORT_IBDEV_NAME: port.RdmaDeviceName = string(a.Value[:len(a.Value)-1]) case nl.DEVLINK_ATTR_PORT_FLAVOUR: port.PortFlavour = native.Uint16(a.Value) case nl.DEVLINK_ATTR_PORT_FUNCTION: port.Fn = &DevlinkPortFn{} for nested := range nl.ParseAttributes(a.Value) { switch nested.Type { case nl.DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR: port.Fn.HwAddr = nested.Value[:] case nl.DEVLINK_PORT_FN_ATTR_STATE: port.Fn.State = uint8(nested.Value[0]) case nl.DEVLINK_PORT_FN_ATTR_OPSTATE: port.Fn.OpState = uint8(nested.Value[0]) } } } } return nil } func parseDevLinkAllPortList(msgs [][]byte) ([]*DevlinkPort, error) { ports := make([]*DevlinkPort, 0, len(msgs)) for _, m := range msgs { attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } port := &DevlinkPort{} if err = port.parseAttributes(attrs); err != nil { return nil, err } ports = append(ports, port) } return ports, nil } // DevLinkGetPortList provides a pointer to devlink ports and nil error, // otherwise returns an error code. func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) { f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) if err != nil { return nil, err } msg := &nl.Genlmsg{ Command: nl.DEVLINK_CMD_PORT_GET, Version: nl.GENL_DEVLINK_VERSION, } req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } ports, err := parseDevLinkAllPortList(msgs) if err != nil { return nil, err } return ports, nil } // DevLinkGetPortList provides a pointer to devlink ports and nil error, // otherwise returns an error code. func DevLinkGetAllPortList() ([]*DevlinkPort, error) { return pkgHandle.DevLinkGetAllPortList() } func parseDevlinkPortMsg(msgs [][]byte) (*DevlinkPort, error) { m := msgs[0] attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } port := &DevlinkPort{} if err = port.parseAttributes(attrs); err != nil { return nil, err } return port, nil } // DevLinkGetPortByIndexprovides a pointer to devlink device and nil error, // otherwise returns an error code. func (h *Handle) DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint32) (*DevlinkPort, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PORT_GET, Bus, Device) if err != nil { return nil, err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_INDEX, nl.Uint32Attr(PortIndex))) respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } port, err := parseDevlinkPortMsg(respmsg) return port, err } // DevlinkGetDeviceResources returns devlink device resources func DevlinkGetDeviceResources(bus string, device string) (*DevlinkResources, error) { return pkgHandle.DevlinkGetDeviceResources(bus, device) } // DevlinkGetDeviceResources returns devlink device resources func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkResources, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_RESOURCE_DUMP, bus, device) if err != nil { return nil, err } respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } var resources DevlinkResources for _, m := range respmsg { attrs, err := nl.ParseRouteAttrAsMap(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } resources.parseAttributes(attrs) } return &resources, nil } // DevlinkGetDeviceParams returns parameters for devlink device // Equivalent to: `devlink dev param show /` func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) if err != nil { return nil, err } req.Flags |= unix.NLM_F_DUMP respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } var params []*DevlinkParam for _, m := range respmsg { attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } p := &DevlinkParam{} if err := p.parseAttributes(attrs); err != nil { return nil, err } params = append(params, p) } return params, nil } // DevlinkGetDeviceParams returns parameters for devlink device // Equivalent to: `devlink dev param show /` func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { return pkgHandle.DevlinkGetDeviceParams(bus, device) } // DevlinkGetDeviceParamByName returns specific parameter for devlink device // Equivalent to: `devlink dev param show / name ` func (h *Handle) DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) if err != nil { return nil, err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param))) respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } if len(respmsg) == 0 { return nil, fmt.Errorf("unexpected response") } attrs, err := nl.ParseRouteAttr(respmsg[0][nl.SizeofGenlmsg:]) if err != nil { return nil, err } p := &DevlinkParam{} if err := p.parseAttributes(attrs); err != nil { return nil, err } return p, nil } // DevlinkGetDeviceParamByName returns specific parameter for devlink device // Equivalent to: `devlink dev param show / name ` func DevlinkGetDeviceParamByName(bus string, device string, param string) (*DevlinkParam, error) { return pkgHandle.DevlinkGetDeviceParamByName(bus, device, param) } // DevlinkSetDeviceParam set specific parameter for devlink device // Equivalent to: `devlink dev param set / name cmode value ` // cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants // value argument should have one of the following types: uint8, uint16, uint32, string, bool func (h *Handle) DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error { // retrive the param type p, err := h.DevlinkGetDeviceParamByName(bus, device, param) if err != nil { return fmt.Errorf("failed to get device param: %v", err) } paramType := p.Type _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_SET, bus, device) if err != nil { return err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_TYPE, nl.Uint8Attr(paramType))) req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_NAME, nl.ZeroTerminated(param))) req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_CMODE, nl.Uint8Attr(cmode))) var valueAsBytes []byte switch paramType { case nl.DEVLINK_PARAM_TYPE_U8: v, ok := value.(uint8) if !ok { return fmt.Errorf("unepected value type required: uint8, actual: %T", value) } valueAsBytes = nl.Uint8Attr(v) case nl.DEVLINK_PARAM_TYPE_U16: v, ok := value.(uint16) if !ok { return fmt.Errorf("unepected value type required: uint16, actual: %T", value) } valueAsBytes = nl.Uint16Attr(v) case nl.DEVLINK_PARAM_TYPE_U32: v, ok := value.(uint32) if !ok { return fmt.Errorf("unepected value type required: uint32, actual: %T", value) } valueAsBytes = nl.Uint32Attr(v) case nl.DEVLINK_PARAM_TYPE_STRING: v, ok := value.(string) if !ok { return fmt.Errorf("unepected value type required: string, actual: %T", value) } valueAsBytes = nl.ZeroTerminated(v) case nl.DEVLINK_PARAM_TYPE_BOOL: v, ok := value.(bool) if !ok { return fmt.Errorf("unepected value type required: bool, actual: %T", value) } if v { valueAsBytes = []byte{} } default: return fmt.Errorf("unsupported parameter type: %d", paramType) } if valueAsBytes != nil { req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PARAM_VALUE_DATA, valueAsBytes)) } _, err = req.Execute(unix.NETLINK_GENERIC, 0) return err } // DevlinkSetDeviceParam set specific parameter for devlink device // Equivalent to: `devlink dev param set / name cmode value ` // cmode argument should contain valid cmode value as uint8, modes are define in nl.DEVLINK_PARAM_CMODE_* constants // value argument should have one of the following types: uint8, uint16, uint32, string, bool func DevlinkSetDeviceParam(bus string, device string, param string, cmode uint8, value interface{}) error { return pkgHandle.DevlinkSetDeviceParam(bus, device, param, cmode, value) } // DevLinkGetPortByIndex provides a pointer to devlink portand nil error, // otherwise returns an error code. func DevLinkGetPortByIndex(Bus string, Device string, PortIndex uint32) (*DevlinkPort, error) { return pkgHandle.DevLinkGetPortByIndex(Bus, Device, PortIndex) } // DevLinkPortAdd adds a devlink port and returns a port on success // otherwise returns nil port and an error code. func (h *Handle) DevLinkPortAdd(Bus string, Device string, Flavour uint16, Attrs DevLinkPortAddAttrs) (*DevlinkPort, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PORT_NEW, Bus, Device) if err != nil { return nil, err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FLAVOUR, nl.Uint16Attr(Flavour))) req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_PF_NUMBER, nl.Uint16Attr(Attrs.PfNumber))) if Flavour == nl.DEVLINK_PORT_FLAVOUR_PCI_SF && Attrs.SfNumberValid { req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_PCI_SF_NUMBER, nl.Uint32Attr(Attrs.SfNumber))) } if Attrs.PortIndexValid { req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_INDEX, nl.Uint32Attr(Attrs.PortIndex))) } if Attrs.ControllerValid { req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, nl.Uint32Attr(Attrs.Controller))) } respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } port, err := parseDevlinkPortMsg(respmsg) return port, err } // DevLinkPortAdd adds a devlink port and returns a port on success // otherwise returns nil port and an error code. func DevLinkPortAdd(Bus string, Device string, Flavour uint16, Attrs DevLinkPortAddAttrs) (*DevlinkPort, error) { return pkgHandle.DevLinkPortAdd(Bus, Device, Flavour, Attrs) } // DevLinkPortDel deletes a devlink port and returns success or error code. func (h *Handle) DevLinkPortDel(Bus string, Device string, PortIndex uint32) error { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PORT_DEL, Bus, Device) if err != nil { return err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_INDEX, nl.Uint32Attr(PortIndex))) _, err = req.Execute(unix.NETLINK_GENERIC, 0) return err } // DevLinkPortDel deletes a devlink port and returns success or error code. func DevLinkPortDel(Bus string, Device string, PortIndex uint32) error { return pkgHandle.DevLinkPortDel(Bus, Device, PortIndex) } // DevlinkPortFnSet sets one or more port function attributes specified by the attribute mask. // It returns 0 on success or error code. func (h *Handle) DevlinkPortFnSet(Bus string, Device string, PortIndex uint32, FnAttrs DevlinkPortFnSetAttrs) error { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PORT_SET, Bus, Device) if err != nil { return err } req.AddData(nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_INDEX, nl.Uint32Attr(PortIndex))) fnAttr := nl.NewRtAttr(nl.DEVLINK_ATTR_PORT_FUNCTION|unix.NLA_F_NESTED, nil) if FnAttrs.HwAddrValid { fnAttr.AddRtAttr(nl.DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR, []byte(FnAttrs.FnAttrs.HwAddr)) } if FnAttrs.StateValid { fnAttr.AddRtAttr(nl.DEVLINK_PORT_FN_ATTR_STATE, nl.Uint8Attr(FnAttrs.FnAttrs.State)) } req.AddData(fnAttr) _, err = req.Execute(unix.NETLINK_GENERIC, 0) return err } // DevlinkPortFnSet sets one or more port function attributes specified by the attribute mask. // It returns 0 on success or error code. func DevlinkPortFnSet(Bus string, Device string, PortIndex uint32, FnAttrs DevlinkPortFnSetAttrs) error { return pkgHandle.DevlinkPortFnSet(Bus, Device, PortIndex, FnAttrs) } // devlinkInfoGetter is function that is responsible for getting devlink info message // this is introduced for test purpose type devlinkInfoGetter func(bus, device string) ([]byte, error) // DevlinkGetDeviceInfoByName returns devlink info for selected device, // otherwise returns an error code. // Equivalent to: `devlink dev info $dev` func (h *Handle) DevlinkGetDeviceInfoByName(Bus string, Device string, getInfoMsg devlinkInfoGetter) (*DevlinkDeviceInfo, error) { info, err := h.DevlinkGetDeviceInfoByNameAsMap(Bus, Device, getInfoMsg) if err != nil { return nil, err } return parseInfoData(info), nil } // DevlinkGetDeviceInfoByName returns devlink info for selected device, // otherwise returns an error code. // Equivalent to: `devlink dev info $dev` func DevlinkGetDeviceInfoByName(Bus string, Device string) (*DevlinkDeviceInfo, error) { return pkgHandle.DevlinkGetDeviceInfoByName(Bus, Device, pkgHandle.getDevlinkInfoMsg) } // DevlinkGetDeviceInfoByNameAsMap returns devlink info for selected device as a map, // otherwise returns an error code. // Equivalent to: `devlink dev info $dev` func (h *Handle) DevlinkGetDeviceInfoByNameAsMap(Bus string, Device string, getInfoMsg devlinkInfoGetter) (map[string]string, error) { response, err := getInfoMsg(Bus, Device) if err != nil { return nil, err } info, err := parseInfoMsg(response) if err != nil { return nil, err } return info, nil } // DevlinkGetDeviceInfoByNameAsMap returns devlink info for selected device as a map, // otherwise returns an error code. // Equivalent to: `devlink dev info $dev` func DevlinkGetDeviceInfoByNameAsMap(Bus string, Device string) (map[string]string, error) { return pkgHandle.DevlinkGetDeviceInfoByNameAsMap(Bus, Device, pkgHandle.getDevlinkInfoMsg) } // GetDevlinkInfo returns devlink info for target device, // otherwise returns an error code. func (d *DevlinkDevice) GetDevlinkInfo() (*DevlinkDeviceInfo, error) { return pkgHandle.DevlinkGetDeviceInfoByName(d.BusName, d.DeviceName, pkgHandle.getDevlinkInfoMsg) } // GetDevlinkInfoAsMap returns devlink info for target device as a map, // otherwise returns an error code. func (d *DevlinkDevice) GetDevlinkInfoAsMap() (map[string]string, error) { return pkgHandle.DevlinkGetDeviceInfoByNameAsMap(d.BusName, d.DeviceName, pkgHandle.getDevlinkInfoMsg) } func (h *Handle) getDevlinkInfoMsg(bus, device string) ([]byte, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_INFO_GET, bus, device) if err != nil { return nil, err } response, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } if len(response) < 1 { return nil, fmt.Errorf("getDevlinkInfoMsg: message too short") } return response[0], nil } func parseInfoMsg(msg []byte) (map[string]string, error) { if len(msg) < nl.SizeofGenlmsg { return nil, fmt.Errorf("parseInfoMsg: message too short") } info := make(map[string]string) err := collectInfoData(msg[nl.SizeofGenlmsg:], info) if err != nil { return nil, err } return info, nil } func collectInfoData(msg []byte, data map[string]string) error { attrs, err := nl.ParseRouteAttr(msg) if err != nil { return err } for _, attr := range attrs { switch attr.Attr.Type { case nl.DEVLINK_ATTR_INFO_DRIVER_NAME: data["driver"] = parseInfoValue(attr.Value) case nl.DEVLINK_ATTR_INFO_SERIAL_NUMBER: data["serialNumber"] = parseInfoValue(attr.Value) case nl.DEVLINK_ATTR_INFO_VERSION_RUNNING, nl.DEVLINK_ATTR_INFO_VERSION_FIXED, nl.DEVLINK_ATTR_INFO_VERSION_STORED: key, value, err := getNestedInfoData(attr.Value) if err != nil { return err } data[key] = value } } if len(data) == 0 { return fmt.Errorf("collectInfoData: could not read attributes") } return nil } func getNestedInfoData(msg []byte) (string, string, error) { nestedAttrs, err := nl.ParseRouteAttr(msg) var key, value string if err != nil { return "", "", err } if len(nestedAttrs) != 2 { return "", "", fmt.Errorf("getNestedInfoData: too few attributes in nested structure") } for _, nestedAttr := range nestedAttrs { switch nestedAttr.Attr.Type { case nl.DEVLINK_ATTR_INFO_VERSION_NAME: key = parseInfoValue(nestedAttr.Value) case nl.DEVLINK_ATTR_INFO_VERSION_VALUE: value = parseInfoValue(nestedAttr.Value) } } if key == "" { return "", "", fmt.Errorf("getNestedInfoData: key not found") } if value == "" { return "", "", fmt.Errorf("getNestedInfoData: value not found") } return key, value, nil } func parseInfoData(data map[string]string) *DevlinkDeviceInfo { info := new(DevlinkDeviceInfo) for key, value := range data { switch key { case "driver": info.Driver = value case "serialNumber": info.SerialNumber = value case "board.id": info.BoardID = value case "fw.app": info.FwApp = value case "fw.app.bundle_id": info.FwAppBoundleID = value case "fw.app.name": info.FwAppName = value case "fw.bundle_id": info.FwBoundleID = value case "fw.mgmt": info.FwMgmt = value case "fw.mgmt.api": info.FwMgmtAPI = value case "fw.mgmt.build": info.FwMgmtBuild = value case "fw.netlist": info.FwNetlist = value case "fw.netlist.build": info.FwNetlistBuild = value case "fw.psid.api": info.FwPsidAPI = value case "fw.undi": info.FwUndi = value } } return info } func parseInfoValue(value []byte) string { v := strings.ReplaceAll(string(value), "\x00", "") return strings.TrimSpace(v) } netlink-1.3.0/devlink_test.go000066400000000000000000000277631466216277000162330ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "flag" "math/rand" "net" "os" "strconv" "testing" "github.com/vishvananda/netlink/nl" ) func TestDevLinkGetDeviceList(t *testing.T) { minKernelRequired(t, 4, 12) setUpNetlinkTestWithKModule(t, "devlink") _, err := DevLinkGetDeviceList() if err != nil { t.Fatal(err) } } func TestDevLinkGetDeviceByName(t *testing.T) { minKernelRequired(t, 4, 12) setUpNetlinkTestWithKModule(t, "devlink") _, err := DevLinkGetDeviceByName("foo", "bar") if err != nil { t.Fatal(err) } } func TestDevLinkSetEswitchMode(t *testing.T) { minKernelRequired(t, 4, 12) setUpNetlinkTestWithKModule(t, "devlink") dev, err := DevLinkGetDeviceByName("foo", "bar") if err != nil { t.Fatal(err) } err = DevLinkSetEswitchMode(dev, "switchdev") if err != nil { t.Fatal(err) } err = DevLinkSetEswitchMode(dev, "legacy") if err != nil { t.Fatal(err) } } func TestDevLinkGetAllPortList(t *testing.T) { minKernelRequired(t, 5, 4) ports, err := DevLinkGetAllPortList() if err != nil { t.Fatal(err) } t.Log("devlink port count = ", len(ports)) for _, port := range ports { t.Log(*port) } } func TestDevLinkAddDelSfPort(t *testing.T) { var addAttrs DevLinkPortAddAttrs minKernelRequired(t, 5, 13) if bus == "" || device == "" { t.Log("devlink bus and device are empty, skipping test") return } dev, err := DevLinkGetDeviceByName(bus, device) if err != nil { t.Fatal(err) return } addAttrs.SfNumberValid = true addAttrs.SfNumber = uint32(sfnum) addAttrs.PfNumber = 0 port, err2 := DevLinkPortAdd(dev.BusName, dev.DeviceName, 7, addAttrs) if err2 != nil { t.Fatal(err2) return } t.Log(*port) if port.Fn != nil { t.Log("function attributes = ", *port.Fn) } err2 = DevLinkPortDel(dev.BusName, dev.DeviceName, port.PortIndex) if err2 != nil { t.Fatal(err2) } } func TestDevLinkSfPortFnSet(t *testing.T) { var addAttrs DevLinkPortAddAttrs var stateAttr DevlinkPortFnSetAttrs minKernelRequired(t, 5, 12) if bus == "" || device == "" { t.Log("devlink bus and device are empty, skipping test") return } dev, err := DevLinkGetDeviceByName(bus, device) if err != nil { t.Fatal(err) return } addAttrs.SfNumberValid = true addAttrs.SfNumber = uint32(sfnum) addAttrs.PfNumber = 0 port, err2 := DevLinkPortAdd(dev.BusName, dev.DeviceName, 7, addAttrs) if err2 != nil { t.Fatal(err2) return } t.Log(*port) if port.Fn != nil { t.Log("function attributes = ", *port.Fn) } macAttr := DevlinkPortFnSetAttrs{ FnAttrs: DevlinkPortFn{ HwAddr: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, }, HwAddrValid: true, } err2 = DevlinkPortFnSet(dev.BusName, dev.DeviceName, port.PortIndex, macAttr) if err2 != nil { t.Log("function mac set err = ", err2) } stateAttr.FnAttrs.State = 1 stateAttr.StateValid = true err2 = DevlinkPortFnSet(dev.BusName, dev.DeviceName, port.PortIndex, stateAttr) if err2 != nil { t.Log("function state set err = ", err2) } port, err3 := DevLinkGetPortByIndex(dev.BusName, dev.DeviceName, port.PortIndex) if err3 == nil { t.Log(*port) t.Log(*port.Fn) } err2 = DevLinkPortDel(dev.BusName, dev.DeviceName, port.PortIndex) if err2 != nil { t.Fatal(err2) } } var bus string var device string var sfnum uint func init() { flag.StringVar(&bus, "bus", "", "devlink device bus name") flag.StringVar(&device, "device", "", "devlink device devicename") flag.UintVar(&sfnum, "sfnum", 0, "devlink port sfnumber") } func TestDevlinkGetDeviceInfoByNameAsMap(t *testing.T) { info, err := pkgHandle.DevlinkGetDeviceInfoByNameAsMap("pci", "0000:00:00.0", mockDevlinkInfoGetter) if err != nil { t.Fatal(err) } testInfo := devlinkTestInfoParesd() for k, v := range info { if testInfo[k] != v { t.Fatal("Value", v, "retrieved for key", k, "is not equal to", testInfo[k]) } } } func TestDevlinkGetDeviceInfoByName(t *testing.T) { info, err := pkgHandle.DevlinkGetDeviceInfoByName("pci", "0000:00:00.0", mockDevlinkInfoGetter) if err != nil { t.Fatal(err) } testInfo := parseInfoData(devlinkTestInfoParesd()) if !areInfoStructsEqual(info, testInfo) { t.Fatal("Info structures are not equal") } } func TestDevlinkGetDeviceInfoByNameAsMapFail(t *testing.T) { _, err := pkgHandle.DevlinkGetDeviceInfoByNameAsMap("pci", "0000:00:00.0", mockDevlinkInfoGetterEmpty) if err == nil { t.Fatal() } } func TestDevlinkGetDeviceInfoByNameFail(t *testing.T) { _, err := pkgHandle.DevlinkGetDeviceInfoByName("pci", "0000:00:00.0", mockDevlinkInfoGetterEmpty) if err == nil { t.Fatal() } } func mockDevlinkInfoGetter(bus, device string) ([]byte, error) { return devlinkInfo(), nil } func mockDevlinkInfoGetterEmpty(bus, device string) ([]byte, error) { return []byte{}, nil } func devlinkInfo() []byte { return []byte{51, 1, 0, 0, 8, 0, 1, 0, 112, 99, 105, 0, 17, 0, 2, 0, 48, 48, 48, 48, 58, 56, 52, 58, 48, 48, 46, 48, 0, 0, 0, 0, 8, 0, 98, 0, 105, 99, 101, 0, 28, 0, 99, 0, 51, 48, 45, 56, 57, 45, 97, 51, 45, 102, 102, 45, 102, 102, 45, 99, 97, 45, 48, 53, 45, 54, 56, 0, 36, 0, 100, 0, 13, 0, 103, 0, 98, 111, 97, 114, 100, 46, 105, 100, 0, 0, 0, 0, 15, 0, 104, 0, 75, 56, 53, 53, 56, 53, 45, 48, 48, 48, 0, 0, 28, 0, 101, 0, 12, 0, 103, 0, 102, 119, 46, 109, 103, 109, 116, 0, 10, 0, 104, 0, 53, 46, 52, 46, 53, 0, 0, 0, 28, 0, 101, 0, 16, 0, 103, 0, 102, 119, 46, 109, 103, 109, 116, 46, 97, 112, 105, 0, 8, 0, 104, 0, 49, 46, 55, 0, 40, 0, 101, 0, 18, 0, 103, 0, 102, 119, 46, 109, 103, 109, 116, 46, 98, 117, 105, 108, 100, 0, 0, 0, 15, 0, 104, 0, 48, 120, 51, 57, 49, 102, 55, 54, 52, 48, 0, 0, 32, 0, 101, 0, 12, 0, 103, 0, 102, 119, 46, 117, 110, 100, 105, 0, 13, 0, 104, 0, 49, 46, 50, 56, 57, 56, 46, 48, 0, 0, 0, 0, 32, 0, 101, 0, 16, 0, 103, 0, 102, 119, 46, 112, 115, 105, 100, 46, 97, 112, 105, 0, 9, 0, 104, 0, 50, 46, 52, 50, 0, 0, 0, 0, 40, 0, 101, 0, 17, 0, 103, 0, 102, 119, 46, 98, 117, 110, 100, 108, 101, 95, 105, 100, 0, 0, 0, 0, 15, 0, 104, 0, 48, 120, 56, 48, 48, 48, 55, 48, 54, 98, 0, 0, 48, 0, 101, 0, 16, 0, 103, 0, 102, 119, 46, 97, 112, 112, 46, 110, 97, 109, 101, 0, 27, 0, 104, 0, 73, 67, 69, 32, 79, 83, 32, 68, 101, 102, 97, 117, 108, 116, 32, 80, 97, 99, 107, 97, 103, 101, 0, 0, 32, 0, 101, 0, 11, 0, 103, 0, 102, 119, 46, 97, 112, 112, 0, 0, 13, 0, 104, 0, 49, 46, 51, 46, 50, 52, 46, 48, 0, 0, 0, 0, 44, 0, 101, 0, 21, 0, 103, 0, 102, 119, 46, 97, 112, 112, 46, 98, 117, 110, 100, 108, 101, 95, 105, 100, 0, 0, 0, 0, 15, 0, 104, 0, 48, 120, 99, 48, 48, 48, 48, 48, 48, 49, 0, 0, 44, 0, 101, 0, 15, 0, 103, 0, 102, 119, 46, 110, 101, 116, 108, 105, 115, 116, 0, 0, 21, 0, 104, 0, 50, 46, 52, 48, 46, 50, 48, 48, 48, 45, 51, 46, 49, 54, 46, 48, 0, 0, 0, 0, 44, 0, 101, 0, 21, 0, 103, 0, 102, 119, 46, 110, 101, 116, 108, 105, 115, 116, 46, 98, 117, 105, 108, 100, 0, 0, 0, 0, 15, 0, 104, 0, 48, 120, 54, 55, 54, 97, 52, 56, 57, 100, 0, 0} } func devlinkTestInfoParesd() map[string]string { return map[string]string{ "board.id": "K85585-000", "fw.app": "1.3.24.0", "fw.app.bundle_id": "0xc0000001", "fw.app.name": "ICE OS Default Package", "fw.bundle_id": "0x8000706b", "fw.mgmt": "5.4.5", "fw.mgmt.api": "1.7", "fw.mgmt.build": "0x391f7640", "fw.netlist": "2.40.2000-3.16.0", "fw.netlist.build": "0x676a489d", "fw.psid.api": "2.42", "fw.undi": "1.2898.0", "driver": "ice", "serialNumber": "30-89-a3-ff-ff-ca-05-68", } } func areInfoStructsEqual(first *DevlinkDeviceInfo, second *DevlinkDeviceInfo) bool { if first.FwApp != second.FwApp || first.FwAppBoundleID != second.FwAppBoundleID || first.FwAppName != second.FwAppName || first.FwBoundleID != second.FwBoundleID || first.FwMgmt != second.FwMgmt || first.FwMgmtAPI != second.FwMgmtAPI || first.FwMgmtBuild != second.FwMgmtBuild || first.FwNetlist != second.FwNetlist || first.FwNetlistBuild != second.FwNetlistBuild || first.FwPsidAPI != second.FwPsidAPI || first.BoardID != second.BoardID || first.FwUndi != second.FwUndi || first.Driver != second.Driver || first.SerialNumber != second.SerialNumber { return false } return true } func TestDevlinkGetDeviceResources(t *testing.T) { minKernelRequired(t, 5, 11) tearDown := setUpNetlinkTestWithKModule(t, "devlink") defer tearDown() if bus == "" || device == "" { //TODO: setup netdevsim device instead of getting device from flags t.Log("devlink bus and device are empty, skipping test") t.SkipNow() } res, err := DevlinkGetDeviceResources(bus, device) if err != nil { t.Fatalf("failed to get device(%s/%s) resources. %s", bus, device, err) } if res.Bus != bus || res.Device != device { t.Fatalf("missmatching bus/device") } t.Logf("Resources: %+v", res) } // devlink device parameters can be tested with netdevsim // function will create netdevsim/netdevsim virtual device that can be used for testing // netdevsim module should be loaded to run devlink param tests func setupDevlinkDeviceParamTest(t *testing.T) (string, string, func()) { t.Helper() skipUnlessRoot(t) skipUnlessKModuleLoaded(t, "netdevsim") testDevID := strconv.Itoa(1000 + rand.Intn(1000)) err := os.WriteFile("/sys/bus/netdevsim/new_device", []byte(testDevID), 0755) if err != nil { t.Fatalf("can't create netdevsim test device %s: %v", testDevID, err) } return "netdevsim", "netdevsim" + testDevID, func() { _ = os.WriteFile("/sys/bus/netdevsim/del_device", []byte(testDevID), 0755) } } func TestDevlinkGetDeviceParams(t *testing.T) { busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) defer cleanupFunc() params, err := DevlinkGetDeviceParams(busName, deviceName) if err != nil { t.Fatalf("failed to get device(%s/%s) parameters. %s", busName, deviceName, err) } if len(params) == 0 { t.Fatal("parameters list is empty") } for _, p := range params { validateDeviceParams(t, p) } } func TestDevlinkGetDeviceParamByName(t *testing.T) { busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) defer cleanupFunc() param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs") if err != nil { t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err) } validateDeviceParams(t, param) } func TestDevlinkSetDeviceParam(t *testing.T) { busName, deviceName, cleanupFunc := setupDevlinkDeviceParamTest(t) defer cleanupFunc() err := DevlinkSetDeviceParam(busName, deviceName, "max_macs", nl.DEVLINK_PARAM_CMODE_DRIVERINIT, uint32(8)) if err != nil { t.Fatalf("failed to set max_macs for device(%s/%s): %s", busName, deviceName, err) } param, err := DevlinkGetDeviceParamByName(busName, deviceName, "max_macs") if err != nil { t.Fatalf("failed to get device(%s/%s) parameter max_macs. %s", busName, deviceName, err) } validateDeviceParams(t, param) v, ok := param.Values[0].Data.(uint32) if !ok { t.Fatalf("unexpected value") } if v != uint32(8) { t.Fatalf("value not set") } } func validateDeviceParams(t *testing.T, p *DevlinkParam) { if p.Name == "" { t.Fatal("Name field not set") } if p.Name == "max_macs" && !p.IsGeneric { t.Fatal("IsGeneric should be true for generic parameter") } // test1 is a driver-specific parameter in netdevsim device, check should // also path on HW devices if p.Name == "test1" && p.IsGeneric { t.Fatal("IsGeneric should be false for driver-specific parameter") } switch p.Type { case nl.DEVLINK_PARAM_TYPE_U8, nl.DEVLINK_PARAM_TYPE_U16, nl.DEVLINK_PARAM_TYPE_U32, nl.DEVLINK_PARAM_TYPE_STRING, nl.DEVLINK_PARAM_TYPE_BOOL: default: t.Fatal("Type has unexpected value") } if len(p.Values) == 0 { t.Fatal("Values are not set") } for _, v := range p.Values { switch v.CMODE { case nl.DEVLINK_PARAM_CMODE_RUNTIME, nl.DEVLINK_PARAM_CMODE_DRIVERINIT, nl.DEVLINK_PARAM_CMODE_PERMANENT: default: t.Fatal("CMODE has unexpected value") } if p.Name == "max_macs" { _, ok := v.Data.(uint32) if !ok { t.Fatalf("value max_macs has wrong type: %T, expected: uint32", v.Data) } } if p.Name == "test1" { _, ok := v.Data.(bool) if !ok { t.Fatalf("value test1 has wrong type: %T, expected: bool", v.Data) } } } } netlink-1.3.0/filter.go000066400000000000000000000217561466216277000150210ustar00rootroot00000000000000package netlink import ( "fmt" "net" ) type Filter interface { Attrs() *FilterAttrs Type() string } // FilterAttrs represents a netlink filter. A filter is associated with a link, // has a handle and a parent. The root filter of a device should have a // parent == HANDLE_ROOT. type FilterAttrs struct { LinkIndex int Handle uint32 Parent uint32 Priority uint16 // lower is higher priority Protocol uint16 // unix.ETH_P_* Chain *uint32 } func (q FilterAttrs) String() string { return fmt.Sprintf("{LinkIndex: %d, Handle: %s, Parent: %s, Priority: %d, Protocol: %d}", q.LinkIndex, HandleStr(q.Handle), HandleStr(q.Parent), q.Priority, q.Protocol) } type TcAct int32 const ( TC_ACT_EXT_SHIFT = 28 TC_ACT_EXT_VAL_MASK = (1 << TC_ACT_EXT_SHIFT) - 1 ) const ( TC_ACT_UNSPEC TcAct = -1 TC_ACT_OK TcAct = 0 TC_ACT_RECLASSIFY TcAct = 1 TC_ACT_SHOT TcAct = 2 TC_ACT_PIPE TcAct = 3 TC_ACT_STOLEN TcAct = 4 TC_ACT_QUEUED TcAct = 5 TC_ACT_REPEAT TcAct = 6 TC_ACT_REDIRECT TcAct = 7 TC_ACT_JUMP TcAct = 0x10000000 ) func getTcActExt(local int32) int32 { return local << TC_ACT_EXT_SHIFT } func getTcActGotoChain() TcAct { return TcAct(getTcActExt(2)) } func getTcActExtOpcode(combined int32) int32 { return combined & (^TC_ACT_EXT_VAL_MASK) } func TcActExtCmp(combined int32, opcode int32) bool { return getTcActExtOpcode(combined) == opcode } func (a TcAct) String() string { switch a { case TC_ACT_UNSPEC: return "unspec" case TC_ACT_OK: return "ok" case TC_ACT_RECLASSIFY: return "reclassify" case TC_ACT_SHOT: return "shot" case TC_ACT_PIPE: return "pipe" case TC_ACT_STOLEN: return "stolen" case TC_ACT_QUEUED: return "queued" case TC_ACT_REPEAT: return "repeat" case TC_ACT_REDIRECT: return "redirect" case TC_ACT_JUMP: return "jump" } if TcActExtCmp(int32(a), int32(getTcActGotoChain())) { return "goto" } return fmt.Sprintf("0x%x", int32(a)) } type TcPolAct int32 const ( TC_POLICE_UNSPEC TcPolAct = TcPolAct(TC_ACT_UNSPEC) TC_POLICE_OK TcPolAct = TcPolAct(TC_ACT_OK) TC_POLICE_RECLASSIFY TcPolAct = TcPolAct(TC_ACT_RECLASSIFY) TC_POLICE_SHOT TcPolAct = TcPolAct(TC_ACT_SHOT) TC_POLICE_PIPE TcPolAct = TcPolAct(TC_ACT_PIPE) ) func (a TcPolAct) String() string { switch a { case TC_POLICE_UNSPEC: return "unspec" case TC_POLICE_OK: return "ok" case TC_POLICE_RECLASSIFY: return "reclassify" case TC_POLICE_SHOT: return "shot" case TC_POLICE_PIPE: return "pipe" } return fmt.Sprintf("0x%x", int32(a)) } type ActionAttrs struct { Index int Capab int Action TcAct Refcnt int Bindcnt int Statistics *ActionStatistic Timestamp *ActionTimestamp } func (q ActionAttrs) String() string { return fmt.Sprintf("{Index: %d, Capab: %x, Action: %s, Refcnt: %d, Bindcnt: %d}", q.Index, q.Capab, q.Action.String(), q.Refcnt, q.Bindcnt) } type ActionTimestamp struct { Installed uint64 LastUsed uint64 Expires uint64 FirstUsed uint64 } func (t ActionTimestamp) String() string { return fmt.Sprintf("Installed %d LastUsed %d Expires %d FirstUsed %d", t.Installed, t.LastUsed, t.Expires, t.FirstUsed) } type ActionStatistic ClassStatistics // Action represents an action in any supported filter. type Action interface { Attrs() *ActionAttrs Type() string } type GenericAction struct { ActionAttrs Chain int32 } func (action *GenericAction) Type() string { return "generic" } func (action *GenericAction) Attrs() *ActionAttrs { return &action.ActionAttrs } type BpfAction struct { ActionAttrs Fd int Name string } func (action *BpfAction) Type() string { return "bpf" } func (action *BpfAction) Attrs() *ActionAttrs { return &action.ActionAttrs } type ConnmarkAction struct { ActionAttrs Zone uint16 } func (action *ConnmarkAction) Type() string { return "connmark" } func (action *ConnmarkAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewConnmarkAction() *ConnmarkAction { return &ConnmarkAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_PIPE, }, } } type CsumUpdateFlags uint32 const ( TCA_CSUM_UPDATE_FLAG_IPV4HDR CsumUpdateFlags = 1 TCA_CSUM_UPDATE_FLAG_ICMP CsumUpdateFlags = 2 TCA_CSUM_UPDATE_FLAG_IGMP CsumUpdateFlags = 4 TCA_CSUM_UPDATE_FLAG_TCP CsumUpdateFlags = 8 TCA_CSUM_UPDATE_FLAG_UDP CsumUpdateFlags = 16 TCA_CSUM_UPDATE_FLAG_UDPLITE CsumUpdateFlags = 32 TCA_CSUM_UPDATE_FLAG_SCTP CsumUpdateFlags = 64 ) type CsumAction struct { ActionAttrs UpdateFlags CsumUpdateFlags } func (action *CsumAction) Type() string { return "csum" } func (action *CsumAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewCsumAction() *CsumAction { return &CsumAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_PIPE, }, } } type MirredAct uint8 func (a MirredAct) String() string { switch a { case TCA_EGRESS_REDIR: return "egress redir" case TCA_EGRESS_MIRROR: return "egress mirror" case TCA_INGRESS_REDIR: return "ingress redir" case TCA_INGRESS_MIRROR: return "ingress mirror" } return "unknown" } const ( TCA_EGRESS_REDIR MirredAct = 1 /* packet redirect to EGRESS*/ TCA_EGRESS_MIRROR MirredAct = 2 /* mirror packet to EGRESS */ TCA_INGRESS_REDIR MirredAct = 3 /* packet redirect to INGRESS*/ TCA_INGRESS_MIRROR MirredAct = 4 /* mirror packet to INGRESS */ ) type MirredAction struct { ActionAttrs MirredAction MirredAct Ifindex int } func (action *MirredAction) Type() string { return "mirred" } func (action *MirredAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewMirredAction(redirIndex int) *MirredAction { return &MirredAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_STOLEN, }, MirredAction: TCA_EGRESS_REDIR, Ifindex: redirIndex, } } type TunnelKeyAct int8 const ( TCA_TUNNEL_KEY_SET TunnelKeyAct = 1 // set tunnel key TCA_TUNNEL_KEY_UNSET TunnelKeyAct = 2 // unset tunnel key ) type TunnelKeyAction struct { ActionAttrs Action TunnelKeyAct SrcAddr net.IP DstAddr net.IP KeyID uint32 DestPort uint16 } func (action *TunnelKeyAction) Type() string { return "tunnel_key" } func (action *TunnelKeyAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewTunnelKeyAction() *TunnelKeyAction { return &TunnelKeyAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_PIPE, }, } } type SkbEditAction struct { ActionAttrs QueueMapping *uint16 PType *uint16 Priority *uint32 Mark *uint32 Mask *uint32 } func (action *SkbEditAction) Type() string { return "skbedit" } func (action *SkbEditAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewSkbEditAction() *SkbEditAction { return &SkbEditAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_PIPE, }, } } type PoliceAction struct { ActionAttrs Rate uint32 // in byte per second Burst uint32 // in byte RCellLog int Mtu uint32 Mpu uint16 // in byte PeakRate uint32 // in byte per second PCellLog int AvRate uint32 // in byte per second Overhead uint16 LinkLayer int ExceedAction TcPolAct NotExceedAction TcPolAct } func (action *PoliceAction) Type() string { return "police" } func (action *PoliceAction) Attrs() *ActionAttrs { return &action.ActionAttrs } func NewPoliceAction() *PoliceAction { return &PoliceAction{ RCellLog: -1, PCellLog: -1, LinkLayer: 1, // ETHERNET ExceedAction: TC_POLICE_RECLASSIFY, NotExceedAction: TC_POLICE_OK, } } // MatchAll filters match all packets type MatchAll struct { FilterAttrs ClassId uint32 Actions []Action } func (filter *MatchAll) Attrs() *FilterAttrs { return &filter.FilterAttrs } func (filter *MatchAll) Type() string { return "matchall" } type FwFilter struct { FilterAttrs ClassId uint32 InDev string Mask uint32 Police *PoliceAction Actions []Action } func (filter *FwFilter) Attrs() *FilterAttrs { return &filter.FilterAttrs } func (filter *FwFilter) Type() string { return "fw" } type BpfFilter struct { FilterAttrs ClassId uint32 Fd int Name string DirectAction bool Id int Tag string } func (filter *BpfFilter) Type() string { return "bpf" } func (filter *BpfFilter) Attrs() *FilterAttrs { return &filter.FilterAttrs } // GenericFilter filters represent types that are not currently understood // by this netlink library. type GenericFilter struct { FilterAttrs FilterType string } func (filter *GenericFilter) Attrs() *FilterAttrs { return &filter.FilterAttrs } func (filter *GenericFilter) Type() string { return filter.FilterType } type PeditAction struct { ActionAttrs Proto uint8 SrcMacAddr net.HardwareAddr DstMacAddr net.HardwareAddr SrcIP net.IP DstIP net.IP SrcPort uint16 DstPort uint16 } func (p *PeditAction) Attrs() *ActionAttrs { return &p.ActionAttrs } func (p *PeditAction) Type() string { return "pedit" } func NewPeditAction() *PeditAction { return &PeditAction{ ActionAttrs: ActionAttrs{ Action: TC_ACT_PIPE, }, } } netlink-1.3.0/filter_linux.go000066400000000000000000001016571466216277000162370ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "encoding/hex" "errors" "fmt" "net" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // Constants used in TcU32Sel.Flags. const ( TC_U32_TERMINAL = nl.TC_U32_TERMINAL TC_U32_OFFSET = nl.TC_U32_OFFSET TC_U32_VAROFFSET = nl.TC_U32_VAROFFSET TC_U32_EAT = nl.TC_U32_EAT ) // Sel of the U32 filters that contains multiple TcU32Key. This is the type // alias and the frontend representation of nl.TcU32Sel. It is serialized into // canonical nl.TcU32Sel with the appropriate endianness. type TcU32Sel = nl.TcU32Sel // TcU32Key contained of Sel in the U32 filters. This is the type alias and the // frontend representation of nl.TcU32Key. It is serialized into chanonical // nl.TcU32Sel with the appropriate endianness. type TcU32Key = nl.TcU32Key // U32 filters on many packet related properties type U32 struct { FilterAttrs ClassId uint32 Divisor uint32 // Divisor MUST be power of 2. Hash uint32 Link uint32 RedirIndex int Sel *TcU32Sel Actions []Action Police *PoliceAction } func (filter *U32) Attrs() *FilterAttrs { return &filter.FilterAttrs } func (filter *U32) Type() string { return "u32" } type Flower struct { FilterAttrs DestIP net.IP DestIPMask net.IPMask SrcIP net.IP SrcIPMask net.IPMask EthType uint16 EncDestIP net.IP EncDestIPMask net.IPMask EncSrcIP net.IP EncSrcIPMask net.IPMask EncDestPort uint16 EncKeyId uint32 SkipHw bool SkipSw bool IPProto *nl.IPProto DestPort uint16 SrcPort uint16 Actions []Action } func (filter *Flower) Attrs() *FilterAttrs { return &filter.FilterAttrs } func (filter *Flower) Type() string { return "flower" } func (filter *Flower) encodeIP(parent *nl.RtAttr, ip net.IP, mask net.IPMask, v4Type, v6Type int, v4MaskType, v6MaskType int) { ipType := v4Type maskType := v4MaskType encodeMask := mask if mask == nil { encodeMask = net.CIDRMask(32, 32) } v4IP := ip.To4() if v4IP == nil { ipType = v6Type maskType = v6MaskType if mask == nil { encodeMask = net.CIDRMask(128, 128) } } else { ip = v4IP } parent.AddRtAttr(ipType, ip) parent.AddRtAttr(maskType, encodeMask) } func (filter *Flower) encode(parent *nl.RtAttr) error { if filter.EthType != 0 { parent.AddRtAttr(nl.TCA_FLOWER_KEY_ETH_TYPE, htons(filter.EthType)) } if filter.SrcIP != nil { filter.encodeIP(parent, filter.SrcIP, filter.SrcIPMask, nl.TCA_FLOWER_KEY_IPV4_SRC, nl.TCA_FLOWER_KEY_IPV6_SRC, nl.TCA_FLOWER_KEY_IPV4_SRC_MASK, nl.TCA_FLOWER_KEY_IPV6_SRC_MASK) } if filter.DestIP != nil { filter.encodeIP(parent, filter.DestIP, filter.DestIPMask, nl.TCA_FLOWER_KEY_IPV4_DST, nl.TCA_FLOWER_KEY_IPV6_DST, nl.TCA_FLOWER_KEY_IPV4_DST_MASK, nl.TCA_FLOWER_KEY_IPV6_DST_MASK) } if filter.EncSrcIP != nil { filter.encodeIP(parent, filter.EncSrcIP, filter.EncSrcIPMask, nl.TCA_FLOWER_KEY_ENC_IPV4_SRC, nl.TCA_FLOWER_KEY_ENC_IPV6_SRC, nl.TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK, nl.TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK) } if filter.EncDestIP != nil { filter.encodeIP(parent, filter.EncDestIP, filter.EncSrcIPMask, nl.TCA_FLOWER_KEY_ENC_IPV4_DST, nl.TCA_FLOWER_KEY_ENC_IPV6_DST, nl.TCA_FLOWER_KEY_ENC_IPV4_DST_MASK, nl.TCA_FLOWER_KEY_ENC_IPV6_DST_MASK) } if filter.EncDestPort != 0 { parent.AddRtAttr(nl.TCA_FLOWER_KEY_ENC_UDP_DST_PORT, htons(filter.EncDestPort)) } if filter.EncKeyId != 0 { parent.AddRtAttr(nl.TCA_FLOWER_KEY_ENC_KEY_ID, htonl(filter.EncKeyId)) } if filter.IPProto != nil { ipproto := *filter.IPProto parent.AddRtAttr(nl.TCA_FLOWER_KEY_IP_PROTO, ipproto.Serialize()) if filter.SrcPort != 0 { switch ipproto { case nl.IPPROTO_TCP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_TCP_SRC, htons(filter.SrcPort)) case nl.IPPROTO_UDP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_UDP_SRC, htons(filter.SrcPort)) case nl.IPPROTO_SCTP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_SCTP_SRC, htons(filter.SrcPort)) } } if filter.DestPort != 0 { switch ipproto { case nl.IPPROTO_TCP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_TCP_DST, htons(filter.DestPort)) case nl.IPPROTO_UDP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_UDP_DST, htons(filter.DestPort)) case nl.IPPROTO_SCTP: parent.AddRtAttr(nl.TCA_FLOWER_KEY_SCTP_DST, htons(filter.DestPort)) } } } var flags uint32 = 0 if filter.SkipHw { flags |= nl.TCA_CLS_FLAGS_SKIP_HW } if filter.SkipSw { flags |= nl.TCA_CLS_FLAGS_SKIP_SW } parent.AddRtAttr(nl.TCA_FLOWER_FLAGS, htonl(flags)) actionsAttr := parent.AddRtAttr(nl.TCA_FLOWER_ACT, nil) if err := EncodeActions(actionsAttr, filter.Actions); err != nil { return err } return nil } func (filter *Flower) decode(data []syscall.NetlinkRouteAttr) error { for _, datum := range data { switch datum.Attr.Type { case nl.TCA_FLOWER_KEY_ETH_TYPE: filter.EthType = ntohs(datum.Value) case nl.TCA_FLOWER_KEY_IPV4_SRC, nl.TCA_FLOWER_KEY_IPV6_SRC: filter.SrcIP = datum.Value case nl.TCA_FLOWER_KEY_IPV4_SRC_MASK, nl.TCA_FLOWER_KEY_IPV6_SRC_MASK: filter.SrcIPMask = datum.Value case nl.TCA_FLOWER_KEY_IPV4_DST, nl.TCA_FLOWER_KEY_IPV6_DST: filter.DestIP = datum.Value case nl.TCA_FLOWER_KEY_IPV4_DST_MASK, nl.TCA_FLOWER_KEY_IPV6_DST_MASK: filter.DestIPMask = datum.Value case nl.TCA_FLOWER_KEY_ENC_IPV4_SRC, nl.TCA_FLOWER_KEY_ENC_IPV6_SRC: filter.EncSrcIP = datum.Value case nl.TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK, nl.TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK: filter.EncSrcIPMask = datum.Value case nl.TCA_FLOWER_KEY_ENC_IPV4_DST, nl.TCA_FLOWER_KEY_ENC_IPV6_DST: filter.EncDestIP = datum.Value case nl.TCA_FLOWER_KEY_ENC_IPV4_DST_MASK, nl.TCA_FLOWER_KEY_ENC_IPV6_DST_MASK: filter.EncDestIPMask = datum.Value case nl.TCA_FLOWER_KEY_ENC_UDP_DST_PORT: filter.EncDestPort = ntohs(datum.Value) case nl.TCA_FLOWER_KEY_ENC_KEY_ID: filter.EncKeyId = ntohl(datum.Value) case nl.TCA_FLOWER_KEY_IP_PROTO: val := new(nl.IPProto) *val = nl.IPProto(datum.Value[0]) filter.IPProto = val case nl.TCA_FLOWER_KEY_TCP_SRC, nl.TCA_FLOWER_KEY_UDP_SRC, nl.TCA_FLOWER_KEY_SCTP_SRC: filter.SrcPort = ntohs(datum.Value) case nl.TCA_FLOWER_KEY_TCP_DST, nl.TCA_FLOWER_KEY_UDP_DST, nl.TCA_FLOWER_KEY_SCTP_DST: filter.DestPort = ntohs(datum.Value) case nl.TCA_FLOWER_ACT: tables, err := nl.ParseRouteAttr(datum.Value) if err != nil { return err } filter.Actions, err = parseActions(tables) if err != nil { return err } case nl.TCA_FLOWER_FLAGS: attr := nl.DeserializeUint32Bitfield(datum.Value) skipSw := attr.Value & nl.TCA_CLS_FLAGS_SKIP_HW skipHw := attr.Value & nl.TCA_CLS_FLAGS_SKIP_SW if skipSw != 0 { filter.SkipSw = true } if skipHw != 0 { filter.SkipHw = true } } } return nil } // FilterDel will delete a filter from the system. // Equivalent to: `tc filter del $filter` func FilterDel(filter Filter) error { return pkgHandle.FilterDel(filter) } // FilterDel will delete a filter from the system. // Equivalent to: `tc filter del $filter` func (h *Handle) FilterDel(filter Filter) error { return h.filterModify(filter, unix.RTM_DELTFILTER, 0) } // FilterAdd will add a filter to the system. // Equivalent to: `tc filter add $filter` func FilterAdd(filter Filter) error { return pkgHandle.FilterAdd(filter) } // FilterAdd will add a filter to the system. // Equivalent to: `tc filter add $filter` func (h *Handle) FilterAdd(filter Filter) error { return h.filterModify(filter, unix.RTM_NEWTFILTER, unix.NLM_F_CREATE|unix.NLM_F_EXCL) } // FilterReplace will replace a filter. // Equivalent to: `tc filter replace $filter` func FilterReplace(filter Filter) error { return pkgHandle.FilterReplace(filter) } // FilterReplace will replace a filter. // Equivalent to: `tc filter replace $filter` func (h *Handle) FilterReplace(filter Filter) error { return h.filterModify(filter, unix.RTM_NEWTFILTER, unix.NLM_F_CREATE) } func (h *Handle) filterModify(filter Filter, proto, flags int) error { req := h.newNetlinkRequest(proto, flags|unix.NLM_F_ACK) base := filter.Attrs() msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: int32(base.LinkIndex), Handle: base.Handle, Parent: base.Parent, Info: MakeHandle(base.Priority, nl.Swap16(base.Protocol)), } req.AddData(msg) if filter.Attrs().Chain != nil { req.AddData(nl.NewRtAttr(nl.TCA_CHAIN, nl.Uint32Attr(*filter.Attrs().Chain))) } req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(filter.Type()))) options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) switch filter := filter.(type) { case *U32: sel := filter.Sel if sel == nil { // match all sel = &nl.TcU32Sel{ Nkeys: 1, Flags: nl.TC_U32_TERMINAL, } sel.Keys = append(sel.Keys, nl.TcU32Key{}) } if native != networkOrder { // Copy TcU32Sel. cSel := *sel keys := make([]nl.TcU32Key, cap(sel.Keys)) copy(keys, sel.Keys) cSel.Keys = keys sel = &cSel // Handle the endianness of attributes sel.Offmask = native.Uint16(htons(sel.Offmask)) sel.Hmask = native.Uint32(htonl(sel.Hmask)) for i, key := range sel.Keys { sel.Keys[i].Mask = native.Uint32(htonl(key.Mask)) sel.Keys[i].Val = native.Uint32(htonl(key.Val)) } } sel.Nkeys = uint8(len(sel.Keys)) options.AddRtAttr(nl.TCA_U32_SEL, sel.Serialize()) if filter.ClassId != 0 { options.AddRtAttr(nl.TCA_U32_CLASSID, nl.Uint32Attr(filter.ClassId)) } if filter.Divisor != 0 { if (filter.Divisor-1)&filter.Divisor != 0 { return fmt.Errorf("illegal divisor %d. Must be a power of 2", filter.Divisor) } options.AddRtAttr(nl.TCA_U32_DIVISOR, nl.Uint32Attr(filter.Divisor)) } if filter.Hash != 0 { options.AddRtAttr(nl.TCA_U32_HASH, nl.Uint32Attr(filter.Hash)) } if filter.Link != 0 { options.AddRtAttr(nl.TCA_U32_LINK, nl.Uint32Attr(filter.Link)) } if filter.Police != nil { police := options.AddRtAttr(nl.TCA_U32_POLICE, nil) if err := encodePolice(police, filter.Police); err != nil { return err } } actionsAttr := options.AddRtAttr(nl.TCA_U32_ACT, nil) // backwards compatibility if filter.RedirIndex != 0 { filter.Actions = append([]Action{NewMirredAction(filter.RedirIndex)}, filter.Actions...) } if err := EncodeActions(actionsAttr, filter.Actions); err != nil { return err } case *FwFilter: if filter.Mask != 0 { b := make([]byte, 4) native.PutUint32(b, filter.Mask) options.AddRtAttr(nl.TCA_FW_MASK, b) } if filter.InDev != "" { options.AddRtAttr(nl.TCA_FW_INDEV, nl.ZeroTerminated(filter.InDev)) } if filter.Police != nil { police := options.AddRtAttr(nl.TCA_FW_POLICE, nil) if err := encodePolice(police, filter.Police); err != nil { return err } } if filter.ClassId != 0 { b := make([]byte, 4) native.PutUint32(b, filter.ClassId) options.AddRtAttr(nl.TCA_FW_CLASSID, b) } actionsAttr := options.AddRtAttr(nl.TCA_FW_ACT, nil) if err := EncodeActions(actionsAttr, filter.Actions); err != nil { return err } case *BpfFilter: var bpfFlags uint32 if filter.ClassId != 0 { options.AddRtAttr(nl.TCA_BPF_CLASSID, nl.Uint32Attr(filter.ClassId)) } if filter.Fd >= 0 { options.AddRtAttr(nl.TCA_BPF_FD, nl.Uint32Attr((uint32(filter.Fd)))) } if filter.Name != "" { options.AddRtAttr(nl.TCA_BPF_NAME, nl.ZeroTerminated(filter.Name)) } if filter.DirectAction { bpfFlags |= nl.TCA_BPF_FLAG_ACT_DIRECT } options.AddRtAttr(nl.TCA_BPF_FLAGS, nl.Uint32Attr(bpfFlags)) case *MatchAll: actionsAttr := options.AddRtAttr(nl.TCA_MATCHALL_ACT, nil) if err := EncodeActions(actionsAttr, filter.Actions); err != nil { return err } if filter.ClassId != 0 { options.AddRtAttr(nl.TCA_MATCHALL_CLASSID, nl.Uint32Attr(filter.ClassId)) } case *Flower: if err := filter.encode(options); err != nil { return err } } req.AddData(options) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // FilterList gets a list of filters in the system. // Equivalent to: `tc filter show`. // Generally returns nothing if link and parent are not specified. func FilterList(link Link, parent uint32) ([]Filter, error) { return pkgHandle.FilterList(link, parent) } // FilterList gets a list of filters in the system. // Equivalent to: `tc filter show`. // Generally returns nothing if link and parent are not specified. func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { req := h.newNetlinkRequest(unix.RTM_GETTFILTER, unix.NLM_F_DUMP) msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Parent: parent, } if link != nil { base := link.Attrs() h.ensureIndex(base) msg.Ifindex = int32(base.Index) } req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTFILTER) if err != nil { return nil, err } var res []Filter for _, m := range msgs { msg := nl.DeserializeTcMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } base := FilterAttrs{ LinkIndex: int(msg.Ifindex), Handle: msg.Handle, Parent: msg.Parent, } base.Priority, base.Protocol = MajorMinor(msg.Info) base.Protocol = nl.Swap16(base.Protocol) var filter Filter filterType := "" detailed := false for _, attr := range attrs { switch attr.Attr.Type { case nl.TCA_KIND: filterType = string(attr.Value[:len(attr.Value)-1]) switch filterType { case "u32": filter = &U32{} case "fw": filter = &FwFilter{} case "bpf": filter = &BpfFilter{} case "matchall": filter = &MatchAll{} case "flower": filter = &Flower{} default: filter = &GenericFilter{FilterType: filterType} } case nl.TCA_OPTIONS: data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } switch filterType { case "u32": detailed, err = parseU32Data(filter, data) if err != nil { return nil, err } case "fw": detailed, err = parseFwData(filter, data) if err != nil { return nil, err } case "bpf": detailed, err = parseBpfData(filter, data) if err != nil { return nil, err } case "matchall": detailed, err = parseMatchAllData(filter, data) if err != nil { return nil, err } case "flower": detailed, err = parseFlowerData(filter, data) if err != nil { return nil, err } default: detailed = true } case nl.TCA_CHAIN: val := new(uint32) *val = native.Uint32(attr.Value) base.Chain = val } } // only return the detailed version of the filter if detailed { *filter.Attrs() = base res = append(res, filter) } } return res, nil } func toTcGen(attrs *ActionAttrs, tcgen *nl.TcGen) { tcgen.Index = uint32(attrs.Index) tcgen.Capab = uint32(attrs.Capab) tcgen.Action = int32(attrs.Action) tcgen.Refcnt = int32(attrs.Refcnt) tcgen.Bindcnt = int32(attrs.Bindcnt) } func toAttrs(tcgen *nl.TcGen, attrs *ActionAttrs) { attrs.Index = int(tcgen.Index) attrs.Capab = int(tcgen.Capab) attrs.Action = TcAct(tcgen.Action) attrs.Refcnt = int(tcgen.Refcnt) attrs.Bindcnt = int(tcgen.Bindcnt) } func toTimeStamp(tcf *nl.Tcf) *ActionTimestamp { return &ActionTimestamp{ Installed: tcf.Install, LastUsed: tcf.LastUse, Expires: tcf.Expires, FirstUsed: tcf.FirstUse} } func encodePolice(attr *nl.RtAttr, action *PoliceAction) error { var rtab [256]uint32 var ptab [256]uint32 police := nl.TcPolice{} police.Index = uint32(action.Attrs().Index) police.Bindcnt = int32(action.Attrs().Bindcnt) police.Capab = uint32(action.Attrs().Capab) police.Refcnt = int32(action.Attrs().Refcnt) police.Rate.Rate = action.Rate police.PeakRate.Rate = action.PeakRate police.Action = int32(action.ExceedAction) if police.Rate.Rate != 0 { police.Rate.Mpu = action.Mpu police.Rate.Overhead = action.Overhead if CalcRtable(&police.Rate, rtab[:], action.RCellLog, action.Mtu, action.LinkLayer) < 0 { return errors.New("TBF: failed to calculate rate table") } police.Burst = Xmittime(uint64(police.Rate.Rate), action.Burst) } police.Mtu = action.Mtu if police.PeakRate.Rate != 0 { police.PeakRate.Mpu = action.Mpu police.PeakRate.Overhead = action.Overhead if CalcRtable(&police.PeakRate, ptab[:], action.PCellLog, action.Mtu, action.LinkLayer) < 0 { return errors.New("POLICE: failed to calculate peak rate table") } } attr.AddRtAttr(nl.TCA_POLICE_TBF, police.Serialize()) if police.Rate.Rate != 0 { attr.AddRtAttr(nl.TCA_POLICE_RATE, SerializeRtab(rtab)) } if police.PeakRate.Rate != 0 { attr.AddRtAttr(nl.TCA_POLICE_PEAKRATE, SerializeRtab(ptab)) } if action.AvRate != 0 { attr.AddRtAttr(nl.TCA_POLICE_AVRATE, nl.Uint32Attr(action.AvRate)) } if action.NotExceedAction != 0 { attr.AddRtAttr(nl.TCA_POLICE_RESULT, nl.Uint32Attr(uint32(action.NotExceedAction))) } return nil } func EncodeActions(attr *nl.RtAttr, actions []Action) error { tabIndex := int(nl.TCA_ACT_TAB) for _, action := range actions { switch action := action.(type) { default: return fmt.Errorf("unknown action type %s", action.Type()) case *PoliceAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("police")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) if err := encodePolice(aopts, action); err != nil { return err } case *MirredAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("mirred")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) mirred := nl.TcMirred{ Eaction: int32(action.MirredAction), Ifindex: uint32(action.Ifindex), } toTcGen(action.Attrs(), &mirred.TcGen) aopts.AddRtAttr(nl.TCA_MIRRED_PARMS, mirred.Serialize()) case *TunnelKeyAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("tunnel_key")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) tun := nl.TcTunnelKey{ Action: int32(action.Action), } toTcGen(action.Attrs(), &tun.TcGen) aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_PARMS, tun.Serialize()) if action.Action == TCA_TUNNEL_KEY_SET { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_KEY_ID, htonl(action.KeyID)) if v4 := action.SrcAddr.To4(); v4 != nil { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_IPV4_SRC, v4[:]) } else if v6 := action.SrcAddr.To16(); v6 != nil { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_IPV6_SRC, v6[:]) } else { return fmt.Errorf("invalid src addr %s for tunnel_key action", action.SrcAddr) } if v4 := action.DstAddr.To4(); v4 != nil { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_IPV4_DST, v4[:]) } else if v6 := action.DstAddr.To16(); v6 != nil { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_IPV6_DST, v6[:]) } else { return fmt.Errorf("invalid dst addr %s for tunnel_key action", action.DstAddr) } if action.DestPort != 0 { aopts.AddRtAttr(nl.TCA_TUNNEL_KEY_ENC_DST_PORT, htons(action.DestPort)) } } case *SkbEditAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("skbedit")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) skbedit := nl.TcSkbEdit{} toTcGen(action.Attrs(), &skbedit.TcGen) aopts.AddRtAttr(nl.TCA_SKBEDIT_PARMS, skbedit.Serialize()) if action.QueueMapping != nil { aopts.AddRtAttr(nl.TCA_SKBEDIT_QUEUE_MAPPING, nl.Uint16Attr(*action.QueueMapping)) } if action.Priority != nil { aopts.AddRtAttr(nl.TCA_SKBEDIT_PRIORITY, nl.Uint32Attr(*action.Priority)) } if action.PType != nil { aopts.AddRtAttr(nl.TCA_SKBEDIT_PTYPE, nl.Uint16Attr(*action.PType)) } if action.Mark != nil { aopts.AddRtAttr(nl.TCA_SKBEDIT_MARK, nl.Uint32Attr(*action.Mark)) } if action.Mask != nil { aopts.AddRtAttr(nl.TCA_SKBEDIT_MASK, nl.Uint32Attr(*action.Mask)) } case *ConnmarkAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("connmark")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) connmark := nl.TcConnmark{ Zone: action.Zone, } toTcGen(action.Attrs(), &connmark.TcGen) aopts.AddRtAttr(nl.TCA_CONNMARK_PARMS, connmark.Serialize()) case *CsumAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("csum")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) csum := nl.TcCsum{ UpdateFlags: uint32(action.UpdateFlags), } toTcGen(action.Attrs(), &csum.TcGen) aopts.AddRtAttr(nl.TCA_CSUM_PARMS, csum.Serialize()) case *BpfAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("bpf")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) gen := nl.TcGen{} toTcGen(action.Attrs(), &gen) aopts.AddRtAttr(nl.TCA_ACT_BPF_PARMS, gen.Serialize()) aopts.AddRtAttr(nl.TCA_ACT_BPF_FD, nl.Uint32Attr(uint32(action.Fd))) aopts.AddRtAttr(nl.TCA_ACT_BPF_NAME, nl.ZeroTerminated(action.Name)) case *GenericAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ table.AddRtAttr(nl.TCA_ACT_KIND, nl.ZeroTerminated("gact")) aopts := table.AddRtAttr(nl.TCA_ACT_OPTIONS, nil) gen := nl.TcGen{} toTcGen(action.Attrs(), &gen) aopts.AddRtAttr(nl.TCA_GACT_PARMS, gen.Serialize()) case *PeditAction: table := attr.AddRtAttr(tabIndex, nil) tabIndex++ pedit := nl.TcPedit{} if action.SrcMacAddr != nil { pedit.SetEthSrc(action.SrcMacAddr) } if action.DstMacAddr != nil { pedit.SetEthDst(action.DstMacAddr) } if action.SrcIP != nil { pedit.SetSrcIP(action.SrcIP) } if action.DstIP != nil { pedit.SetDstIP(action.DstIP) } if action.SrcPort != 0 { pedit.SetSrcPort(action.SrcPort, action.Proto) } if action.DstPort != 0 { pedit.SetDstPort(action.DstPort, action.Proto) } pedit.Encode(table) } } return nil } func parsePolice(data syscall.NetlinkRouteAttr, police *PoliceAction) { switch data.Attr.Type { case nl.TCA_POLICE_RESULT: police.NotExceedAction = TcPolAct(native.Uint32(data.Value[0:4])) case nl.TCA_POLICE_AVRATE: police.AvRate = native.Uint32(data.Value[0:4]) case nl.TCA_POLICE_TBF: p := *nl.DeserializeTcPolice(data.Value) police.ActionAttrs = ActionAttrs{} police.Attrs().Index = int(p.Index) police.Attrs().Bindcnt = int(p.Bindcnt) police.Attrs().Capab = int(p.Capab) police.Attrs().Refcnt = int(p.Refcnt) police.ExceedAction = TcPolAct(p.Action) police.Rate = p.Rate.Rate police.PeakRate = p.PeakRate.Rate police.Burst = Xmitsize(uint64(p.Rate.Rate), p.Burst) police.Mtu = p.Mtu police.LinkLayer = int(p.Rate.Linklayer) & nl.TC_LINKLAYER_MASK police.Overhead = p.Rate.Overhead } } func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { var actions []Action for _, table := range tables { var action Action var actionType string var actionnStatistic *ActionStatistic var actionTimestamp *ActionTimestamp aattrs, err := nl.ParseRouteAttr(table.Value) if err != nil { return nil, err } nextattr: for _, aattr := range aattrs { switch aattr.Attr.Type { case nl.TCA_KIND: actionType = string(aattr.Value[:len(aattr.Value)-1]) // only parse if the action is mirred or bpf switch actionType { case "mirred": action = &MirredAction{} case "bpf": action = &BpfAction{} case "connmark": action = &ConnmarkAction{} case "csum": action = &CsumAction{} case "gact": action = &GenericAction{} case "tunnel_key": action = &TunnelKeyAction{} case "skbedit": action = &SkbEditAction{} case "police": action = &PoliceAction{} case "pedit": action = &PeditAction{} default: break nextattr } case nl.TCA_OPTIONS: adata, err := nl.ParseRouteAttr(aattr.Value) if err != nil { return nil, err } for _, adatum := range adata { switch actionType { case "mirred": switch adatum.Attr.Type { case nl.TCA_MIRRED_PARMS: mirred := *nl.DeserializeTcMirred(adatum.Value) action.(*MirredAction).ActionAttrs = ActionAttrs{} toAttrs(&mirred.TcGen, action.Attrs()) action.(*MirredAction).Ifindex = int(mirred.Ifindex) action.(*MirredAction).MirredAction = MirredAct(mirred.Eaction) case nl.TCA_MIRRED_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "tunnel_key": switch adatum.Attr.Type { case nl.TCA_TUNNEL_KEY_PARMS: tun := *nl.DeserializeTunnelKey(adatum.Value) action.(*TunnelKeyAction).ActionAttrs = ActionAttrs{} toAttrs(&tun.TcGen, action.Attrs()) action.(*TunnelKeyAction).Action = TunnelKeyAct(tun.Action) case nl.TCA_TUNNEL_KEY_ENC_KEY_ID: action.(*TunnelKeyAction).KeyID = networkOrder.Uint32(adatum.Value[0:4]) case nl.TCA_TUNNEL_KEY_ENC_IPV6_SRC, nl.TCA_TUNNEL_KEY_ENC_IPV4_SRC: action.(*TunnelKeyAction).SrcAddr = adatum.Value[:] case nl.TCA_TUNNEL_KEY_ENC_IPV6_DST, nl.TCA_TUNNEL_KEY_ENC_IPV4_DST: action.(*TunnelKeyAction).DstAddr = adatum.Value[:] case nl.TCA_TUNNEL_KEY_ENC_DST_PORT: action.(*TunnelKeyAction).DestPort = ntohs(adatum.Value) case nl.TCA_TUNNEL_KEY_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "skbedit": switch adatum.Attr.Type { case nl.TCA_SKBEDIT_PARMS: skbedit := *nl.DeserializeSkbEdit(adatum.Value) action.(*SkbEditAction).ActionAttrs = ActionAttrs{} toAttrs(&skbedit.TcGen, action.Attrs()) case nl.TCA_SKBEDIT_MARK: mark := native.Uint32(adatum.Value[0:4]) action.(*SkbEditAction).Mark = &mark case nl.TCA_SKBEDIT_MASK: mask := native.Uint32(adatum.Value[0:4]) action.(*SkbEditAction).Mask = &mask case nl.TCA_SKBEDIT_PRIORITY: priority := native.Uint32(adatum.Value[0:4]) action.(*SkbEditAction).Priority = &priority case nl.TCA_SKBEDIT_PTYPE: ptype := native.Uint16(adatum.Value[0:2]) action.(*SkbEditAction).PType = &ptype case nl.TCA_SKBEDIT_QUEUE_MAPPING: mapping := native.Uint16(adatum.Value[0:2]) action.(*SkbEditAction).QueueMapping = &mapping case nl.TCA_SKBEDIT_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "bpf": switch adatum.Attr.Type { case nl.TCA_ACT_BPF_PARMS: gen := *nl.DeserializeTcGen(adatum.Value) toAttrs(&gen, action.Attrs()) case nl.TCA_ACT_BPF_FD: action.(*BpfAction).Fd = int(native.Uint32(adatum.Value[0:4])) case nl.TCA_ACT_BPF_NAME: action.(*BpfAction).Name = string(adatum.Value[:len(adatum.Value)-1]) case nl.TCA_ACT_BPF_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "connmark": switch adatum.Attr.Type { case nl.TCA_CONNMARK_PARMS: connmark := *nl.DeserializeTcConnmark(adatum.Value) action.(*ConnmarkAction).ActionAttrs = ActionAttrs{} toAttrs(&connmark.TcGen, action.Attrs()) action.(*ConnmarkAction).Zone = connmark.Zone case nl.TCA_CONNMARK_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "csum": switch adatum.Attr.Type { case nl.TCA_CSUM_PARMS: csum := *nl.DeserializeTcCsum(adatum.Value) action.(*CsumAction).ActionAttrs = ActionAttrs{} toAttrs(&csum.TcGen, action.Attrs()) action.(*CsumAction).UpdateFlags = CsumUpdateFlags(csum.UpdateFlags) case nl.TCA_CSUM_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "gact": switch adatum.Attr.Type { case nl.TCA_GACT_PARMS: gen := *nl.DeserializeTcGen(adatum.Value) toAttrs(&gen, action.Attrs()) if action.Attrs().Action.String() == "goto" { action.(*GenericAction).Chain = TC_ACT_EXT_VAL_MASK & gen.Action } case nl.TCA_GACT_TM: tcTs := nl.DeserializeTcf(adatum.Value) actionTimestamp = toTimeStamp(tcTs) } case "police": parsePolice(adatum, action.(*PoliceAction)) } } case nl.TCA_ACT_STATS: s, err := parseTcStats2(aattr.Value) if err != nil { return nil, err } actionnStatistic = (*ActionStatistic)(s) } } action.Attrs().Statistics = actionnStatistic action.Attrs().Timestamp = actionTimestamp actions = append(actions, action) } return actions, nil } func parseU32Data(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { u32 := filter.(*U32) detailed := false for _, datum := range data { switch datum.Attr.Type { case nl.TCA_U32_SEL: detailed = true sel := nl.DeserializeTcU32Sel(datum.Value) u32.Sel = sel if native != networkOrder { // Handle the endianness of attributes u32.Sel.Offmask = native.Uint16(htons(sel.Offmask)) u32.Sel.Hmask = native.Uint32(htonl(sel.Hmask)) for i, key := range u32.Sel.Keys { u32.Sel.Keys[i].Mask = native.Uint32(htonl(key.Mask)) u32.Sel.Keys[i].Val = native.Uint32(htonl(key.Val)) } } case nl.TCA_U32_ACT: tables, err := nl.ParseRouteAttr(datum.Value) if err != nil { return detailed, err } u32.Actions, err = parseActions(tables) if err != nil { return detailed, err } for _, action := range u32.Actions { if action, ok := action.(*MirredAction); ok { u32.RedirIndex = int(action.Ifindex) } } case nl.TCA_U32_POLICE: var police PoliceAction adata, _ := nl.ParseRouteAttr(datum.Value) for _, aattr := range adata { parsePolice(aattr, &police) } u32.Police = &police case nl.TCA_U32_CLASSID: u32.ClassId = native.Uint32(datum.Value) case nl.TCA_U32_DIVISOR: u32.Divisor = native.Uint32(datum.Value) case nl.TCA_U32_HASH: u32.Hash = native.Uint32(datum.Value) case nl.TCA_U32_LINK: u32.Link = native.Uint32(datum.Value) } } return detailed, nil } func parseFwData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { fw := filter.(*FwFilter) detailed := true for _, datum := range data { switch datum.Attr.Type { case nl.TCA_FW_MASK: fw.Mask = native.Uint32(datum.Value[0:4]) case nl.TCA_FW_CLASSID: fw.ClassId = native.Uint32(datum.Value[0:4]) case nl.TCA_FW_INDEV: fw.InDev = string(datum.Value[:len(datum.Value)-1]) case nl.TCA_FW_POLICE: var police PoliceAction adata, _ := nl.ParseRouteAttr(datum.Value) for _, aattr := range adata { parsePolice(aattr, &police) } fw.Police = &police case nl.TCA_FW_ACT: tables, err := nl.ParseRouteAttr(datum.Value) if err != nil { return detailed, err } fw.Actions, err = parseActions(tables) if err != nil { return detailed, err } } } return detailed, nil } func parseBpfData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { bpf := filter.(*BpfFilter) detailed := true for _, datum := range data { switch datum.Attr.Type { case nl.TCA_BPF_FD: bpf.Fd = int(native.Uint32(datum.Value[0:4])) case nl.TCA_BPF_NAME: bpf.Name = string(datum.Value[:len(datum.Value)-1]) case nl.TCA_BPF_CLASSID: bpf.ClassId = native.Uint32(datum.Value[0:4]) case nl.TCA_BPF_FLAGS: flags := native.Uint32(datum.Value[0:4]) if (flags & nl.TCA_BPF_FLAG_ACT_DIRECT) != 0 { bpf.DirectAction = true } case nl.TCA_BPF_ID: bpf.Id = int(native.Uint32(datum.Value[0:4])) case nl.TCA_BPF_TAG: bpf.Tag = hex.EncodeToString(datum.Value) } } return detailed, nil } func parseMatchAllData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { matchall := filter.(*MatchAll) detailed := true for _, datum := range data { switch datum.Attr.Type { case nl.TCA_MATCHALL_CLASSID: matchall.ClassId = native.Uint32(datum.Value[0:4]) case nl.TCA_MATCHALL_ACT: tables, err := nl.ParseRouteAttr(datum.Value) if err != nil { return detailed, err } matchall.Actions, err = parseActions(tables) if err != nil { return detailed, err } } } return detailed, nil } func parseFlowerData(filter Filter, data []syscall.NetlinkRouteAttr) (bool, error) { return true, filter.(*Flower).decode(data) } func AlignToAtm(size uint) uint { var linksize, cells int cells = int(size / nl.ATM_CELL_PAYLOAD) if (size % nl.ATM_CELL_PAYLOAD) > 0 { cells++ } linksize = cells * nl.ATM_CELL_SIZE return uint(linksize) } func AdjustSize(sz uint, mpu uint, linklayer int) uint { if sz < mpu { sz = mpu } switch linklayer { case nl.LINKLAYER_ATM: return AlignToAtm(sz) default: return sz } } func CalcRtable(rate *nl.TcRateSpec, rtab []uint32, cellLog int, mtu uint32, linklayer int) int { bps := rate.Rate mpu := rate.Mpu var sz uint if mtu == 0 { mtu = 2047 } if cellLog < 0 { cellLog = 0 for (mtu >> uint(cellLog)) > 255 { cellLog++ } } for i := 0; i < 256; i++ { sz = AdjustSize(uint((i+1)< 2*65536 { t.Fatalf("Unexpected socket receive buffer size: %d (expected around %d)", s, 65536) } } } func verifySockTimeVal(t *testing.T, fd int, tv unix.Timeval) { var ( tr unix.Timeval v = uint32(0x10) ) _, _, errno := unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd), unix.SOL_SOCKET, unix.SO_SNDTIMEO, uintptr(unsafe.Pointer(&tr)), uintptr(unsafe.Pointer(&v)), 0) if errno != 0 { t.Fatal(errno) } if tr.Sec != tv.Sec || tr.Usec != tv.Usec { t.Fatalf("Unexpected timeout value read: %v. Expected: %v", tr, tv) } _, _, errno = unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, uintptr(unsafe.Pointer(&tr)), uintptr(unsafe.Pointer(&v)), 0) if errno != 0 { t.Fatal(errno) } if tr.Sec != tv.Sec || tr.Usec != tv.Usec { t.Fatalf("Unexpected timeout value read: %v. Expected: %v", tr, tv) } } var ( iter = 10 numThread = uint32(4) prefix = "iface" handle1 *Handle handle2 *Handle ns1 netns.NsHandle ns2 netns.NsHandle done uint32 initError error once sync.Once ) func getXfrmState(thread int) *XfrmState { return &XfrmState{ Src: net.IPv4(byte(192), byte(168), 1, byte(1+thread)), Dst: net.IPv4(byte(192), byte(168), 2, byte(1+thread)), Proto: XFRM_PROTO_AH, Mode: XFRM_MODE_TUNNEL, Spi: thread, Auth: &XfrmStateAlgo{ Name: "hmac(sha256)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, } } func getXfrmPolicy(thread int) *XfrmPolicy { return &XfrmPolicy{ Src: &net.IPNet{IP: net.IPv4(byte(10), byte(10), byte(thread), 0), Mask: []byte{255, 255, 255, 0}}, Dst: &net.IPNet{IP: net.IPv4(byte(10), byte(10), byte(thread), 0), Mask: []byte{255, 255, 255, 0}}, Proto: 17, DstPort: 1234, SrcPort: 5678, Dir: XFRM_DIR_OUT, Tmpls: []XfrmPolicyTmpl{ { Src: net.IPv4(byte(192), byte(168), 1, byte(thread)), Dst: net.IPv4(byte(192), byte(168), 2, byte(thread)), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, }, }, } } func initParallel() { ns1, initError = netns.New() if initError != nil { return } handle1, initError = NewHandleAt(ns1) if initError != nil { return } ns2, initError = netns.New() if initError != nil { return } handle2, initError = NewHandleAt(ns2) if initError != nil { return } } func parallelDone() { atomic.AddUint32(&done, 1) if done == numThread { if ns1.IsOpen() { ns1.Close() } if ns2.IsOpen() { ns2.Close() } if handle1 != nil { handle1.Close() } if handle2 != nil { handle2.Close() } } } // Do few route and xfrm operation on the two handles in parallel func runParallelTests(t *testing.T, thread int) { skipUnlessRoot(t) defer parallelDone() t.Parallel() once.Do(initParallel) if initError != nil { t.Fatal(initError) } state := getXfrmState(thread) policy := getXfrmPolicy(thread) for i := 0; i < iter; i++ { ifName := fmt.Sprintf("%s_%d_%d", prefix, thread, i) link := &Dummy{LinkAttrs{Name: ifName}} err := handle1.LinkAdd(link) if err != nil { t.Fatal(err) } l, err := handle1.LinkByName(ifName) if err != nil { t.Fatal(err) } err = handle1.LinkSetUp(l) if err != nil { t.Fatal(err) } handle1.LinkSetNsFd(l, int(ns2)) if err != nil { t.Fatal(err) } err = handle1.XfrmStateAdd(state) if err != nil { t.Fatal(err) } err = handle1.XfrmPolicyAdd(policy) if err != nil { t.Fatal(err) } err = handle2.LinkSetDown(l) if err != nil { t.Fatal(err) } err = handle2.XfrmStateAdd(state) if err != nil { t.Fatal(err) } err = handle2.XfrmPolicyAdd(policy) if err != nil { t.Fatal(err) } _, err = handle2.LinkByName(ifName) if err != nil { t.Fatal(err) } handle2.LinkSetNsFd(l, int(ns1)) if err != nil { t.Fatal(err) } err = handle1.LinkSetUp(l) if err != nil { t.Fatal(err) } _, err = handle1.LinkByName(ifName) if err != nil { t.Fatal(err) } err = handle1.XfrmPolicyDel(policy) if err != nil { t.Fatal(err) } err = handle2.XfrmPolicyDel(policy) if err != nil { t.Fatal(err) } err = handle1.XfrmStateDel(state) if err != nil { t.Fatal(err) } err = handle2.XfrmStateDel(state) if err != nil { t.Fatal(err) } } } func TestHandleParallel1(t *testing.T) { runParallelTests(t, 1) } func TestHandleParallel2(t *testing.T) { runParallelTests(t, 2) } func TestHandleParallel3(t *testing.T) { runParallelTests(t, 3) } func TestHandleParallel4(t *testing.T) { runParallelTests(t, 4) } netlink-1.3.0/handle_unspecified.go000066400000000000000000000146451466216277000173440ustar00rootroot00000000000000// +build !linux package netlink import ( "net" "time" "github.com/vishvananda/netns" ) type Handle struct{} func NewHandle(nlFamilies ...int) (*Handle, error) { return nil, ErrNotImplemented } func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (*Handle, error) { return nil, ErrNotImplemented } func NewHandleAtFrom(newNs, curNs netns.NsHandle) (*Handle, error) { return nil, ErrNotImplemented } func (h *Handle) Close() {} func (h *Handle) Delete() {} func (h *Handle) SupportsNetlinkFamily(nlFamily int) bool { return false } func (h *Handle) SetSocketTimeout(to time.Duration) error { return ErrNotImplemented } func (h *Handle) SetPromiscOn(link Link) error { return ErrNotImplemented } func (h *Handle) SetPromiscOff(link Link) error { return ErrNotImplemented } func (h *Handle) LinkSetUp(link Link) error { return ErrNotImplemented } func (h *Handle) LinkSetDown(link Link) error { return ErrNotImplemented } func (h *Handle) LinkSetMTU(link Link, mtu int) error { return ErrNotImplemented } func (h *Handle) LinkSetName(link Link, name string) error { return ErrNotImplemented } func (h *Handle) LinkSetAlias(link Link, name string) error { return ErrNotImplemented } func (h *Handle) LinkSetHardwareAddr(link Link, hwaddr net.HardwareAddr) error { return ErrNotImplemented } func (h *Handle) LinkSetVfHardwareAddr(link Link, vf int, hwaddr net.HardwareAddr) error { return ErrNotImplemented } func (h *Handle) LinkSetVfVlan(link Link, vf, vlan int) error { return ErrNotImplemented } func (h *Handle) LinkSetVfVlanQos(link Link, vf, vlan, qos int) error { return ErrNotImplemented } func (h *Handle) LinkSetVfVlanQosProto(link Link, vf, vlan, qos, proto int) error { return ErrNotImplemented } func (h *Handle) LinkSetVfTxRate(link Link, vf, rate int) error { return ErrNotImplemented } func (h *Handle) LinkSetVfRate(link Link, vf, minRate, maxRate int) error { return ErrNotImplemented } func (h *Handle) LinkSetMaster(link Link, master Link) error { return ErrNotImplemented } func (h *Handle) LinkSetNoMaster(link Link) error { return ErrNotImplemented } func (h *Handle) LinkSetMasterByIndex(link Link, masterIndex int) error { return ErrNotImplemented } func (h *Handle) LinkSetNsPid(link Link, nspid int) error { return ErrNotImplemented } func (h *Handle) LinkSetNsFd(link Link, fd int) error { return ErrNotImplemented } func (h *Handle) LinkAdd(link Link) error { return ErrNotImplemented } func (h *Handle) LinkDel(link Link) error { return ErrNotImplemented } func (h *Handle) LinkByName(name string) (Link, error) { return nil, ErrNotImplemented } func (h *Handle) LinkByAlias(alias string) (Link, error) { return nil, ErrNotImplemented } func (h *Handle) LinkByIndex(index int) (Link, error) { return nil, ErrNotImplemented } func (h *Handle) LinkList() ([]Link, error) { return nil, ErrNotImplemented } func (h *Handle) LinkSetHairpin(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetGuard(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetFastLeave(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetLearning(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetRootBlock(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetFlood(link Link, mode bool) error { return ErrNotImplemented } func (h *Handle) LinkSetTxQLen(link Link, qlen int) error { return ErrNotImplemented } func (h *Handle) LinkSetGroup(link Link, group int) error { return ErrNotImplemented } func (h *Handle) LinkSetGSOMaxSize(link Link, maxSize int) error { return ErrNotImplemented } func (h *Handle) LinkSetGROMaxSize(link Link, maxSize int) error { return ErrNotImplemented } func (h *Handle) LinkSetGSOIPv4MaxSize(link Link, maxSize int) error { return ErrNotImplemented } func (h *Handle) LinkSetGROIPv4MaxSize(link Link, maxSize int) error { return ErrNotImplemented } func (h *Handle) setProtinfoAttr(link Link, mode bool, attr int) error { return ErrNotImplemented } func (h *Handle) AddrAdd(link Link, addr *Addr) error { return ErrNotImplemented } func (h *Handle) AddrDel(link Link, addr *Addr) error { return ErrNotImplemented } func (h *Handle) AddrList(link Link, family int) ([]Addr, error) { return nil, ErrNotImplemented } func (h *Handle) ClassDel(class Class) error { return ErrNotImplemented } func (h *Handle) ClassChange(class Class) error { return ErrNotImplemented } func (h *Handle) ClassReplace(class Class) error { return ErrNotImplemented } func (h *Handle) ClassAdd(class Class) error { return ErrNotImplemented } func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { return nil, ErrNotImplemented } func (h *Handle) FilterDel(filter Filter) error { return ErrNotImplemented } func (h *Handle) FilterAdd(filter Filter) error { return ErrNotImplemented } func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { return nil, ErrNotImplemented } func (h *Handle) NeighAdd(neigh *Neigh) error { return ErrNotImplemented } func (h *Handle) NeighSet(neigh *Neigh) error { return ErrNotImplemented } func (h *Handle) NeighAppend(neigh *Neigh) error { return ErrNotImplemented } func (h *Handle) NeighDel(neigh *Neigh) error { return ErrNotImplemented } func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) { return nil, ErrNotImplemented } func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) { return nil, ErrNotImplemented } func (h *Handle) RouteAdd(route *Route) error { return ErrNotImplemented } func (h *Handle) RouteAppend(route *Route) error { return ErrNotImplemented } func (h *Handle) RouteChange(route *Route) error { return ErrNotImplemented } func (h *Handle) RouteDel(route *Route) error { return ErrNotImplemented } func (h *Handle) RouteGet(destination net.IP) ([]Route, error) { return nil, ErrNotImplemented } func (h *Handle) RouteList(link Link, family int) ([]Route, error) { return nil, ErrNotImplemented } func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { return nil, ErrNotImplemented } func (h *Handle) RouteReplace(route *Route) error { return ErrNotImplemented } func (h *Handle) RuleAdd(rule *Rule) error { return ErrNotImplemented } func (h *Handle) RuleDel(rule *Rule) error { return ErrNotImplemented } func (h *Handle) RuleList(family int) ([]Rule, error) { return nil, ErrNotImplemented } netlink-1.3.0/inet_diag.go000066400000000000000000000012621466216277000154450ustar00rootroot00000000000000package netlink // INET_DIAG constatns const ( INET_DIAG_NONE = iota INET_DIAG_MEMINFO INET_DIAG_INFO INET_DIAG_VEGASINFO INET_DIAG_CONG INET_DIAG_TOS INET_DIAG_TCLASS INET_DIAG_SKMEMINFO INET_DIAG_SHUTDOWN INET_DIAG_DCTCPINFO INET_DIAG_PROTOCOL INET_DIAG_SKV6ONLY INET_DIAG_LOCALS INET_DIAG_PEERS INET_DIAG_PAD INET_DIAG_MARK INET_DIAG_BBRINFO INET_DIAG_CLASS_ID INET_DIAG_MD5SIG INET_DIAG_ULP_INFO INET_DIAG_SK_BPF_STORAGES INET_DIAG_CGROUP_ID INET_DIAG_SOCKOPT INET_DIAG_MAX ) type InetDiagTCPInfoResp struct { InetDiagMsg *Socket TCPInfo *TCPInfo TCPBBRInfo *TCPBBRInfo } type InetDiagUDPInfoResp struct { InetDiagMsg *Socket Memory *MemInfo } netlink-1.3.0/ioctl_linux.go000066400000000000000000000043671466216277000160640ustar00rootroot00000000000000package netlink import ( "syscall" "unsafe" "golang.org/x/sys/unix" ) // ioctl for statistics. const ( // ETHTOOL_GSSET_INFO gets string set info ETHTOOL_GSSET_INFO = 0x00000037 // SIOCETHTOOL is Ethtool interface SIOCETHTOOL = 0x8946 // ETHTOOL_GSTRINGS gets specified string set ETHTOOL_GSTRINGS = 0x0000001b // ETHTOOL_GSTATS gets NIC-specific statistics ETHTOOL_GSTATS = 0x0000001d ) // string set id. const ( // ETH_SS_TEST is self-test result names, for use with %ETHTOOL_TEST ETH_SS_TEST = iota // ETH_SS_STATS statistic names, for use with %ETHTOOL_GSTATS ETH_SS_STATS // ETH_SS_PRIV_FLAGS are driver private flag names ETH_SS_PRIV_FLAGS // _ETH_SS_NTUPLE_FILTERS is deprecated _ETH_SS_NTUPLE_FILTERS // ETH_SS_FEATURES are device feature names ETH_SS_FEATURES // ETH_SS_RSS_HASH_FUNCS is RSS hush function names ETH_SS_RSS_HASH_FUNCS ) // IfreqSlave is a struct for ioctl bond manipulation syscalls. // It is used to assign slave to bond interface with Name. type IfreqSlave struct { Name [unix.IFNAMSIZ]byte Slave [unix.IFNAMSIZ]byte } // Ifreq is a struct for ioctl ethernet manipulation syscalls. type Ifreq struct { Name [unix.IFNAMSIZ]byte Data uintptr } // ethtoolSset is a string set information type ethtoolSset struct { cmd uint32 reserved uint32 mask uint64 data [1]uint32 } type ethtoolStats struct { cmd uint32 nStats uint32 // Followed by nStats * []uint64. } // newIocltSlaveReq returns filled IfreqSlave with proper interface names // It is used by ioctl to assign slave to bond master func newIocltSlaveReq(slave, master string) *IfreqSlave { ifreq := &IfreqSlave{} copy(ifreq.Name[:unix.IFNAMSIZ-1], master) copy(ifreq.Slave[:unix.IFNAMSIZ-1], slave) return ifreq } // newIocltStringSetReq creates request to get interface string set func newIocltStringSetReq(linkName string) (*Ifreq, *ethtoolSset) { e := ðtoolSset{ cmd: ETHTOOL_GSSET_INFO, mask: 1 << ETH_SS_STATS, } ifreq := &Ifreq{Data: uintptr(unsafe.Pointer(e))} copy(ifreq.Name[:unix.IFNAMSIZ-1], linkName) return ifreq, e } // getSocketUDP returns file descriptor to new UDP socket // It is used for communication with ioctl interface. func getSocketUDP() (int, error) { return syscall.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) } netlink-1.3.0/ipset_linux.go000066400000000000000000000376371466216277000161040ustar00rootroot00000000000000package netlink import ( "encoding/binary" "log" "net" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // IPSetEntry is used for adding, updating, retreiving and deleting entries type IPSetEntry struct { Comment string MAC net.HardwareAddr IP net.IP CIDR uint8 Timeout *uint32 Packets *uint64 Bytes *uint64 Protocol *uint8 Port *uint16 IP2 net.IP CIDR2 uint8 IFace string Mark *uint32 Replace bool // replace existing entry } // IPSetResult is the result of a dump request for a set type IPSetResult struct { Nfgenmsg *nl.Nfgenmsg Protocol uint8 ProtocolMinVersion uint8 Revision uint8 Family uint8 Flags uint8 SetName string TypeName string Comment string MarkMask uint32 IPFrom net.IP IPTo net.IP PortFrom uint16 PortTo uint16 HashSize uint32 NumEntries uint32 MaxElements uint32 References uint32 SizeInMemory uint32 CadtFlags uint32 Timeout *uint32 LineNo uint32 Entries []IPSetEntry } // IpsetCreateOptions is the options struct for creating a new ipset type IpsetCreateOptions struct { Replace bool // replace existing ipset Timeout *uint32 Counters bool Comments bool Skbinfo bool Family uint8 Revision uint8 IPFrom net.IP IPTo net.IP PortFrom uint16 PortTo uint16 MaxElements uint32 } // IpsetProtocol returns the ipset protocol version from the kernel func IpsetProtocol() (uint8, uint8, error) { return pkgHandle.IpsetProtocol() } // IpsetCreate creates a new ipset func IpsetCreate(setname, typename string, options IpsetCreateOptions) error { return pkgHandle.IpsetCreate(setname, typename, options) } // IpsetDestroy destroys an existing ipset func IpsetDestroy(setname string) error { return pkgHandle.IpsetDestroy(setname) } // IpsetFlush flushes an existing ipset func IpsetFlush(setname string) error { return pkgHandle.IpsetFlush(setname) } // IpsetSwap swaps two ipsets. func IpsetSwap(setname, othersetname string) error { return pkgHandle.IpsetSwap(setname, othersetname) } // IpsetList dumps an specific ipset. func IpsetList(setname string) (*IPSetResult, error) { return pkgHandle.IpsetList(setname) } // IpsetListAll dumps all ipsets. func IpsetListAll() ([]IPSetResult, error) { return pkgHandle.IpsetListAll() } // IpsetAdd adds an entry to an existing ipset. func IpsetAdd(setname string, entry *IPSetEntry) error { return pkgHandle.IpsetAdd(setname, entry) } // IpsetDel deletes an entry from an existing ipset. func IpsetDel(setname string, entry *IPSetEntry) error { return pkgHandle.IpsetDel(setname, entry) } // IpsetTest tests whether an entry is in a set or not. func IpsetTest(setname string, entry *IPSetEntry) (bool, error) { return pkgHandle.IpsetTest(setname, entry) } func (h *Handle) IpsetProtocol() (protocol uint8, minVersion uint8, err error) { req := h.newIpsetRequest(nl.IPSET_CMD_PROTOCOL) msgs, err := req.Execute(unix.NETLINK_NETFILTER, 0) if err != nil { return 0, 0, err } response := ipsetUnserialize(msgs) return response.Protocol, response.ProtocolMinVersion, nil } func (h *Handle) IpsetCreate(setname, typename string, options IpsetCreateOptions) error { req := h.newIpsetRequest(nl.IPSET_CMD_CREATE) if !options.Replace { req.Flags |= unix.NLM_F_EXCL } req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_TYPENAME, nl.ZeroTerminated(typename))) revision := options.Revision if revision == 0 { revision = getIpsetDefaultWithTypeName(typename) } req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_REVISION, nl.Uint8Attr(revision))) data := nl.NewRtAttr(nl.IPSET_ATTR_DATA|int(nl.NLA_F_NESTED), nil) var family uint8 switch typename { case "hash:mac": case "bitmap:port": buf := make([]byte, 4) binary.BigEndian.PutUint16(buf, options.PortFrom) binary.BigEndian.PutUint16(buf[2:], options.PortTo) data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PORT_FROM|int(nl.NLA_F_NET_BYTEORDER), buf[:2])) data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PORT_TO|int(nl.NLA_F_NET_BYTEORDER), buf[2:])) default: family = options.Family if family == 0 { family = unix.AF_INET } } req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_FAMILY, nl.Uint8Attr(family))) if options.MaxElements != 0 { data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_MAXELEM | nl.NLA_F_NET_BYTEORDER, Value: options.MaxElements}) } if timeout := options.Timeout; timeout != nil { data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER, Value: *timeout}) } var cadtFlags uint32 if options.Comments { cadtFlags |= nl.IPSET_FLAG_WITH_COMMENT } if options.Counters { cadtFlags |= nl.IPSET_FLAG_WITH_COUNTERS } if options.Skbinfo { cadtFlags |= nl.IPSET_FLAG_WITH_SKBINFO } if cadtFlags != 0 { data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_CADT_FLAGS | nl.NLA_F_NET_BYTEORDER, Value: cadtFlags}) } req.AddData(data) _, err := ipsetExecute(req) return err } func (h *Handle) IpsetDestroy(setname string) error { req := h.newIpsetRequest(nl.IPSET_CMD_DESTROY) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) _, err := ipsetExecute(req) return err } func (h *Handle) IpsetFlush(setname string) error { req := h.newIpsetRequest(nl.IPSET_CMD_FLUSH) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) _, err := ipsetExecute(req) return err } func (h *Handle) IpsetSwap(setname, othersetname string) error { req := h.newIpsetRequest(nl.IPSET_CMD_SWAP) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_TYPENAME, nl.ZeroTerminated(othersetname))) _, err := ipsetExecute(req) return err } func (h *Handle) IpsetList(name string) (*IPSetResult, error) { req := h.newIpsetRequest(nl.IPSET_CMD_LIST) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(name))) msgs, err := ipsetExecute(req) if err != nil { return nil, err } result := ipsetUnserialize(msgs) return &result, nil } func (h *Handle) IpsetListAll() ([]IPSetResult, error) { req := h.newIpsetRequest(nl.IPSET_CMD_LIST) msgs, err := ipsetExecute(req) if err != nil { return nil, err } result := make([]IPSetResult, len(msgs)) for i, msg := range msgs { result[i].unserialize(msg) } return result, nil } // IpsetAdd adds an entry to an existing ipset. func (h *Handle) IpsetAdd(setname string, entry *IPSetEntry) error { return h.ipsetAddDel(nl.IPSET_CMD_ADD, setname, entry) } // IpsetDel deletes an entry from an existing ipset. func (h *Handle) IpsetDel(setname string, entry *IPSetEntry) error { return h.ipsetAddDel(nl.IPSET_CMD_DEL, setname, entry) } func encodeIP(ip net.IP) (*nl.RtAttr, error) { typ := int(nl.NLA_F_NET_BYTEORDER) if ip4 := ip.To4(); ip4 != nil { typ |= nl.IPSET_ATTR_IPADDR_IPV4 ip = ip4 } else { typ |= nl.IPSET_ATTR_IPADDR_IPV6 } return nl.NewRtAttr(typ, ip), nil } func buildEntryData(entry *IPSetEntry) (*nl.RtAttr, error) { data := nl.NewRtAttr(nl.IPSET_ATTR_DATA|int(nl.NLA_F_NESTED), nil) if entry.Comment != "" { data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_COMMENT, nl.ZeroTerminated(entry.Comment))) } if entry.Timeout != nil { data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER, Value: *entry.Timeout}) } if entry.IP != nil { nestedData, err := encodeIP(entry.IP) if err != nil { return nil, err } data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IP|int(nl.NLA_F_NESTED), nestedData.Serialize())) } if entry.MAC != nil { data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_ETHER, entry.MAC)) } if entry.CIDR != 0 { data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_CIDR, nl.Uint8Attr(entry.CIDR))) } if entry.IP2 != nil { nestedData, err := encodeIP(entry.IP2) if err != nil { return nil, err } data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IP2|int(nl.NLA_F_NESTED), nestedData.Serialize())) } if entry.CIDR2 != 0 { data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_CIDR2, nl.Uint8Attr(entry.CIDR2))) } if entry.Port != nil { if entry.Protocol == nil { // use tcp protocol as default val := uint8(unix.IPPROTO_TCP) entry.Protocol = &val } data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PROTO, nl.Uint8Attr(*entry.Protocol))) buf := make([]byte, 2) binary.BigEndian.PutUint16(buf, *entry.Port) data.AddChild(nl.NewRtAttr(int(nl.IPSET_ATTR_PORT|nl.NLA_F_NET_BYTEORDER), buf)) } if entry.IFace != "" { data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IFACE, nl.ZeroTerminated(entry.IFace))) } if entry.Mark != nil { data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_MARK | nl.NLA_F_NET_BYTEORDER, Value: *entry.Mark}) } return data, nil } func (h *Handle) ipsetAddDel(nlCmd int, setname string, entry *IPSetEntry) error { req := h.newIpsetRequest(nlCmd) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) if !entry.Replace { req.Flags |= unix.NLM_F_EXCL } data, err := buildEntryData(entry) if err != nil { return err } data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_LINENO | nl.NLA_F_NET_BYTEORDER, Value: 0}) req.AddData(data) _, err = ipsetExecute(req) return err } func (h *Handle) IpsetTest(setname string, entry *IPSetEntry) (bool, error) { req := h.newIpsetRequest(nl.IPSET_CMD_TEST) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname))) if !entry.Replace { req.Flags |= unix.NLM_F_EXCL } data, err := buildEntryData(entry) if err != nil { return false, err } req.AddData(data) _, err = ipsetExecute(req) if err != nil { if err == nl.IPSetError(nl.IPSET_ERR_EXIST) { // not exist return false, nil } return false, err } return true, nil } func (h *Handle) newIpsetRequest(cmd int) *nl.NetlinkRequest { req := h.newNetlinkRequest(cmd|(unix.NFNL_SUBSYS_IPSET<<8), nl.GetIpsetFlags(cmd)) // Add the netfilter header msg := &nl.Nfgenmsg{ NfgenFamily: uint8(unix.AF_NETLINK), Version: nl.NFNETLINK_V0, ResId: 0, } req.AddData(msg) req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_PROTOCOL, nl.Uint8Attr(nl.IPSET_PROTOCOL))) return req } func getIpsetDefaultWithTypeName(typename string) uint8 { switch typename { case "hash:ip,port", "hash:ip,port,ip", "hash:ip,port,net", "hash:net,port": return 1 } return 0 } func ipsetExecute(req *nl.NetlinkRequest) (msgs [][]byte, err error) { msgs, err = req.Execute(unix.NETLINK_NETFILTER, 0) if err != nil { if errno := int(err.(syscall.Errno)); errno >= nl.IPSET_ERR_PRIVATE { err = nl.IPSetError(uintptr(errno)) } } return } func ipsetUnserialize(msgs [][]byte) (result IPSetResult) { for _, msg := range msgs { result.unserialize(msg) } return result } func (result *IPSetResult) unserialize(msg []byte) { result.Nfgenmsg = nl.DeserializeNfgenmsg(msg) for attr := range nl.ParseAttributes(msg[4:]) { switch attr.Type { case nl.IPSET_ATTR_PROTOCOL: result.Protocol = attr.Value[0] case nl.IPSET_ATTR_SETNAME: result.SetName = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_COMMENT: result.Comment = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_TYPENAME: result.TypeName = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_REVISION: result.Revision = attr.Value[0] case nl.IPSET_ATTR_FAMILY: result.Family = attr.Value[0] case nl.IPSET_ATTR_FLAGS: result.Flags = attr.Value[0] case nl.IPSET_ATTR_DATA | nl.NLA_F_NESTED: result.parseAttrData(attr.Value) case nl.IPSET_ATTR_ADT | nl.NLA_F_NESTED: result.parseAttrADT(attr.Value) case nl.IPSET_ATTR_PROTOCOL_MIN: result.ProtocolMinVersion = attr.Value[0] case nl.IPSET_ATTR_MARKMASK: result.MarkMask = attr.Uint32() default: log.Printf("unknown ipset attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK) } } } func (result *IPSetResult) parseAttrData(data []byte) { for attr := range nl.ParseAttributes(data) { switch attr.Type { case nl.IPSET_ATTR_HASHSIZE | nl.NLA_F_NET_BYTEORDER: result.HashSize = attr.Uint32() case nl.IPSET_ATTR_MAXELEM | nl.NLA_F_NET_BYTEORDER: result.MaxElements = attr.Uint32() case nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER: val := attr.Uint32() result.Timeout = &val case nl.IPSET_ATTR_ELEMENTS | nl.NLA_F_NET_BYTEORDER: result.NumEntries = attr.Uint32() case nl.IPSET_ATTR_REFERENCES | nl.NLA_F_NET_BYTEORDER: result.References = attr.Uint32() case nl.IPSET_ATTR_MEMSIZE | nl.NLA_F_NET_BYTEORDER: result.SizeInMemory = attr.Uint32() case nl.IPSET_ATTR_CADT_FLAGS | nl.NLA_F_NET_BYTEORDER: result.CadtFlags = attr.Uint32() case nl.IPSET_ATTR_IP | nl.NLA_F_NESTED: for nested := range nl.ParseAttributes(attr.Value) { switch nested.Type { case nl.IPSET_ATTR_IP | nl.NLA_F_NET_BYTEORDER: result.Entries = append(result.Entries, IPSetEntry{IP: nested.Value}) case nl.IPSET_ATTR_IP: result.IPFrom = nested.Value default: log.Printf("unknown nested ipset data attribute from kernel: %+v %v", nested, nested.Type&nl.NLA_TYPE_MASK) } } case nl.IPSET_ATTR_IP_TO | nl.NLA_F_NESTED: for nested := range nl.ParseAttributes(attr.Value) { switch nested.Type { case nl.IPSET_ATTR_IP: result.IPTo = nested.Value default: log.Printf("unknown nested ipset data attribute from kernel: %+v %v", nested, nested.Type&nl.NLA_TYPE_MASK) } } case nl.IPSET_ATTR_PORT_FROM | nl.NLA_F_NET_BYTEORDER: result.PortFrom = networkOrder.Uint16(attr.Value) case nl.IPSET_ATTR_PORT_TO | nl.NLA_F_NET_BYTEORDER: result.PortTo = networkOrder.Uint16(attr.Value) case nl.IPSET_ATTR_CADT_LINENO | nl.NLA_F_NET_BYTEORDER: result.LineNo = attr.Uint32() case nl.IPSET_ATTR_COMMENT: result.Comment = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_MARKMASK: result.MarkMask = attr.Uint32() default: log.Printf("unknown ipset data attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK) } } } func (result *IPSetResult) parseAttrADT(data []byte) { for attr := range nl.ParseAttributes(data) { switch attr.Type { case nl.IPSET_ATTR_DATA | nl.NLA_F_NESTED: result.Entries = append(result.Entries, parseIPSetEntry(attr.Value)) default: log.Printf("unknown ADT attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK) } } } func parseIPSetEntry(data []byte) (entry IPSetEntry) { for attr := range nl.ParseAttributes(data) { switch attr.Type { case nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER: val := attr.Uint32() entry.Timeout = &val case nl.IPSET_ATTR_BYTES | nl.NLA_F_NET_BYTEORDER: val := attr.Uint64() entry.Bytes = &val case nl.IPSET_ATTR_PACKETS | nl.NLA_F_NET_BYTEORDER: val := attr.Uint64() entry.Packets = &val case nl.IPSET_ATTR_ETHER: entry.MAC = net.HardwareAddr(attr.Value) case nl.IPSET_ATTR_IP: entry.IP = net.IP(attr.Value) case nl.IPSET_ATTR_COMMENT: entry.Comment = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_IP | nl.NLA_F_NESTED: for attr := range nl.ParseAttributes(attr.Value) { switch attr.Type { case nl.IPSET_ATTR_IPADDR_IPV4, nl.IPSET_ATTR_IPADDR_IPV6: entry.IP = net.IP(attr.Value) default: log.Printf("unknown nested ADT attribute from kernel: %+v", attr) } } case nl.IPSET_ATTR_IP2 | nl.NLA_F_NESTED: for attr := range nl.ParseAttributes(attr.Value) { switch attr.Type { case nl.IPSET_ATTR_IPADDR_IPV4, nl.IPSET_ATTR_IPADDR_IPV6: entry.IP2 = net.IP(attr.Value) default: log.Printf("unknown nested ADT attribute from kernel: %+v", attr) } } case nl.IPSET_ATTR_CIDR: entry.CIDR = attr.Value[0] case nl.IPSET_ATTR_CIDR2: entry.CIDR2 = attr.Value[0] case nl.IPSET_ATTR_PORT | nl.NLA_F_NET_BYTEORDER: val := networkOrder.Uint16(attr.Value) entry.Port = &val case nl.IPSET_ATTR_PROTO: val := attr.Value[0] entry.Protocol = &val case nl.IPSET_ATTR_IFACE: entry.IFace = nl.BytesToString(attr.Value) case nl.IPSET_ATTR_MARK | nl.NLA_F_NET_BYTEORDER: val := attr.Uint32() entry.Mark = &val default: log.Printf("unknown ADT attribute from kernel: %+v", attr) } } return } netlink-1.3.0/ipset_linux_test.go000066400000000000000000000432111466216277000171240ustar00rootroot00000000000000package netlink import ( "bytes" "io/ioutil" "net" "testing" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) func TestParseIpsetProtocolResult(t *testing.T) { msgBytes, err := ioutil.ReadFile("testdata/ipset_protocol_result") if err != nil { t.Fatalf("reading test fixture failed: %v", err) } msg := ipsetUnserialize([][]byte{msgBytes}) if msg.Protocol != 6 { t.Errorf("expected msg.Protocol to equal 6, got %d", msg.Protocol) } } func TestParseIpsetListResult(t *testing.T) { msgBytes, err := ioutil.ReadFile("testdata/ipset_list_result") if err != nil { t.Fatalf("reading test fixture failed: %v", err) } msg := ipsetUnserialize([][]byte{msgBytes}) if msg.SetName != "clients" { t.Errorf(`expected SetName to equal "clients", got %q`, msg.SetName) } if msg.TypeName != "hash:mac" { t.Errorf(`expected TypeName to equal "hash:mac", got %q`, msg.TypeName) } if msg.Protocol != 6 { t.Errorf("expected Protocol to equal 6, got %d", msg.Protocol) } if msg.References != 0 { t.Errorf("expected References to equal 0, got %d", msg.References) } if msg.NumEntries != 2 { t.Errorf("expected NumEntries to equal 2, got %d", msg.NumEntries) } if msg.HashSize != 1024 { t.Errorf("expected HashSize to equal 1024, got %d", msg.HashSize) } if *msg.Timeout != 3600 { t.Errorf("expected Timeout to equal 3600, got %d", *msg.Timeout) } if msg.MaxElements != 65536 { t.Errorf("expected MaxElements to equal 65536, got %d", msg.MaxElements) } if msg.CadtFlags != nl.IPSET_FLAG_WITH_COMMENT|nl.IPSET_FLAG_WITH_COUNTERS { t.Error("expected CadtFlags to be IPSET_FLAG_WITH_COMMENT and IPSET_FLAG_WITH_COUNTERS") } if len(msg.Entries) != 2 { t.Fatalf("expected 2 Entries, got %d", len(msg.Entries)) } // first entry ent := msg.Entries[0] if int(*ent.Timeout) != 3577 { t.Errorf("expected Timeout for first entry to equal 3577, got %d", *ent.Timeout) } if int(*ent.Bytes) != 4121 { t.Errorf("expected Bytes for first entry to equal 4121, got %d", *ent.Bytes) } if int(*ent.Packets) != 42 { t.Errorf("expected Packets for first entry to equal 42, got %d", *ent.Packets) } if ent.Comment != "foo bar" { t.Errorf("unexpected Comment for first entry: %q", ent.Comment) } expectedMAC := net.HardwareAddr{0xde, 0xad, 0x0, 0x0, 0xbe, 0xef} if !bytes.Equal(ent.MAC, expectedMAC) { t.Errorf("expected MAC for first entry to be %s, got %s", expectedMAC.String(), ent.MAC.String()) } // second entry ent = msg.Entries[1] expectedMAC = net.HardwareAddr{0x1, 0x2, 0x3, 0x0, 0x1, 0x2} if !bytes.Equal(ent.MAC, expectedMAC) { t.Errorf("expected MAC for second entry to be %s, got %s", expectedMAC.String(), ent.MAC.String()) } } func TestIpsetCreateListAddDelDestroy(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() timeout := uint32(3) err := IpsetCreate("my-test-ipset-1", "hash:ip", IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: true, Skbinfo: false, }) if err != nil { t.Fatal(err) } err = IpsetCreate("my-test-ipset-2", "hash:net", IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }) if err != nil { t.Fatal(err) } results, err := IpsetListAll() if err != nil { t.Fatal(err) } if len(results) != 2 { t.Fatalf("expected 2 IPSets to be created, got %d", len(results)) } if results[0].SetName != "my-test-ipset-1" { t.Errorf("expected name to be 'my-test-ipset-1', but got '%s'", results[0].SetName) } if results[1].SetName != "my-test-ipset-2" { t.Errorf("expected name to be 'my-test-ipset-2', but got '%s'", results[1].SetName) } if results[0].TypeName != "hash:ip" { t.Errorf("expected type to be 'hash:ip', but got '%s'", results[0].TypeName) } if results[1].TypeName != "hash:net" { t.Errorf("expected type to be 'hash:net', but got '%s'", results[1].TypeName) } if *results[0].Timeout != 3 { t.Errorf("expected timeout to be 3, but got '%d'", *results[0].Timeout) } ip := net.ParseIP("10.99.99.99") exist, err := IpsetTest("my-test-ipset-1", &IPSetEntry{ IP: ip, }) if err != nil { t.Fatal(err) } if exist { t.Errorf("entry should not exist before being added: %s", ip.String()) } err = IpsetAdd("my-test-ipset-1", &IPSetEntry{ Comment: "test comment", IP: ip, Replace: false, }) if err != nil { t.Fatal(err) } exist, err = IpsetTest("my-test-ipset-1", &IPSetEntry{ IP: ip, }) if err != nil { t.Fatal(err) } if !exist { t.Errorf("entry should exist after being added: %s", ip.String()) } result, err := IpsetList("my-test-ipset-1") if err != nil { t.Fatal(err) } if len(result.Entries) != 1 { t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries)) } if result.Entries[0].IP.String() != "10.99.99.99" { t.Fatalf("expected entry to be '10.99.99.99', got '%s'", result.Entries[0].IP.String()) } if result.Entries[0].Comment != "test comment" { // This is only supported in the kernel module from revision 2 or 4, so comments may be ignored. t.Logf("expected comment to be 'test comment', got '%s'", result.Entries[0].Comment) } err = IpsetDel("my-test-ipset-1", &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.99"), }) if err != nil { t.Fatal(err) } result, err = IpsetList("my-test-ipset-1") if err != nil { t.Fatal(err) } if len(result.Entries) != 0 { t.Fatalf("expected 0 entries to exist, got %d", len(result.Entries)) } err = IpsetDestroy("my-test-ipset-1") if err != nil { t.Fatal(err) } err = IpsetDestroy("my-test-ipset-2") if err != nil { t.Fatal(err) } } func TestIpsetCreateListAddDelDestroyWithTestCases(t *testing.T) { timeout := uint32(3) protocalTCP := uint8(unix.IPPROTO_TCP) port := uint16(80) testCases := []struct { desc string setname string typename string options IpsetCreateOptions entry *IPSetEntry }{ { desc: "Type-hash:ip", setname: "my-test-ipset-1", typename: "hash:ip", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: true, Skbinfo: false, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.99"), Replace: false, }, }, { desc: "Type-hash:net", setname: "my-test-ipset-2", typename: "hash:net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), CIDR: 24, Replace: false, }, }, { desc: "Type-hash:net,net", setname: "my-test-ipset-4", typename: "hash:net,net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), CIDR: 24, IP2: net.ParseIP("10.99.0.0"), CIDR2: 24, Replace: false, }, }, { desc: "Type-hash:ip,ip", setname: "my-test-ipset-5", typename: "hash:net,net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), IP2: net.ParseIP("10.99.0.0"), Replace: false, }, }, { desc: "Type-hash:ip,port", setname: "my-test-ipset-6", typename: "hash:ip,port", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.1"), Protocol: &protocalTCP, Port: &port, Replace: false, }, }, { desc: "Type-hash:net,port,net", setname: "my-test-ipset-7", typename: "hash:net,port,net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), CIDR: 24, IP2: net.ParseIP("10.99.0.0"), CIDR2: 24, Protocol: &protocalTCP, Port: &port, Replace: false, }, }, { desc: "Type-hash:mac", setname: "my-test-ipset-8", typename: "hash:mac", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: true, Skbinfo: false, }, entry: &IPSetEntry{ Comment: "test comment", MAC: net.HardwareAddr{0x26, 0x6f, 0x0d, 0x5b, 0xc1, 0x9d}, Replace: false, }, }, { desc: "Type-hash:net,iface", setname: "my-test-ipset-9", typename: "hash:net,iface", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: true, Skbinfo: false, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), CIDR: 24, IFace: "eth0", Replace: false, }, }, { desc: "Type-hash:ip,mark", setname: "my-test-ipset-10", typename: "hash:ip,mark", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: true, Skbinfo: false, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), Mark: &timeout, Replace: false, }, }, { desc: "Type-hash:net6", setname: "my-test-ipset-11", typename: "hash:net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, Family: unix.AF_INET6, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("::1"), CIDR: 128, Replace: false, }, }, { desc: "Type-hash:net6:net6", setname: "my-test-ipset-11", typename: "hash:net,net", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: false, Comments: true, Skbinfo: true, Family: unix.AF_INET6, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("::1"), CIDR: 128, IP2: net.ParseIP("::2"), CIDR2: 128, Replace: false, }, }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() err := IpsetCreate(tC.setname, tC.typename, tC.options) if err != nil { t.Fatal(err) } result, err := IpsetList(tC.setname) if err != nil { t.Fatal(err) } if result.SetName != tC.setname { t.Errorf("expected name to be '%s', but got '%s'", tC.setname, result.SetName) } if result.TypeName != tC.typename { t.Errorf("expected type to be '%s', but got '%s'", tC.typename, result.TypeName) } if *result.Timeout != timeout { t.Errorf("expected timeout to be %d, but got '%d'", timeout, *result.Timeout) } err = IpsetAdd(tC.setname, tC.entry) if err != nil { t.Error(result.Protocol, result.Family) t.Fatal(err) } exist, err := IpsetTest(tC.setname, tC.entry) if err != nil { t.Fatal(err) } if !exist { t.Errorf("entry should exist, but 'test' got false, case: %s", tC.desc) } result, err = IpsetList(tC.setname) if err != nil { t.Fatal(err) } if len(result.Entries) != 1 { t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries)) } if tC.entry.IP != nil { if !tC.entry.IP.Equal(result.Entries[0].IP) { t.Fatalf("expected entry to be '%v', got '%v'", tC.entry.IP, result.Entries[0].IP) } } if tC.entry.CIDR > 0 { if result.Entries[0].CIDR != tC.entry.CIDR { t.Fatalf("expected cidr to be '%d', got '%d'", tC.entry.CIDR, result.Entries[0].CIDR) } } if tC.entry.IP2 != nil { if !tC.entry.IP2.Equal(result.Entries[0].IP2) { t.Fatalf("expected entry.ip2 to be '%v', got '%v'", tC.entry.IP2, result.Entries[0].IP2) } } if tC.entry.CIDR2 > 0 { if result.Entries[0].CIDR2 != tC.entry.CIDR2 { t.Fatalf("expected cidr2 to be '%d', got '%d'", tC.entry.CIDR2, result.Entries[0].CIDR2) } } if tC.entry.Port != nil { if *result.Entries[0].Protocol != *tC.entry.Protocol { t.Fatalf("expected protocol to be '%d', got '%d'", *tC.entry.Protocol, *result.Entries[0].Protocol) } if *result.Entries[0].Port != *tC.entry.Port { t.Fatalf("expected port to be '%d', got '%d'", *tC.entry.Port, *result.Entries[0].Port) } } if tC.entry.MAC != nil { if result.Entries[0].MAC.String() != tC.entry.MAC.String() { t.Fatalf("expected mac to be '%v', got '%v'", tC.entry.MAC, result.Entries[0].MAC) } } if tC.entry.IFace != "" { if result.Entries[0].IFace != tC.entry.IFace { t.Fatalf("expected iface to be '%v', got '%v'", tC.entry.IFace, result.Entries[0].IFace) } } if tC.entry.Mark != nil { if *result.Entries[0].Mark != *tC.entry.Mark { t.Fatalf("expected mark to be '%v', got '%v'", *tC.entry.Mark, *result.Entries[0].Mark) } } if result.Entries[0].Comment != tC.entry.Comment { // This is only supported in the kernel module from revision 2 or 4, so comments may be ignored. t.Logf("expected comment to be '%s', got '%s'", tC.entry.Comment, result.Entries[0].Comment) } err = IpsetDel(tC.setname, tC.entry) if err != nil { t.Fatal(err) } exist, err = IpsetTest(tC.setname, tC.entry) if err != nil { t.Fatal(err) } if exist { t.Errorf("entry should be deleted, but 'test' got true, case: %s", tC.desc) } result, err = IpsetList(tC.setname) if err != nil { t.Fatal(err) } if len(result.Entries) != 0 { t.Fatalf("expected 0 entries to exist, got %d", len(result.Entries)) } err = IpsetDestroy(tC.setname) if err != nil { t.Fatal(err) } }) } } func TestIpsetBitmapCreateListWithTestCases(t *testing.T) { timeout := uint32(3) testCases := []struct { desc string setname string typename string options IpsetCreateOptions entry *IPSetEntry }{ { desc: "Type-bitmap:port", setname: "my-test-ipset-11", typename: "bitmap:port", options: IpsetCreateOptions{ Replace: true, Timeout: &timeout, Counters: true, Comments: false, Skbinfo: false, PortFrom: 100, PortTo: 600, }, entry: &IPSetEntry{ Comment: "test comment", IP: net.ParseIP("10.99.99.0"), CIDR: 26, Mark: &timeout, Replace: false, }, }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() err := IpsetCreate(tC.setname, tC.typename, tC.options) if err != nil { t.Fatal(err) } result, err := IpsetList(tC.setname) if err != nil { t.Fatal(err) } if tC.typename == "bitmap:port" { if result.PortFrom != tC.options.PortFrom || result.PortTo != tC.options.PortTo { t.Fatalf("expected port range %d-%d, got %d-%d", tC.options.PortFrom, tC.options.PortTo, result.PortFrom, result.PortTo) } } else if tC.typename == "bitmap:ip" { if result.IPFrom == nil || result.IPTo == nil || result.IPFrom.Equal(tC.options.IPFrom) || result.IPTo.Equal(tC.options.IPTo) { t.Fatalf("expected ip range %v-%v, got %v-%v", tC.options.IPFrom, tC.options.IPTo, result.IPFrom, result.IPTo) } } }) } } func TestIpsetSwap(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ipset1 := "my-test-ipset-swap-1" ipset2 := "my-test-ipset-swap-2" err := IpsetCreate(ipset1, "hash:ip", IpsetCreateOptions{ Replace: true, }) if err != nil { t.Fatal(err) } defer func() { _ = IpsetDestroy(ipset1) }() err = IpsetCreate(ipset2, "hash:ip", IpsetCreateOptions{ Replace: true, }) if err != nil { t.Fatal(err) } defer func() { _ = IpsetDestroy(ipset2) }() err = IpsetAdd(ipset1, &IPSetEntry{ IP: net.ParseIP("10.99.99.99"), }) if err != nil { t.Fatal(err) } assertHasOneEntry := func(name string) { result, err := IpsetList(name) if err != nil { t.Fatal(err) } if len(result.Entries) != 1 { t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries)) } if result.Entries[0].IP.String() != "10.99.99.99" { t.Fatalf("expected entry to be '10.99.99.99', got '%s'", result.Entries[0].IP.String()) } } assertIsEmpty := func(name string) { result, err := IpsetList(name) if err != nil { t.Fatal(err) } if len(result.Entries) != 0 { t.Fatalf("expected 0 entry be created, got '%d'", len(result.Entries)) } } assertHasOneEntry(ipset1) assertIsEmpty(ipset2) err = IpsetSwap(ipset1, ipset2) if err != nil { t.Fatal(err) } assertIsEmpty(ipset1) assertHasOneEntry(ipset2) } func nextIP(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ if ip[j] > 0 { break } } } // TestIpsetMaxElements tests that we can create an ipset containing // 128k elements, which is double the default size (64k elements). func TestIpsetMaxElements(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ipsetName := "my-test-ipset-max" maxElements := uint32(128 << 10) err := IpsetCreate(ipsetName, "hash:ip", IpsetCreateOptions{ Replace: true, MaxElements: maxElements, }) if err != nil { t.Fatal(err) } defer func() { _ = IpsetDestroy(ipsetName) }() ip := net.ParseIP("10.0.0.0") for i := uint32(0); i < maxElements; i++ { err = IpsetAdd(ipsetName, &IPSetEntry{ IP: ip, }) if err != nil { t.Fatal(err) } nextIP(ip) } result, err := IpsetList(ipsetName) if err != nil { t.Fatal(err) } if len(result.Entries) != int(maxElements) { t.Fatalf("expected '%d' entry be created, got '%d'", maxElements, len(result.Entries)) } } netlink-1.3.0/link.go000066400000000000000000000732261466216277000144700ustar00rootroot00000000000000package netlink import ( "fmt" "net" "os" "strconv" ) // Link represents a link device from netlink. Shared link attributes // like name may be retrieved using the Attrs() method. Unique data // can be retrieved by casting the object to the proper type. type Link interface { Attrs() *LinkAttrs Type() string } type ( NsPid int NsFd int ) // LinkAttrs represents data shared by most link types type LinkAttrs struct { Index int MTU int TxQLen int // Transmit Queue Length Name string HardwareAddr net.HardwareAddr Flags net.Flags RawFlags uint32 ParentIndex int // index of the parent link device MasterIndex int // must be the index of a bridge Namespace interface{} // nil | NsPid | NsFd Alias string AltNames []string Statistics *LinkStatistics Promisc int Allmulti int Multi int Xdp *LinkXdp EncapType string Protinfo *Protinfo OperState LinkOperState PhysSwitchID int NetNsID int NumTxQueues int NumRxQueues int TSOMaxSegs uint32 TSOMaxSize uint32 GSOMaxSegs uint32 GSOMaxSize uint32 GROMaxSize uint32 GSOIPv4MaxSize uint32 GROIPv4MaxSize uint32 Vfs []VfInfo // virtual functions available on link Group uint32 PermHWAddr net.HardwareAddr Slave LinkSlave } // LinkSlave represents a slave device. type LinkSlave interface { SlaveType() string } // VfInfo represents configuration of virtual function type VfInfo struct { ID int Mac net.HardwareAddr Vlan int Qos int VlanProto int TxRate int // IFLA_VF_TX_RATE Max TxRate Spoofchk bool LinkState uint32 MaxTxRate uint32 // IFLA_VF_RATE Max TxRate MinTxRate uint32 // IFLA_VF_RATE Min TxRate RxPackets uint64 TxPackets uint64 RxBytes uint64 TxBytes uint64 Multicast uint64 Broadcast uint64 RxDropped uint64 TxDropped uint64 RssQuery uint32 Trust uint32 } // LinkOperState represents the values of the IFLA_OPERSTATE link // attribute, which contains the RFC2863 state of the interface. type LinkOperState uint8 const ( OperUnknown = iota // Status can't be determined. OperNotPresent // Some component is missing. OperDown // Down. OperLowerLayerDown // Down due to state of lower layer. OperTesting // In some test mode. OperDormant // Not up but pending an external event. OperUp // Up, ready to send packets. ) func (s LinkOperState) String() string { switch s { case OperNotPresent: return "not-present" case OperDown: return "down" case OperLowerLayerDown: return "lower-layer-down" case OperTesting: return "testing" case OperDormant: return "dormant" case OperUp: return "up" default: return "unknown" } } // NewLinkAttrs returns LinkAttrs structure filled with default values func NewLinkAttrs() LinkAttrs { return LinkAttrs{ NetNsID: -1, TxQLen: -1, } } type LinkStatistics LinkStatistics64 /* Ref: struct rtnl_link_stats {...} */ type LinkStatistics32 struct { RxPackets uint32 TxPackets uint32 RxBytes uint32 TxBytes uint32 RxErrors uint32 TxErrors uint32 RxDropped uint32 TxDropped uint32 Multicast uint32 Collisions uint32 RxLengthErrors uint32 RxOverErrors uint32 RxCrcErrors uint32 RxFrameErrors uint32 RxFifoErrors uint32 RxMissedErrors uint32 TxAbortedErrors uint32 TxCarrierErrors uint32 TxFifoErrors uint32 TxHeartbeatErrors uint32 TxWindowErrors uint32 RxCompressed uint32 TxCompressed uint32 } func (s32 LinkStatistics32) to64() *LinkStatistics64 { return &LinkStatistics64{ RxPackets: uint64(s32.RxPackets), TxPackets: uint64(s32.TxPackets), RxBytes: uint64(s32.RxBytes), TxBytes: uint64(s32.TxBytes), RxErrors: uint64(s32.RxErrors), TxErrors: uint64(s32.TxErrors), RxDropped: uint64(s32.RxDropped), TxDropped: uint64(s32.TxDropped), Multicast: uint64(s32.Multicast), Collisions: uint64(s32.Collisions), RxLengthErrors: uint64(s32.RxLengthErrors), RxOverErrors: uint64(s32.RxOverErrors), RxCrcErrors: uint64(s32.RxCrcErrors), RxFrameErrors: uint64(s32.RxFrameErrors), RxFifoErrors: uint64(s32.RxFifoErrors), RxMissedErrors: uint64(s32.RxMissedErrors), TxAbortedErrors: uint64(s32.TxAbortedErrors), TxCarrierErrors: uint64(s32.TxCarrierErrors), TxFifoErrors: uint64(s32.TxFifoErrors), TxHeartbeatErrors: uint64(s32.TxHeartbeatErrors), TxWindowErrors: uint64(s32.TxWindowErrors), RxCompressed: uint64(s32.RxCompressed), TxCompressed: uint64(s32.TxCompressed), } } /* Ref: struct rtnl_link_stats64 {...} */ type LinkStatistics64 struct { RxPackets uint64 TxPackets uint64 RxBytes uint64 TxBytes uint64 RxErrors uint64 TxErrors uint64 RxDropped uint64 TxDropped uint64 Multicast uint64 Collisions uint64 RxLengthErrors uint64 RxOverErrors uint64 RxCrcErrors uint64 RxFrameErrors uint64 RxFifoErrors uint64 RxMissedErrors uint64 TxAbortedErrors uint64 TxCarrierErrors uint64 TxFifoErrors uint64 TxHeartbeatErrors uint64 TxWindowErrors uint64 RxCompressed uint64 TxCompressed uint64 } type LinkXdp struct { Fd int Attached bool AttachMode uint32 Flags uint32 ProgId uint32 } // Device links cannot be created via netlink. These links // are links created by udev like 'lo' and 'etho0' type Device struct { LinkAttrs } func (device *Device) Attrs() *LinkAttrs { return &device.LinkAttrs } func (device *Device) Type() string { return "device" } // Dummy links are dummy ethernet devices type Dummy struct { LinkAttrs } func (dummy *Dummy) Attrs() *LinkAttrs { return &dummy.LinkAttrs } func (dummy *Dummy) Type() string { return "dummy" } // Ifb links are advanced dummy devices for packet filtering type Ifb struct { LinkAttrs } func (ifb *Ifb) Attrs() *LinkAttrs { return &ifb.LinkAttrs } func (ifb *Ifb) Type() string { return "ifb" } // Bridge links are simple linux bridges type Bridge struct { LinkAttrs MulticastSnooping *bool AgeingTime *uint32 HelloTime *uint32 VlanFiltering *bool VlanDefaultPVID *uint16 GroupFwdMask *uint16 } func (bridge *Bridge) Attrs() *LinkAttrs { return &bridge.LinkAttrs } func (bridge *Bridge) Type() string { return "bridge" } // Vlan links have ParentIndex set in their Attrs() type Vlan struct { LinkAttrs VlanId int VlanProtocol VlanProtocol } func (vlan *Vlan) Attrs() *LinkAttrs { return &vlan.LinkAttrs } func (vlan *Vlan) Type() string { return "vlan" } type MacvlanMode uint16 const ( MACVLAN_MODE_DEFAULT MacvlanMode = iota MACVLAN_MODE_PRIVATE MACVLAN_MODE_VEPA MACVLAN_MODE_BRIDGE MACVLAN_MODE_PASSTHRU MACVLAN_MODE_SOURCE ) // Macvlan links have ParentIndex set in their Attrs() type Macvlan struct { LinkAttrs Mode MacvlanMode // MACAddrs is only populated for Macvlan SOURCE links MACAddrs []net.HardwareAddr BCQueueLen uint32 UsedBCQueueLen uint32 } func (macvlan *Macvlan) Attrs() *LinkAttrs { return &macvlan.LinkAttrs } func (macvlan *Macvlan) Type() string { return "macvlan" } // Macvtap - macvtap is a virtual interfaces based on macvlan type Macvtap struct { Macvlan } func (macvtap Macvtap) Type() string { return "macvtap" } type TuntapMode uint16 type TuntapFlag uint16 // Tuntap links created via /dev/tun/tap, but can be destroyed via netlink type Tuntap struct { LinkAttrs Mode TuntapMode Flags TuntapFlag NonPersist bool Queues int Fds []*os.File Owner uint32 Group uint32 } func (tuntap *Tuntap) Attrs() *LinkAttrs { return &tuntap.LinkAttrs } func (tuntap *Tuntap) Type() string { return "tuntap" } type NetkitMode uint32 const ( NETKIT_MODE_L2 NetkitMode = iota NETKIT_MODE_L3 ) type NetkitPolicy int const ( NETKIT_POLICY_FORWARD NetkitPolicy = 0 NETKIT_POLICY_BLACKHOLE NetkitPolicy = 2 ) func (n *Netkit) IsPrimary() bool { return n.isPrimary } // SetPeerAttrs will not take effect if trying to modify an existing netkit device func (n *Netkit) SetPeerAttrs(Attrs *LinkAttrs) { n.peerLinkAttrs = *Attrs } type Netkit struct { LinkAttrs Mode NetkitMode Policy NetkitPolicy PeerPolicy NetkitPolicy isPrimary bool peerLinkAttrs LinkAttrs } func (n *Netkit) Attrs() *LinkAttrs { return &n.LinkAttrs } func (n *Netkit) Type() string { return "netkit" } // Veth devices must specify PeerName on create type Veth struct { LinkAttrs PeerName string // veth on create only PeerHardwareAddr net.HardwareAddr PeerNamespace interface{} } func (veth *Veth) Attrs() *LinkAttrs { return &veth.LinkAttrs } func (veth *Veth) Type() string { return "veth" } // Wireguard represent links of type "wireguard", see https://www.wireguard.com/ type Wireguard struct { LinkAttrs } func (wg *Wireguard) Attrs() *LinkAttrs { return &wg.LinkAttrs } func (wg *Wireguard) Type() string { return "wireguard" } // GenericLink links represent types that are not currently understood // by this netlink library. type GenericLink struct { LinkAttrs LinkType string } func (generic *GenericLink) Attrs() *LinkAttrs { return &generic.LinkAttrs } func (generic *GenericLink) Type() string { return generic.LinkType } type Vxlan struct { LinkAttrs VxlanId int VtepDevIndex int SrcAddr net.IP Group net.IP TTL int TOS int Learning bool Proxy bool RSC bool L2miss bool L3miss bool UDPCSum bool UDP6ZeroCSumTx bool UDP6ZeroCSumRx bool NoAge bool GBP bool FlowBased bool Age int Limit int Port int PortLow int PortHigh int } func (vxlan *Vxlan) Attrs() *LinkAttrs { return &vxlan.LinkAttrs } func (vxlan *Vxlan) Type() string { return "vxlan" } type IPVlanMode uint16 const ( IPVLAN_MODE_L2 IPVlanMode = iota IPVLAN_MODE_L3 IPVLAN_MODE_L3S IPVLAN_MODE_MAX ) type IPVlanFlag uint16 const ( IPVLAN_FLAG_BRIDGE IPVlanFlag = iota IPVLAN_FLAG_PRIVATE IPVLAN_FLAG_VEPA ) type IPVlan struct { LinkAttrs Mode IPVlanMode Flag IPVlanFlag } func (ipvlan *IPVlan) Attrs() *LinkAttrs { return &ipvlan.LinkAttrs } func (ipvlan *IPVlan) Type() string { return "ipvlan" } // IPVtap - IPVtap is a virtual interfaces based on ipvlan type IPVtap struct { IPVlan } func (ipvtap *IPVtap) Attrs() *LinkAttrs { return &ipvtap.LinkAttrs } func (ipvtap IPVtap) Type() string { return "ipvtap" } // VlanProtocol type type VlanProtocol int func (p VlanProtocol) String() string { s, ok := VlanProtocolToString[p] if !ok { return fmt.Sprintf("VlanProtocol(%d)", p) } return s } // StringToVlanProtocol returns vlan protocol, or unknown is the s is invalid. func StringToVlanProtocol(s string) VlanProtocol { mode, ok := StringToVlanProtocolMap[s] if !ok { return VLAN_PROTOCOL_UNKNOWN } return mode } // VlanProtocol possible values const ( VLAN_PROTOCOL_UNKNOWN VlanProtocol = 0 VLAN_PROTOCOL_8021Q VlanProtocol = 0x8100 VLAN_PROTOCOL_8021AD VlanProtocol = 0x88A8 ) var VlanProtocolToString = map[VlanProtocol]string{ VLAN_PROTOCOL_8021Q: "802.1q", VLAN_PROTOCOL_8021AD: "802.1ad", } var StringToVlanProtocolMap = map[string]VlanProtocol{ "802.1q": VLAN_PROTOCOL_8021Q, "802.1ad": VLAN_PROTOCOL_8021AD, } // BondMode type type BondMode int func (b BondMode) String() string { s, ok := bondModeToString[b] if !ok { return fmt.Sprintf("BondMode(%d)", b) } return s } // StringToBondMode returns bond mode, or unknown is the s is invalid. func StringToBondMode(s string) BondMode { mode, ok := StringToBondModeMap[s] if !ok { return BOND_MODE_UNKNOWN } return mode } // Possible BondMode const ( BOND_MODE_BALANCE_RR BondMode = iota BOND_MODE_ACTIVE_BACKUP BOND_MODE_BALANCE_XOR BOND_MODE_BROADCAST BOND_MODE_802_3AD BOND_MODE_BALANCE_TLB BOND_MODE_BALANCE_ALB BOND_MODE_UNKNOWN ) var bondModeToString = map[BondMode]string{ BOND_MODE_BALANCE_RR: "balance-rr", BOND_MODE_ACTIVE_BACKUP: "active-backup", BOND_MODE_BALANCE_XOR: "balance-xor", BOND_MODE_BROADCAST: "broadcast", BOND_MODE_802_3AD: "802.3ad", BOND_MODE_BALANCE_TLB: "balance-tlb", BOND_MODE_BALANCE_ALB: "balance-alb", } var StringToBondModeMap = map[string]BondMode{ "balance-rr": BOND_MODE_BALANCE_RR, "active-backup": BOND_MODE_ACTIVE_BACKUP, "balance-xor": BOND_MODE_BALANCE_XOR, "broadcast": BOND_MODE_BROADCAST, "802.3ad": BOND_MODE_802_3AD, "balance-tlb": BOND_MODE_BALANCE_TLB, "balance-alb": BOND_MODE_BALANCE_ALB, } // BondArpValidate type type BondArpValidate int // Possible BondArpValidate value const ( BOND_ARP_VALIDATE_NONE BondArpValidate = iota BOND_ARP_VALIDATE_ACTIVE BOND_ARP_VALIDATE_BACKUP BOND_ARP_VALIDATE_ALL ) var bondArpValidateToString = map[BondArpValidate]string{ BOND_ARP_VALIDATE_NONE: "none", BOND_ARP_VALIDATE_ACTIVE: "active", BOND_ARP_VALIDATE_BACKUP: "backup", BOND_ARP_VALIDATE_ALL: "none", } var StringToBondArpValidateMap = map[string]BondArpValidate{ "none": BOND_ARP_VALIDATE_NONE, "active": BOND_ARP_VALIDATE_ACTIVE, "backup": BOND_ARP_VALIDATE_BACKUP, "all": BOND_ARP_VALIDATE_ALL, } func (b BondArpValidate) String() string { s, ok := bondArpValidateToString[b] if !ok { return fmt.Sprintf("BondArpValidate(%d)", b) } return s } // BondPrimaryReselect type type BondPrimaryReselect int // Possible BondPrimaryReselect value const ( BOND_PRIMARY_RESELECT_ALWAYS BondPrimaryReselect = iota BOND_PRIMARY_RESELECT_BETTER BOND_PRIMARY_RESELECT_FAILURE ) var bondPrimaryReselectToString = map[BondPrimaryReselect]string{ BOND_PRIMARY_RESELECT_ALWAYS: "always", BOND_PRIMARY_RESELECT_BETTER: "better", BOND_PRIMARY_RESELECT_FAILURE: "failure", } var StringToBondPrimaryReselectMap = map[string]BondPrimaryReselect{ "always": BOND_PRIMARY_RESELECT_ALWAYS, "better": BOND_PRIMARY_RESELECT_BETTER, "failure": BOND_PRIMARY_RESELECT_FAILURE, } func (b BondPrimaryReselect) String() string { s, ok := bondPrimaryReselectToString[b] if !ok { return fmt.Sprintf("BondPrimaryReselect(%d)", b) } return s } // BondArpAllTargets type type BondArpAllTargets int // Possible BondArpAllTargets value const ( BOND_ARP_ALL_TARGETS_ANY BondArpAllTargets = iota BOND_ARP_ALL_TARGETS_ALL ) var bondArpAllTargetsToString = map[BondArpAllTargets]string{ BOND_ARP_ALL_TARGETS_ANY: "any", BOND_ARP_ALL_TARGETS_ALL: "all", } var StringToBondArpAllTargetsMap = map[string]BondArpAllTargets{ "any": BOND_ARP_ALL_TARGETS_ANY, "all": BOND_ARP_ALL_TARGETS_ALL, } func (b BondArpAllTargets) String() string { s, ok := bondArpAllTargetsToString[b] if !ok { return fmt.Sprintf("BondArpAllTargets(%d)", b) } return s } // BondFailOverMac type type BondFailOverMac int // Possible BondFailOverMac value const ( BOND_FAIL_OVER_MAC_NONE BondFailOverMac = iota BOND_FAIL_OVER_MAC_ACTIVE BOND_FAIL_OVER_MAC_FOLLOW ) var bondFailOverMacToString = map[BondFailOverMac]string{ BOND_FAIL_OVER_MAC_NONE: "none", BOND_FAIL_OVER_MAC_ACTIVE: "active", BOND_FAIL_OVER_MAC_FOLLOW: "follow", } var StringToBondFailOverMacMap = map[string]BondFailOverMac{ "none": BOND_FAIL_OVER_MAC_NONE, "active": BOND_FAIL_OVER_MAC_ACTIVE, "follow": BOND_FAIL_OVER_MAC_FOLLOW, } func (b BondFailOverMac) String() string { s, ok := bondFailOverMacToString[b] if !ok { return fmt.Sprintf("BondFailOverMac(%d)", b) } return s } // BondXmitHashPolicy type type BondXmitHashPolicy int func (b BondXmitHashPolicy) String() string { s, ok := bondXmitHashPolicyToString[b] if !ok { return fmt.Sprintf("XmitHashPolicy(%d)", b) } return s } // StringToBondXmitHashPolicy returns bond lacp arte, or unknown is the s is invalid. func StringToBondXmitHashPolicy(s string) BondXmitHashPolicy { lacp, ok := StringToBondXmitHashPolicyMap[s] if !ok { return BOND_XMIT_HASH_POLICY_UNKNOWN } return lacp } // Possible BondXmitHashPolicy value const ( BOND_XMIT_HASH_POLICY_LAYER2 BondXmitHashPolicy = iota BOND_XMIT_HASH_POLICY_LAYER3_4 BOND_XMIT_HASH_POLICY_LAYER2_3 BOND_XMIT_HASH_POLICY_ENCAP2_3 BOND_XMIT_HASH_POLICY_ENCAP3_4 BOND_XMIT_HASH_POLICY_VLAN_SRCMAC BOND_XMIT_HASH_POLICY_UNKNOWN ) var bondXmitHashPolicyToString = map[BondXmitHashPolicy]string{ BOND_XMIT_HASH_POLICY_LAYER2: "layer2", BOND_XMIT_HASH_POLICY_LAYER3_4: "layer3+4", BOND_XMIT_HASH_POLICY_LAYER2_3: "layer2+3", BOND_XMIT_HASH_POLICY_ENCAP2_3: "encap2+3", BOND_XMIT_HASH_POLICY_ENCAP3_4: "encap3+4", BOND_XMIT_HASH_POLICY_VLAN_SRCMAC: "vlan+srcmac", } var StringToBondXmitHashPolicyMap = map[string]BondXmitHashPolicy{ "layer2": BOND_XMIT_HASH_POLICY_LAYER2, "layer3+4": BOND_XMIT_HASH_POLICY_LAYER3_4, "layer2+3": BOND_XMIT_HASH_POLICY_LAYER2_3, "encap2+3": BOND_XMIT_HASH_POLICY_ENCAP2_3, "encap3+4": BOND_XMIT_HASH_POLICY_ENCAP3_4, "vlan+srcmac": BOND_XMIT_HASH_POLICY_VLAN_SRCMAC, } // BondLacpRate type type BondLacpRate int func (b BondLacpRate) String() string { s, ok := bondLacpRateToString[b] if !ok { return fmt.Sprintf("LacpRate(%d)", b) } return s } // StringToBondLacpRate returns bond lacp arte, or unknown is the s is invalid. func StringToBondLacpRate(s string) BondLacpRate { lacp, ok := StringToBondLacpRateMap[s] if !ok { return BOND_LACP_RATE_UNKNOWN } return lacp } // Possible BondLacpRate value const ( BOND_LACP_RATE_SLOW BondLacpRate = iota BOND_LACP_RATE_FAST BOND_LACP_RATE_UNKNOWN ) var bondLacpRateToString = map[BondLacpRate]string{ BOND_LACP_RATE_SLOW: "slow", BOND_LACP_RATE_FAST: "fast", } var StringToBondLacpRateMap = map[string]BondLacpRate{ "slow": BOND_LACP_RATE_SLOW, "fast": BOND_LACP_RATE_FAST, } // BondAdSelect type type BondAdSelect int // Possible BondAdSelect value const ( BOND_AD_SELECT_STABLE BondAdSelect = iota BOND_AD_SELECT_BANDWIDTH BOND_AD_SELECT_COUNT ) var bondAdSelectToString = map[BondAdSelect]string{ BOND_AD_SELECT_STABLE: "stable", BOND_AD_SELECT_BANDWIDTH: "bandwidth", BOND_AD_SELECT_COUNT: "count", } var StringToBondAdSelectMap = map[string]BondAdSelect{ "stable": BOND_AD_SELECT_STABLE, "bandwidth": BOND_AD_SELECT_BANDWIDTH, "count": BOND_AD_SELECT_COUNT, } func (b BondAdSelect) String() string { s, ok := bondAdSelectToString[b] if !ok { return fmt.Sprintf("BondAdSelect(%d)", b) } return s } // BondAdInfo represents ad info for bond type BondAdInfo struct { AggregatorId int NumPorts int ActorKey int PartnerKey int PartnerMac net.HardwareAddr } // Bond representation type Bond struct { LinkAttrs Mode BondMode ActiveSlave int Miimon int UpDelay int DownDelay int UseCarrier int ArpInterval int ArpIpTargets []net.IP ArpValidate BondArpValidate ArpAllTargets BondArpAllTargets Primary int PrimaryReselect BondPrimaryReselect FailOverMac BondFailOverMac XmitHashPolicy BondXmitHashPolicy ResendIgmp int NumPeerNotif int AllSlavesActive int MinLinks int LpInterval int PacketsPerSlave int LacpRate BondLacpRate AdSelect BondAdSelect // looking at iproute tool AdInfo can only be retrived. It can't be set. AdInfo *BondAdInfo AdActorSysPrio int AdUserPortKey int AdActorSystem net.HardwareAddr TlbDynamicLb int } func NewLinkBond(atr LinkAttrs) *Bond { return &Bond{ LinkAttrs: atr, Mode: -1, ActiveSlave: -1, Miimon: -1, UpDelay: -1, DownDelay: -1, UseCarrier: -1, ArpInterval: -1, ArpIpTargets: nil, ArpValidate: -1, ArpAllTargets: -1, Primary: -1, PrimaryReselect: -1, FailOverMac: -1, XmitHashPolicy: -1, ResendIgmp: -1, NumPeerNotif: -1, AllSlavesActive: -1, MinLinks: -1, LpInterval: -1, PacketsPerSlave: -1, LacpRate: -1, AdSelect: -1, AdActorSysPrio: -1, AdUserPortKey: -1, AdActorSystem: nil, TlbDynamicLb: -1, } } // Flag mask for bond options. Bond.Flagmask must be set to on for option to work. const ( BOND_MODE_MASK uint64 = 1 << (1 + iota) BOND_ACTIVE_SLAVE_MASK BOND_MIIMON_MASK BOND_UPDELAY_MASK BOND_DOWNDELAY_MASK BOND_USE_CARRIER_MASK BOND_ARP_INTERVAL_MASK BOND_ARP_VALIDATE_MASK BOND_ARP_ALL_TARGETS_MASK BOND_PRIMARY_MASK BOND_PRIMARY_RESELECT_MASK BOND_FAIL_OVER_MAC_MASK BOND_XMIT_HASH_POLICY_MASK BOND_RESEND_IGMP_MASK BOND_NUM_PEER_NOTIF_MASK BOND_ALL_SLAVES_ACTIVE_MASK BOND_MIN_LINKS_MASK BOND_LP_INTERVAL_MASK BOND_PACKETS_PER_SLAVE_MASK BOND_LACP_RATE_MASK BOND_AD_SELECT_MASK ) // Attrs implementation. func (bond *Bond) Attrs() *LinkAttrs { return &bond.LinkAttrs } // Type implementation fro Vxlan. func (bond *Bond) Type() string { return "bond" } // BondSlaveState represents the values of the IFLA_BOND_SLAVE_STATE bond slave // attribute, which contains the state of the bond slave. type BondSlaveState uint8 const ( //BondStateActive Link is active. BondStateActive BondSlaveState = iota //BondStateBackup Link is backup. BondStateBackup ) func (s BondSlaveState) String() string { switch s { case BondStateActive: return "ACTIVE" case BondStateBackup: return "BACKUP" default: return strconv.Itoa(int(s)) } } // BondSlaveMiiStatus represents the values of the IFLA_BOND_SLAVE_MII_STATUS bond slave // attribute, which contains the status of MII link monitoring type BondSlaveMiiStatus uint8 const ( //BondLinkUp link is up and running. BondLinkUp BondSlaveMiiStatus = iota //BondLinkFail link has just gone down. BondLinkFail //BondLinkDown link has been down for too long time. BondLinkDown //BondLinkBack link is going back. BondLinkBack ) func (s BondSlaveMiiStatus) String() string { switch s { case BondLinkUp: return "UP" case BondLinkFail: return "GOING_DOWN" case BondLinkDown: return "DOWN" case BondLinkBack: return "GOING_BACK" default: return strconv.Itoa(int(s)) } } type BondSlave struct { State BondSlaveState MiiStatus BondSlaveMiiStatus LinkFailureCount uint32 PermHardwareAddr net.HardwareAddr QueueId uint16 AggregatorId uint16 AdActorOperPortState uint8 AdPartnerOperPortState uint16 } func (b *BondSlave) SlaveType() string { return "bond" } type VrfSlave struct { Table uint32 } func (v *VrfSlave) SlaveType() string { return "vrf" } // Geneve devices must specify RemoteIP and ID (VNI) on create // https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/drivers/net/geneve.c#L1209-L1223 type Geneve struct { LinkAttrs ID uint32 // vni Remote net.IP Ttl uint8 Tos uint8 Dport uint16 UdpCsum uint8 UdpZeroCsum6Tx uint8 UdpZeroCsum6Rx uint8 Link uint32 FlowBased bool InnerProtoInherit bool Df GeneveDf } func (geneve *Geneve) Attrs() *LinkAttrs { return &geneve.LinkAttrs } func (geneve *Geneve) Type() string { return "geneve" } type GeneveDf uint8 const ( GENEVE_DF_UNSET GeneveDf = iota GENEVE_DF_SET GENEVE_DF_INHERIT GENEVE_DF_MAX ) // Gretap devices must specify LocalIP and RemoteIP on create type Gretap struct { LinkAttrs IKey uint32 OKey uint32 EncapSport uint16 EncapDport uint16 Local net.IP Remote net.IP IFlags uint16 OFlags uint16 PMtuDisc uint8 Ttl uint8 Tos uint8 EncapType uint16 EncapFlags uint16 Link uint32 FlowBased bool } func (gretap *Gretap) Attrs() *LinkAttrs { return &gretap.LinkAttrs } func (gretap *Gretap) Type() string { if gretap.Local.To4() == nil { return "ip6gretap" } return "gretap" } type Iptun struct { LinkAttrs Ttl uint8 Tos uint8 PMtuDisc uint8 Link uint32 Local net.IP Remote net.IP EncapSport uint16 EncapDport uint16 EncapType uint16 EncapFlags uint16 FlowBased bool Proto uint8 } func (iptun *Iptun) Attrs() *LinkAttrs { return &iptun.LinkAttrs } func (iptun *Iptun) Type() string { return "ipip" } type Ip6tnl struct { LinkAttrs Link uint32 Local net.IP Remote net.IP Ttl uint8 Tos uint8 Flags uint32 Proto uint8 FlowInfo uint32 EncapLimit uint8 EncapType uint16 EncapFlags uint16 EncapSport uint16 EncapDport uint16 FlowBased bool } func (ip6tnl *Ip6tnl) Attrs() *LinkAttrs { return &ip6tnl.LinkAttrs } func (ip6tnl *Ip6tnl) Type() string { return "ip6tnl" } // from https://elixir.bootlin.com/linux/v5.15.4/source/include/uapi/linux/if_tunnel.h#L84 type TunnelEncapType uint16 const ( None TunnelEncapType = iota FOU GUE ) // from https://elixir.bootlin.com/linux/v5.15.4/source/include/uapi/linux/if_tunnel.h#L91 type TunnelEncapFlag uint16 const ( CSum TunnelEncapFlag = 1 << 0 CSum6 = 1 << 1 RemCSum = 1 << 2 ) // from https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ip6_tunnel.h#L12 type IP6TunnelFlag uint16 const ( IP6_TNL_F_IGN_ENCAP_LIMIT IP6TunnelFlag = 1 // don't add encapsulation limit if one isn't present in inner packet IP6_TNL_F_USE_ORIG_TCLASS = 2 // copy the traffic class field from the inner packet IP6_TNL_F_USE_ORIG_FLOWLABEL = 4 // copy the flowlabel from the inner packet IP6_TNL_F_MIP6_DEV = 8 // being used for Mobile IPv6 IP6_TNL_F_RCV_DSCP_COPY = 10 // copy DSCP from the outer packet IP6_TNL_F_USE_ORIG_FWMARK = 20 // copy fwmark from inner packet IP6_TNL_F_ALLOW_LOCAL_REMOTE = 40 // allow remote endpoint on the local node ) type Sittun struct { LinkAttrs Link uint32 Ttl uint8 Tos uint8 PMtuDisc uint8 Proto uint8 Local net.IP Remote net.IP EncapLimit uint8 EncapType uint16 EncapFlags uint16 EncapSport uint16 EncapDport uint16 } func (sittun *Sittun) Attrs() *LinkAttrs { return &sittun.LinkAttrs } func (sittun *Sittun) Type() string { return "sit" } type Vti struct { LinkAttrs IKey uint32 OKey uint32 Link uint32 Local net.IP Remote net.IP } func (vti *Vti) Attrs() *LinkAttrs { return &vti.LinkAttrs } func (vti *Vti) Type() string { if vti.Local.To4() == nil { return "vti6" } return "vti" } type Gretun struct { LinkAttrs Link uint32 IFlags uint16 OFlags uint16 IKey uint32 OKey uint32 Local net.IP Remote net.IP Ttl uint8 Tos uint8 PMtuDisc uint8 EncapType uint16 EncapFlags uint16 EncapSport uint16 EncapDport uint16 FlowBased bool } func (gretun *Gretun) Attrs() *LinkAttrs { return &gretun.LinkAttrs } func (gretun *Gretun) Type() string { if gretun.Local.To4() == nil { return "ip6gre" } return "gre" } type Vrf struct { LinkAttrs Table uint32 } func (vrf *Vrf) Attrs() *LinkAttrs { return &vrf.LinkAttrs } func (vrf *Vrf) Type() string { return "vrf" } type GTP struct { LinkAttrs FD0 int FD1 int Role int PDPHashsize int } func (gtp *GTP) Attrs() *LinkAttrs { return >p.LinkAttrs } func (gtp *GTP) Type() string { return "gtp" } // Virtual XFRM Interfaces // // Named "xfrmi" to prevent confusion with XFRM objects type Xfrmi struct { LinkAttrs Ifid uint32 } func (xfrm *Xfrmi) Attrs() *LinkAttrs { return &xfrm.LinkAttrs } func (xfrm *Xfrmi) Type() string { return "xfrm" } // IPoIB interface type IPoIBMode uint16 func (m *IPoIBMode) String() string { str, ok := iPoIBModeToString[*m] if !ok { return fmt.Sprintf("mode(%d)", *m) } return str } const ( IPOIB_MODE_DATAGRAM = iota IPOIB_MODE_CONNECTED ) var iPoIBModeToString = map[IPoIBMode]string{ IPOIB_MODE_DATAGRAM: "datagram", IPOIB_MODE_CONNECTED: "connected", } var StringToIPoIBMode = map[string]IPoIBMode{ "datagram": IPOIB_MODE_DATAGRAM, "connected": IPOIB_MODE_CONNECTED, } const ( CAN_STATE_ERROR_ACTIVE = iota CAN_STATE_ERROR_WARNING CAN_STATE_ERROR_PASSIVE CAN_STATE_BUS_OFF CAN_STATE_STOPPED CAN_STATE_SLEEPING ) type Can struct { LinkAttrs BitRate uint32 SamplePoint uint32 TimeQuanta uint32 PropagationSegment uint32 PhaseSegment1 uint32 PhaseSegment2 uint32 SyncJumpWidth uint32 BitRatePreScaler uint32 Name string TimeSegment1Min uint32 TimeSegment1Max uint32 TimeSegment2Min uint32 TimeSegment2Max uint32 SyncJumpWidthMax uint32 BitRatePreScalerMin uint32 BitRatePreScalerMax uint32 BitRatePreScalerInc uint32 ClockFrequency uint32 State uint32 Mask uint32 Flags uint32 TxError uint16 RxError uint16 RestartMs uint32 } func (can *Can) Attrs() *LinkAttrs { return &can.LinkAttrs } func (can *Can) Type() string { return "can" } type IPoIB struct { LinkAttrs Pkey uint16 Mode IPoIBMode Umcast uint16 } func (ipoib *IPoIB) Attrs() *LinkAttrs { return &ipoib.LinkAttrs } func (ipoib *IPoIB) Type() string { return "ipoib" } type BareUDP struct { LinkAttrs Port uint16 EtherType uint16 SrcPortMin uint16 MultiProto bool } func (bareudp *BareUDP) Attrs() *LinkAttrs { return &bareudp.LinkAttrs } func (bareudp *BareUDP) Type() string { return "bareudp" } // iproute2 supported devices; // vlan | veth | vcan | dummy | ifb | macvlan | macvtap | // bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | // gre | gretap | ip6gre | ip6gretap | vti | vti6 | nlmon | // bond_slave | ipvlan | xfrm | bareudp // LinkNotFoundError wraps the various not found errors when // getting/reading links. This is intended for better error // handling by dependent code so that "not found error" can // be distinguished from other errors type LinkNotFoundError struct { error } netlink-1.3.0/link_linux.go000066400000000000000000003476351466216277000157170ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "fmt" "io/ioutil" "net" "os" "strconv" "strings" "syscall" "unsafe" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) const ( SizeofLinkStats32 = 0x5c SizeofLinkStats64 = 0xb8 ) const ( TUNTAP_MODE_TUN TuntapMode = unix.IFF_TUN TUNTAP_MODE_TAP TuntapMode = unix.IFF_TAP TUNTAP_DEFAULTS TuntapFlag = unix.IFF_TUN_EXCL | unix.IFF_ONE_QUEUE TUNTAP_VNET_HDR TuntapFlag = unix.IFF_VNET_HDR TUNTAP_TUN_EXCL TuntapFlag = unix.IFF_TUN_EXCL TUNTAP_NO_PI TuntapFlag = unix.IFF_NO_PI TUNTAP_ONE_QUEUE TuntapFlag = unix.IFF_ONE_QUEUE TUNTAP_MULTI_QUEUE TuntapFlag = unix.IFF_MULTI_QUEUE TUNTAP_MULTI_QUEUE_DEFAULTS TuntapFlag = TUNTAP_MULTI_QUEUE | TUNTAP_NO_PI ) var StringToTuntapModeMap = map[string]TuntapMode{ "tun": TUNTAP_MODE_TUN, "tap": TUNTAP_MODE_TAP, } func (ttm TuntapMode) String() string { switch ttm { case TUNTAP_MODE_TUN: return "tun" case TUNTAP_MODE_TAP: return "tap" } return "unknown" } const ( VF_LINK_STATE_AUTO uint32 = 0 VF_LINK_STATE_ENABLE uint32 = 1 VF_LINK_STATE_DISABLE uint32 = 2 ) var macvlanModes = [...]uint32{ 0, nl.MACVLAN_MODE_PRIVATE, nl.MACVLAN_MODE_VEPA, nl.MACVLAN_MODE_BRIDGE, nl.MACVLAN_MODE_PASSTHRU, nl.MACVLAN_MODE_SOURCE, } func ensureIndex(link *LinkAttrs) { if link != nil && link.Index == 0 { newlink, _ := LinkByName(link.Name) if newlink != nil { link.Index = newlink.Attrs().Index } } } func (h *Handle) ensureIndex(link *LinkAttrs) { if link != nil && link.Index == 0 { newlink, _ := h.LinkByName(link.Name) if newlink != nil { link.Index = newlink.Attrs().Index } } } func (h *Handle) LinkSetARPOff(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change |= unix.IFF_NOARP msg.Flags |= unix.IFF_NOARP msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func LinkSetARPOff(link Link) error { return pkgHandle.LinkSetARPOff(link) } func (h *Handle) LinkSetARPOn(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change |= unix.IFF_NOARP msg.Flags &= ^uint32(unix.IFF_NOARP) msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func LinkSetARPOn(link Link) error { return pkgHandle.LinkSetARPOn(link) } func (h *Handle) SetPromiscOn(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_PROMISC msg.Flags = unix.IFF_PROMISC msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetAllmulticastOn enables the reception of all hardware multicast packets for the link device. // Equivalent to: `ip link set $link allmulticast on` func LinkSetAllmulticastOn(link Link) error { return pkgHandle.LinkSetAllmulticastOn(link) } // LinkSetAllmulticastOn enables the reception of all hardware multicast packets for the link device. // Equivalent to: `ip link set $link allmulticast on` func (h *Handle) LinkSetAllmulticastOn(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_ALLMULTI msg.Flags = unix.IFF_ALLMULTI msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetAllmulticastOff disables the reception of all hardware multicast packets for the link device. // Equivalent to: `ip link set $link allmulticast off` func LinkSetAllmulticastOff(link Link) error { return pkgHandle.LinkSetAllmulticastOff(link) } // LinkSetAllmulticastOff disables the reception of all hardware multicast packets for the link device. // Equivalent to: `ip link set $link allmulticast off` func (h *Handle) LinkSetAllmulticastOff(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_ALLMULTI msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetMulticastOn enables the reception of multicast packets for the link device. // Equivalent to: `ip link set $link multicast on` func LinkSetMulticastOn(link Link) error { return pkgHandle.LinkSetMulticastOn(link) } // LinkSetMulticastOn enables the reception of multicast packets for the link device. // Equivalent to: `ip link set $link multicast on` func (h *Handle) LinkSetMulticastOn(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_MULTICAST msg.Flags = unix.IFF_MULTICAST msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetAllmulticastOff disables the reception of multicast packets for the link device. // Equivalent to: `ip link set $link multicast off` func LinkSetMulticastOff(link Link) error { return pkgHandle.LinkSetMulticastOff(link) } // LinkSetAllmulticastOff disables the reception of multicast packets for the link device. // Equivalent to: `ip link set $link multicast off` func (h *Handle) LinkSetMulticastOff(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_MULTICAST msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func MacvlanMACAddrAdd(link Link, addr net.HardwareAddr) error { return pkgHandle.MacvlanMACAddrAdd(link, addr) } func (h *Handle) MacvlanMACAddrAdd(link Link, addr net.HardwareAddr) error { return h.macvlanMACAddrChange(link, []net.HardwareAddr{addr}, nl.MACVLAN_MACADDR_ADD) } func MacvlanMACAddrDel(link Link, addr net.HardwareAddr) error { return pkgHandle.MacvlanMACAddrDel(link, addr) } func (h *Handle) MacvlanMACAddrDel(link Link, addr net.HardwareAddr) error { return h.macvlanMACAddrChange(link, []net.HardwareAddr{addr}, nl.MACVLAN_MACADDR_DEL) } func MacvlanMACAddrFlush(link Link) error { return pkgHandle.MacvlanMACAddrFlush(link) } func (h *Handle) MacvlanMACAddrFlush(link Link) error { return h.macvlanMACAddrChange(link, nil, nl.MACVLAN_MACADDR_FLUSH) } func MacvlanMACAddrSet(link Link, addrs []net.HardwareAddr) error { return pkgHandle.MacvlanMACAddrSet(link, addrs) } func (h *Handle) MacvlanMACAddrSet(link Link, addrs []net.HardwareAddr) error { return h.macvlanMACAddrChange(link, addrs, nl.MACVLAN_MACADDR_SET) } func (h *Handle) macvlanMACAddrChange(link Link, addrs []net.HardwareAddr, mode uint32) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) linkInfo.AddRtAttr(nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type())) inner := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) // IFLA_MACVLAN_MACADDR_MODE = mode b := make([]byte, 4) native.PutUint32(b, mode) inner.AddRtAttr(nl.IFLA_MACVLAN_MACADDR_MODE, b) // populate message with MAC addrs, if necessary switch mode { case nl.MACVLAN_MACADDR_ADD, nl.MACVLAN_MACADDR_DEL: if len(addrs) == 1 { inner.AddRtAttr(nl.IFLA_MACVLAN_MACADDR, []byte(addrs[0])) } case nl.MACVLAN_MACADDR_SET: mad := inner.AddRtAttr(nl.IFLA_MACVLAN_MACADDR_DATA, nil) for _, addr := range addrs { mad.AddRtAttr(nl.IFLA_MACVLAN_MACADDR, []byte(addr)) } } req.AddData(linkInfo) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetMacvlanMode sets the mode of a macvlan or macvtap link device. // Note that passthrough mode cannot be set to and from and will fail. // Equivalent to: `ip link set $link type (macvlan|macvtap) mode $mode func LinkSetMacvlanMode(link Link, mode MacvlanMode) error { return pkgHandle.LinkSetMacvlanMode(link, mode) } // LinkSetMacvlanMode sets the mode of the macvlan or macvtap link device. // Note that passthrough mode cannot be set to and from and will fail. // Equivalent to: `ip link set $link type (macvlan|macvtap) mode $mode func (h *Handle) LinkSetMacvlanMode(link Link, mode MacvlanMode) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) linkInfo.AddRtAttr(nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type())) data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[mode])) req.AddData(linkInfo) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func BridgeSetMcastSnoop(link Link, on bool) error { return pkgHandle.BridgeSetMcastSnoop(link, on) } func (h *Handle) BridgeSetMcastSnoop(link Link, on bool) error { bridge := link.(*Bridge) bridge.MulticastSnooping = &on return h.linkModify(bridge, unix.NLM_F_ACK) } func BridgeSetVlanFiltering(link Link, on bool) error { return pkgHandle.BridgeSetVlanFiltering(link, on) } func (h *Handle) BridgeSetVlanFiltering(link Link, on bool) error { bridge := link.(*Bridge) bridge.VlanFiltering = &on return h.linkModify(bridge, unix.NLM_F_ACK) } func BridgeSetVlanDefaultPVID(link Link, pvid uint16) error { return pkgHandle.BridgeSetVlanDefaultPVID(link, pvid) } func (h *Handle) BridgeSetVlanDefaultPVID(link Link, pvid uint16) error { bridge := link.(*Bridge) bridge.VlanDefaultPVID = &pvid return h.linkModify(bridge, unix.NLM_F_ACK) } func SetPromiscOn(link Link) error { return pkgHandle.SetPromiscOn(link) } func (h *Handle) SetPromiscOff(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_PROMISC msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func SetPromiscOff(link Link) error { return pkgHandle.SetPromiscOff(link) } // LinkSetUp enables the link device. // Equivalent to: `ip link set $link up` func LinkSetUp(link Link) error { return pkgHandle.LinkSetUp(link) } // LinkSetUp enables the link device. // Equivalent to: `ip link set $link up` func (h *Handle) LinkSetUp(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_UP msg.Flags = unix.IFF_UP msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetDown disables link device. // Equivalent to: `ip link set $link down` func LinkSetDown(link Link) error { return pkgHandle.LinkSetDown(link) } // LinkSetDown disables link device. // Equivalent to: `ip link set $link down` func (h *Handle) LinkSetDown(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Change = unix.IFF_UP msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetMTU sets the mtu of the link device. // Equivalent to: `ip link set $link mtu $mtu` func LinkSetMTU(link Link, mtu int) error { return pkgHandle.LinkSetMTU(link, mtu) } // LinkSetMTU sets the mtu of the link device. // Equivalent to: `ip link set $link mtu $mtu` func (h *Handle) LinkSetMTU(link Link, mtu int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(mtu)) data := nl.NewRtAttr(unix.IFLA_MTU, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetName sets the name of the link device. // Equivalent to: `ip link set $link name $name` func LinkSetName(link Link, name string) error { return pkgHandle.LinkSetName(link, name) } // LinkSetName sets the name of the link device. // Equivalent to: `ip link set $link name $name` func (h *Handle) LinkSetName(link Link, name string) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_IFNAME, []byte(name)) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetAlias sets the alias of the link device. // Equivalent to: `ip link set dev $link alias $name` func LinkSetAlias(link Link, name string) error { return pkgHandle.LinkSetAlias(link, name) } // LinkSetAlias sets the alias of the link device. // Equivalent to: `ip link set dev $link alias $name` func (h *Handle) LinkSetAlias(link Link, name string) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_IFALIAS, []byte(name)) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkAddAltName adds a new alternative name for the link device. // Equivalent to: `ip link property add $link altname $name` func LinkAddAltName(link Link, name string) error { return pkgHandle.LinkAddAltName(link, name) } // LinkAddAltName adds a new alternative name for the link device. // Equivalent to: `ip link property add $link altname $name` func (h *Handle) LinkAddAltName(link Link, name string) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_NEWLINKPROP, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_PROP_LIST|unix.NLA_F_NESTED, nil) data.AddRtAttr(unix.IFLA_ALT_IFNAME, []byte(name)) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkDelAltName delete an alternative name for the link device. // Equivalent to: `ip link property del $link altname $name` func LinkDelAltName(link Link, name string) error { return pkgHandle.LinkDelAltName(link, name) } // LinkDelAltName delete an alternative name for the link device. // Equivalent to: `ip link property del $link altname $name` func (h *Handle) LinkDelAltName(link Link, name string) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_DELLINKPROP, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_PROP_LIST|unix.NLA_F_NESTED, nil) data.AddRtAttr(unix.IFLA_ALT_IFNAME, []byte(name)) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetHardwareAddr sets the hardware address of the link device. // Equivalent to: `ip link set $link address $hwaddr` func LinkSetHardwareAddr(link Link, hwaddr net.HardwareAddr) error { return pkgHandle.LinkSetHardwareAddr(link, hwaddr) } // LinkSetHardwareAddr sets the hardware address of the link device. // Equivalent to: `ip link set $link address $hwaddr` func (h *Handle) LinkSetHardwareAddr(link Link, hwaddr net.HardwareAddr) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_ADDRESS, []byte(hwaddr)) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfHardwareAddr sets the hardware address of a vf for the link. // Equivalent to: `ip link set $link vf $vf mac $hwaddr` func LinkSetVfHardwareAddr(link Link, vf int, hwaddr net.HardwareAddr) error { return pkgHandle.LinkSetVfHardwareAddr(link, vf, hwaddr) } // LinkSetVfHardwareAddr sets the hardware address of a vf for the link. // Equivalent to: `ip link set $link vf $vf mac $hwaddr` func (h *Handle) LinkSetVfHardwareAddr(link Link, vf int, hwaddr net.HardwareAddr) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfMac{ Vf: uint32(vf), } copy(vfmsg.Mac[:], []byte(hwaddr)) info.AddRtAttr(nl.IFLA_VF_MAC, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfVlan sets the vlan of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan` func LinkSetVfVlan(link Link, vf, vlan int) error { return pkgHandle.LinkSetVfVlan(link, vf, vlan) } // LinkSetVfVlan sets the vlan of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan` func (h *Handle) LinkSetVfVlan(link Link, vf, vlan int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfVlan{ Vf: uint32(vf), Vlan: uint32(vlan), } info.AddRtAttr(nl.IFLA_VF_VLAN, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfVlanQos sets the vlan and qos priority of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan qos $qos` func LinkSetVfVlanQos(link Link, vf, vlan, qos int) error { return pkgHandle.LinkSetVfVlanQos(link, vf, vlan, qos) } // LinkSetVfVlanQos sets the vlan and qos priority of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan qos $qos` func (h *Handle) LinkSetVfVlanQos(link Link, vf, vlan, qos int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfVlan{ Vf: uint32(vf), Vlan: uint32(vlan), Qos: uint32(qos), } info.AddRtAttr(nl.IFLA_VF_VLAN, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfVlanQosProto sets the vlan, qos and protocol of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan qos $qos proto $proto` func LinkSetVfVlanQosProto(link Link, vf, vlan, qos, proto int) error { return pkgHandle.LinkSetVfVlanQosProto(link, vf, vlan, qos, proto) } // LinkSetVfVlanQosProto sets the vlan, qos and protocol of a vf for the link. // Equivalent to: `ip link set $link vf $vf vlan $vlan qos $qos proto $proto` func (h *Handle) LinkSetVfVlanQosProto(link Link, vf, vlan, qos, proto int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) vfInfo := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfVlanList := vfInfo.AddRtAttr(nl.IFLA_VF_VLAN_LIST, nil) vfmsg := nl.VfVlanInfo{ VfVlan: nl.VfVlan{ Vf: uint32(vf), Vlan: uint32(vlan), Qos: uint32(qos), }, VlanProto: (uint16(proto)>>8)&0xFF | (uint16(proto)&0xFF)<<8, } vfVlanList.AddRtAttr(nl.IFLA_VF_VLAN_INFO, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfTxRate sets the tx rate of a vf for the link. // Equivalent to: `ip link set $link vf $vf rate $rate` func LinkSetVfTxRate(link Link, vf, rate int) error { return pkgHandle.LinkSetVfTxRate(link, vf, rate) } // LinkSetVfTxRate sets the tx rate of a vf for the link. // Equivalent to: `ip link set $link vf $vf rate $rate` func (h *Handle) LinkSetVfTxRate(link Link, vf, rate int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfTxRate{ Vf: uint32(vf), Rate: uint32(rate), } info.AddRtAttr(nl.IFLA_VF_TX_RATE, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfRate sets the min and max tx rate of a vf for the link. // Equivalent to: `ip link set $link vf $vf min_tx_rate $min_rate max_tx_rate $max_rate` func LinkSetVfRate(link Link, vf, minRate, maxRate int) error { return pkgHandle.LinkSetVfRate(link, vf, minRate, maxRate) } // LinkSetVfRate sets the min and max tx rate of a vf for the link. // Equivalent to: `ip link set $link vf $vf min_tx_rate $min_rate max_tx_rate $max_rate` func (h *Handle) LinkSetVfRate(link Link, vf, minRate, maxRate int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfRate{ Vf: uint32(vf), MinTxRate: uint32(minRate), MaxTxRate: uint32(maxRate), } info.AddRtAttr(nl.IFLA_VF_RATE, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfState enables/disables virtual link state on a vf. // Equivalent to: `ip link set $link vf $vf state $state` func LinkSetVfState(link Link, vf int, state uint32) error { return pkgHandle.LinkSetVfState(link, vf, state) } // LinkSetVfState enables/disables virtual link state on a vf. // Equivalent to: `ip link set $link vf $vf state $state` func (h *Handle) LinkSetVfState(link Link, vf int, state uint32) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfLinkState{ Vf: uint32(vf), LinkState: state, } info.AddRtAttr(nl.IFLA_VF_LINK_STATE, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfSpoofchk enables/disables spoof check on a vf for the link. // Equivalent to: `ip link set $link vf $vf spoofchk $check` func LinkSetVfSpoofchk(link Link, vf int, check bool) error { return pkgHandle.LinkSetVfSpoofchk(link, vf, check) } // LinkSetVfSpoofchk enables/disables spoof check on a vf for the link. // Equivalent to: `ip link set $link vf $vf spoofchk $check` func (h *Handle) LinkSetVfSpoofchk(link Link, vf int, check bool) error { var setting uint32 base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) if check { setting = 1 } vfmsg := nl.VfSpoofchk{ Vf: uint32(vf), Setting: setting, } info.AddRtAttr(nl.IFLA_VF_SPOOFCHK, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfTrust enables/disables trust state on a vf for the link. // Equivalent to: `ip link set $link vf $vf trust $state` func LinkSetVfTrust(link Link, vf int, state bool) error { return pkgHandle.LinkSetVfTrust(link, vf, state) } // LinkSetVfTrust enables/disables trust state on a vf for the link. // Equivalent to: `ip link set $link vf $vf trust $state` func (h *Handle) LinkSetVfTrust(link Link, vf int, state bool) error { var setting uint32 base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) if state { setting = 1 } vfmsg := nl.VfTrust{ Vf: uint32(vf), Setting: setting, } info.AddRtAttr(nl.IFLA_VF_TRUST, vfmsg.Serialize()) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetVfNodeGUID sets the node GUID of a vf for the link. // Equivalent to: `ip link set dev $link vf $vf node_guid $nodeguid` func LinkSetVfNodeGUID(link Link, vf int, nodeguid net.HardwareAddr) error { return pkgHandle.LinkSetVfGUID(link, vf, nodeguid, nl.IFLA_VF_IB_NODE_GUID) } // LinkSetVfPortGUID sets the port GUID of a vf for the link. // Equivalent to: `ip link set dev $link vf $vf port_guid $portguid` func LinkSetVfPortGUID(link Link, vf int, portguid net.HardwareAddr) error { return pkgHandle.LinkSetVfGUID(link, vf, portguid, nl.IFLA_VF_IB_PORT_GUID) } // LinkSetVfGUID sets the node or port GUID of a vf for the link. func (h *Handle) LinkSetVfGUID(link Link, vf int, vfGuid net.HardwareAddr, guidType int) error { var err error var guid uint64 buf := bytes.NewBuffer(vfGuid) err = binary.Read(buf, binary.BigEndian, &guid) if err != nil { return err } base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) data := nl.NewRtAttr(unix.IFLA_VFINFO_LIST, nil) info := data.AddRtAttr(nl.IFLA_VF_INFO, nil) vfmsg := nl.VfGUID{ Vf: uint32(vf), GUID: guid, } info.AddRtAttr(guidType, vfmsg.Serialize()) req.AddData(data) _, err = req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetMaster sets the master of the link device. // Equivalent to: `ip link set $link master $master` func LinkSetMaster(link Link, master Link) error { return pkgHandle.LinkSetMaster(link, master) } // LinkSetMaster sets the master of the link device. // Equivalent to: `ip link set $link master $master` func (h *Handle) LinkSetMaster(link Link, master Link) error { index := 0 if master != nil { masterBase := master.Attrs() h.ensureIndex(masterBase) index = masterBase.Index } if index <= 0 { return fmt.Errorf("Device does not exist") } return h.LinkSetMasterByIndex(link, index) } // LinkSetNoMaster removes the master of the link device. // Equivalent to: `ip link set $link nomaster` func LinkSetNoMaster(link Link) error { return pkgHandle.LinkSetNoMaster(link) } // LinkSetNoMaster removes the master of the link device. // Equivalent to: `ip link set $link nomaster` func (h *Handle) LinkSetNoMaster(link Link) error { return h.LinkSetMasterByIndex(link, 0) } // LinkSetMasterByIndex sets the master of the link device. // Equivalent to: `ip link set $link master $master` func LinkSetMasterByIndex(link Link, masterIndex int) error { return pkgHandle.LinkSetMasterByIndex(link, masterIndex) } // LinkSetMasterByIndex sets the master of the link device. // Equivalent to: `ip link set $link master $master` func (h *Handle) LinkSetMasterByIndex(link Link, masterIndex int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(masterIndex)) data := nl.NewRtAttr(unix.IFLA_MASTER, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetNsPid puts the device into a new network namespace. The // pid must be a pid of a running process. // Equivalent to: `ip link set $link netns $pid` func LinkSetNsPid(link Link, nspid int) error { return pkgHandle.LinkSetNsPid(link, nspid) } // LinkSetNsPid puts the device into a new network namespace. The // pid must be a pid of a running process. // Equivalent to: `ip link set $link netns $pid` func (h *Handle) LinkSetNsPid(link Link, nspid int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(nspid)) data := nl.NewRtAttr(unix.IFLA_NET_NS_PID, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetNsFd puts the device into a new network namespace. The // fd must be an open file descriptor to a network namespace. // Similar to: `ip link set $link netns $ns` func LinkSetNsFd(link Link, fd int) error { return pkgHandle.LinkSetNsFd(link, fd) } // LinkSetNsFd puts the device into a new network namespace. The // fd must be an open file descriptor to a network namespace. // Similar to: `ip link set $link netns $ns` func (h *Handle) LinkSetNsFd(link Link, fd int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(fd)) data := nl.NewRtAttr(unix.IFLA_NET_NS_FD, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetXdpFd adds a bpf function to the driver. The fd must be a bpf // program loaded with bpf(type=BPF_PROG_TYPE_XDP) func LinkSetXdpFd(link Link, fd int) error { return LinkSetXdpFdWithFlags(link, fd, 0) } // LinkSetXdpFdWithFlags adds a bpf function to the driver with the given // options. The fd must be a bpf program loaded with bpf(type=BPF_PROG_TYPE_XDP) func LinkSetXdpFdWithFlags(link Link, fd, flags int) error { base := link.Attrs() ensureIndex(base) req := nl.NewNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) addXdpAttrs(&LinkXdp{Fd: fd, Flags: uint32(flags)}, req) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGSOMaxSegs sets the GSO maximum segment count of the link device. // Equivalent to: `ip link set $link gso_max_segs $maxSegs` func LinkSetGSOMaxSegs(link Link, maxSegs int) error { return pkgHandle.LinkSetGSOMaxSegs(link, maxSegs) } // LinkSetGSOMaxSegs sets the GSO maximum segment count of the link device. // Equivalent to: `ip link set $link gso_max_segs $maxSegs` func (h *Handle) LinkSetGSOMaxSegs(link Link, maxSize int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(maxSize)) data := nl.NewRtAttr(unix.IFLA_GSO_MAX_SEGS, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGSOMaxSize sets the IPv6 GSO maximum size of the link device. // Equivalent to: `ip link set $link gso_max_size $maxSize` func LinkSetGSOMaxSize(link Link, maxSize int) error { return pkgHandle.LinkSetGSOMaxSize(link, maxSize) } // LinkSetGSOMaxSize sets the IPv6 GSO maximum size of the link device. // Equivalent to: `ip link set $link gso_max_size $maxSize` func (h *Handle) LinkSetGSOMaxSize(link Link, maxSize int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(maxSize)) data := nl.NewRtAttr(unix.IFLA_GSO_MAX_SIZE, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGROMaxSize sets the IPv6 GRO maximum size of the link device. // Equivalent to: `ip link set $link gro_max_size $maxSize` func LinkSetGROMaxSize(link Link, maxSize int) error { return pkgHandle.LinkSetGROMaxSize(link, maxSize) } // LinkSetGROMaxSize sets the IPv6 GRO maximum size of the link device. // Equivalent to: `ip link set $link gro_max_size $maxSize` func (h *Handle) LinkSetGROMaxSize(link Link, maxSize int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(maxSize)) data := nl.NewRtAttr(unix.IFLA_GRO_MAX_SIZE, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGSOIPv4MaxSize sets the IPv4 GSO maximum size of the link device. // Equivalent to: `ip link set $link gso_ipv4_max_size $maxSize` func LinkSetGSOIPv4MaxSize(link Link, maxSize int) error { return pkgHandle.LinkSetGSOIPv4MaxSize(link, maxSize) } // LinkSetGSOIPv4MaxSize sets the IPv4 GSO maximum size of the link device. // Equivalent to: `ip link set $link gso_ipv4_max_size $maxSize` func (h *Handle) LinkSetGSOIPv4MaxSize(link Link, maxSize int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(maxSize)) data := nl.NewRtAttr(unix.IFLA_GSO_IPV4_MAX_SIZE, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGROIPv4MaxSize sets the IPv4 GRO maximum size of the link device. // Equivalent to: `ip link set $link gro_ipv4_max_size $maxSize` func LinkSetGROIPv4MaxSize(link Link, maxSize int) error { return pkgHandle.LinkSetGROIPv4MaxSize(link, maxSize) } // LinkSetGROIPv4MaxSize sets the IPv4 GRO maximum size of the link device. // Equivalent to: `ip link set $link gro_ipv4_max_size $maxSize` func (h *Handle) LinkSetGROIPv4MaxSize(link Link, maxSize int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(maxSize)) data := nl.NewRtAttr(unix.IFLA_GRO_IPV4_MAX_SIZE, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func boolAttr(val bool) []byte { var v uint8 if val { v = 1 } return nl.Uint8Attr(v) } type vxlanPortRange struct { Lo, Hi uint16 } func addVxlanAttrs(vxlan *Vxlan, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if vxlan.FlowBased { vxlan.VxlanId = 0 } data.AddRtAttr(nl.IFLA_VXLAN_ID, nl.Uint32Attr(uint32(vxlan.VxlanId))) if vxlan.VtepDevIndex != 0 { data.AddRtAttr(nl.IFLA_VXLAN_LINK, nl.Uint32Attr(uint32(vxlan.VtepDevIndex))) } if vxlan.SrcAddr != nil { ip := vxlan.SrcAddr.To4() if ip != nil { data.AddRtAttr(nl.IFLA_VXLAN_LOCAL, []byte(ip)) } else { ip = vxlan.SrcAddr.To16() if ip != nil { data.AddRtAttr(nl.IFLA_VXLAN_LOCAL6, []byte(ip)) } } } if vxlan.Group != nil { group := vxlan.Group.To4() if group != nil { data.AddRtAttr(nl.IFLA_VXLAN_GROUP, []byte(group)) } else { group = vxlan.Group.To16() if group != nil { data.AddRtAttr(nl.IFLA_VXLAN_GROUP6, []byte(group)) } } } data.AddRtAttr(nl.IFLA_VXLAN_TTL, nl.Uint8Attr(uint8(vxlan.TTL))) data.AddRtAttr(nl.IFLA_VXLAN_TOS, nl.Uint8Attr(uint8(vxlan.TOS))) data.AddRtAttr(nl.IFLA_VXLAN_LEARNING, boolAttr(vxlan.Learning)) data.AddRtAttr(nl.IFLA_VXLAN_PROXY, boolAttr(vxlan.Proxy)) data.AddRtAttr(nl.IFLA_VXLAN_RSC, boolAttr(vxlan.RSC)) data.AddRtAttr(nl.IFLA_VXLAN_L2MISS, boolAttr(vxlan.L2miss)) data.AddRtAttr(nl.IFLA_VXLAN_L3MISS, boolAttr(vxlan.L3miss)) data.AddRtAttr(nl.IFLA_VXLAN_UDP_ZERO_CSUM6_TX, boolAttr(vxlan.UDP6ZeroCSumTx)) data.AddRtAttr(nl.IFLA_VXLAN_UDP_ZERO_CSUM6_RX, boolAttr(vxlan.UDP6ZeroCSumRx)) if vxlan.UDPCSum { data.AddRtAttr(nl.IFLA_VXLAN_UDP_CSUM, boolAttr(vxlan.UDPCSum)) } if vxlan.GBP { data.AddRtAttr(nl.IFLA_VXLAN_GBP, []byte{}) } if vxlan.FlowBased { data.AddRtAttr(nl.IFLA_VXLAN_FLOWBASED, boolAttr(vxlan.FlowBased)) } if vxlan.NoAge { data.AddRtAttr(nl.IFLA_VXLAN_AGEING, nl.Uint32Attr(0)) } else if vxlan.Age > 0 { data.AddRtAttr(nl.IFLA_VXLAN_AGEING, nl.Uint32Attr(uint32(vxlan.Age))) } if vxlan.Limit > 0 { data.AddRtAttr(nl.IFLA_VXLAN_LIMIT, nl.Uint32Attr(uint32(vxlan.Limit))) } if vxlan.Port > 0 { data.AddRtAttr(nl.IFLA_VXLAN_PORT, htons(uint16(vxlan.Port))) } if vxlan.PortLow > 0 || vxlan.PortHigh > 0 { pr := vxlanPortRange{uint16(vxlan.PortLow), uint16(vxlan.PortHigh)} buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, &pr) data.AddRtAttr(nl.IFLA_VXLAN_PORT_RANGE, buf.Bytes()) } } func addBondAttrs(bond *Bond, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if bond.Mode >= 0 { data.AddRtAttr(nl.IFLA_BOND_MODE, nl.Uint8Attr(uint8(bond.Mode))) } if bond.ActiveSlave >= 0 { data.AddRtAttr(nl.IFLA_BOND_ACTIVE_SLAVE, nl.Uint32Attr(uint32(bond.ActiveSlave))) } if bond.Miimon >= 0 { data.AddRtAttr(nl.IFLA_BOND_MIIMON, nl.Uint32Attr(uint32(bond.Miimon))) } if bond.UpDelay >= 0 { data.AddRtAttr(nl.IFLA_BOND_UPDELAY, nl.Uint32Attr(uint32(bond.UpDelay))) } if bond.DownDelay >= 0 { data.AddRtAttr(nl.IFLA_BOND_DOWNDELAY, nl.Uint32Attr(uint32(bond.DownDelay))) } if bond.UseCarrier >= 0 { data.AddRtAttr(nl.IFLA_BOND_USE_CARRIER, nl.Uint8Attr(uint8(bond.UseCarrier))) } if bond.ArpInterval >= 0 { data.AddRtAttr(nl.IFLA_BOND_ARP_INTERVAL, nl.Uint32Attr(uint32(bond.ArpInterval))) } if bond.ArpIpTargets != nil { msg := data.AddRtAttr(nl.IFLA_BOND_ARP_IP_TARGET, nil) for i := range bond.ArpIpTargets { ip := bond.ArpIpTargets[i].To4() if ip != nil { msg.AddRtAttr(i, []byte(ip)) continue } ip = bond.ArpIpTargets[i].To16() if ip != nil { msg.AddRtAttr(i, []byte(ip)) } } } if bond.ArpValidate >= 0 { data.AddRtAttr(nl.IFLA_BOND_ARP_VALIDATE, nl.Uint32Attr(uint32(bond.ArpValidate))) } if bond.ArpAllTargets >= 0 { data.AddRtAttr(nl.IFLA_BOND_ARP_ALL_TARGETS, nl.Uint32Attr(uint32(bond.ArpAllTargets))) } if bond.Primary >= 0 { data.AddRtAttr(nl.IFLA_BOND_PRIMARY, nl.Uint32Attr(uint32(bond.Primary))) } if bond.PrimaryReselect >= 0 { data.AddRtAttr(nl.IFLA_BOND_PRIMARY_RESELECT, nl.Uint8Attr(uint8(bond.PrimaryReselect))) } if bond.FailOverMac >= 0 { data.AddRtAttr(nl.IFLA_BOND_FAIL_OVER_MAC, nl.Uint8Attr(uint8(bond.FailOverMac))) } if bond.XmitHashPolicy >= 0 { data.AddRtAttr(nl.IFLA_BOND_XMIT_HASH_POLICY, nl.Uint8Attr(uint8(bond.XmitHashPolicy))) } if bond.ResendIgmp >= 0 { data.AddRtAttr(nl.IFLA_BOND_RESEND_IGMP, nl.Uint32Attr(uint32(bond.ResendIgmp))) } if bond.NumPeerNotif >= 0 { data.AddRtAttr(nl.IFLA_BOND_NUM_PEER_NOTIF, nl.Uint8Attr(uint8(bond.NumPeerNotif))) } if bond.AllSlavesActive >= 0 { data.AddRtAttr(nl.IFLA_BOND_ALL_SLAVES_ACTIVE, nl.Uint8Attr(uint8(bond.AllSlavesActive))) } if bond.MinLinks >= 0 { data.AddRtAttr(nl.IFLA_BOND_MIN_LINKS, nl.Uint32Attr(uint32(bond.MinLinks))) } if bond.LpInterval >= 0 { data.AddRtAttr(nl.IFLA_BOND_LP_INTERVAL, nl.Uint32Attr(uint32(bond.LpInterval))) } if bond.PacketsPerSlave >= 0 { data.AddRtAttr(nl.IFLA_BOND_PACKETS_PER_SLAVE, nl.Uint32Attr(uint32(bond.PacketsPerSlave))) } if bond.LacpRate >= 0 { data.AddRtAttr(nl.IFLA_BOND_AD_LACP_RATE, nl.Uint8Attr(uint8(bond.LacpRate))) } if bond.AdSelect >= 0 { data.AddRtAttr(nl.IFLA_BOND_AD_SELECT, nl.Uint8Attr(uint8(bond.AdSelect))) } if bond.AdActorSysPrio >= 0 { data.AddRtAttr(nl.IFLA_BOND_AD_ACTOR_SYS_PRIO, nl.Uint16Attr(uint16(bond.AdActorSysPrio))) } if bond.AdUserPortKey >= 0 { data.AddRtAttr(nl.IFLA_BOND_AD_USER_PORT_KEY, nl.Uint16Attr(uint16(bond.AdUserPortKey))) } if bond.AdActorSystem != nil { data.AddRtAttr(nl.IFLA_BOND_AD_ACTOR_SYSTEM, []byte(bond.AdActorSystem)) } if bond.TlbDynamicLb >= 0 { data.AddRtAttr(nl.IFLA_BOND_TLB_DYNAMIC_LB, nl.Uint8Attr(uint8(bond.TlbDynamicLb))) } } func cleanupFds(fds []*os.File) { for _, f := range fds { f.Close() } } // LinkAdd adds a new link device. The type and features of the device // are taken from the parameters in the link object. // Equivalent to: `ip link add $link` func LinkAdd(link Link) error { return pkgHandle.LinkAdd(link) } // LinkAdd adds a new link device. The type and features of the device // are taken from the parameters in the link object. // Equivalent to: `ip link add $link` func (h *Handle) LinkAdd(link Link) error { return h.linkModify(link, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) } func LinkModify(link Link) error { return pkgHandle.LinkModify(link) } func (h *Handle) LinkModify(link Link) error { return h.linkModify(link, unix.NLM_F_REQUEST|unix.NLM_F_ACK) } func (h *Handle) linkModify(link Link, flags int) error { // TODO: support extra data for macvlan base := link.Attrs() // if tuntap, then the name can be empty, OS will provide a name tuntap, isTuntap := link.(*Tuntap) if base.Name == "" && !isTuntap { return fmt.Errorf("LinkAttrs.Name cannot be empty") } if isTuntap { if tuntap.Mode < unix.IFF_TUN || tuntap.Mode > unix.IFF_TAP { return fmt.Errorf("Tuntap.Mode %v unknown", tuntap.Mode) } queues := tuntap.Queues var fds []*os.File var req ifReq copy(req.Name[:15], base.Name) req.Flags = uint16(tuntap.Flags) if queues == 0 { //Legacy compatibility queues = 1 if tuntap.Flags == 0 { req.Flags = uint16(TUNTAP_DEFAULTS) } } else { // For best peformance set Flags to TUNTAP_MULTI_QUEUE_DEFAULTS | TUNTAP_VNET_HDR // when a) KVM has support for this ABI and // b) the value of the flag is queryable using the TUNGETIFF ioctl if tuntap.Flags == 0 { req.Flags = uint16(TUNTAP_MULTI_QUEUE_DEFAULTS) } } req.Flags |= uint16(tuntap.Mode) const TUN = "/dev/net/tun" for i := 0; i < queues; i++ { localReq := req fd, err := unix.Open(TUN, os.O_RDWR|syscall.O_CLOEXEC, 0) if err != nil { cleanupFds(fds) return err } _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&localReq))) if errno != 0 { // close the new fd unix.Close(fd) // and the already opened ones cleanupFds(fds) return fmt.Errorf("Tuntap IOCTL TUNSETIFF failed [%d], errno %v", i, errno) } _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETOWNER, uintptr(tuntap.Owner)) if errno != 0 { cleanupFds(fds) return fmt.Errorf("Tuntap IOCTL TUNSETOWNER failed [%d], errno %v", i, errno) } _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETGROUP, uintptr(tuntap.Group)) if errno != 0 { cleanupFds(fds) return fmt.Errorf("Tuntap IOCTL TUNSETGROUP failed [%d], errno %v", i, errno) } // Set the tun device to non-blocking before use. The below comment // taken from: // // https://github.com/mistsys/tuntap/commit/161418c25003bbee77d085a34af64d189df62bea // // Note there is a complication because in go, if a device node is // opened, go sets it to use nonblocking I/O. However a /dev/net/tun // doesn't work with epoll until after the TUNSETIFF ioctl has been // done. So we open the unix fd directly, do the ioctl, then put the // fd in nonblocking mode, an then finally wrap it in a os.File, // which will see the nonblocking mode and add the fd to the // pollable set, so later on when we Read() from it blocked the // calling thread in the kernel. // // See // https://github.com/golang/go/issues/30426 // which got exposed in go 1.13 by the fix to // https://github.com/golang/go/issues/30624 err = unix.SetNonblock(fd, true) if err != nil { cleanupFds(fds) return fmt.Errorf("Tuntap set to non-blocking failed [%d], err %v", i, err) } // create the file from the file descriptor and store it file := os.NewFile(uintptr(fd), TUN) fds = append(fds, file) // 1) we only care for the name of the first tap in the multi queue set // 2) if the original name was empty, the localReq has now the actual name // // In addition: // This ensures that the link name is always identical to what the kernel returns. // Not only in case of an empty name, but also when using name templates. // e.g. when the provided name is "tap%d", the kernel replaces %d with the next available number. if i == 0 { link.Attrs().Name = strings.Trim(string(localReq.Name[:]), "\x00") } } control := func(file *os.File, f func(fd uintptr)) error { name := file.Name() conn, err := file.SyscallConn() if err != nil { return fmt.Errorf("SyscallConn() failed on %s: %v", name, err) } if err := conn.Control(f); err != nil { return fmt.Errorf("Failed to get file descriptor for %s: %v", name, err) } return nil } // only persist interface if NonPersist is NOT set if !tuntap.NonPersist { var errno syscall.Errno if err := control(fds[0], func(fd uintptr) { _, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TUNSETPERSIST), 1) }); err != nil { return err } if errno != 0 { cleanupFds(fds) return fmt.Errorf("Tuntap IOCTL TUNSETPERSIST failed, errno %v", errno) } } h.ensureIndex(base) // can't set master during create, so set it afterwards if base.MasterIndex != 0 { // TODO: verify MasterIndex is actually a bridge? err := h.LinkSetMasterByIndex(link, base.MasterIndex) if err != nil { // un-persist (e.g. allow the interface to be removed) the tuntap // should not hurt if not set prior, condition might be not needed if !tuntap.NonPersist { // ignore error _ = control(fds[0], func(fd uintptr) { _, _, _ = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TUNSETPERSIST), 0) }) } cleanupFds(fds) return err } } if tuntap.Queues == 0 { cleanupFds(fds) } else { tuntap.Fds = fds } return nil } req := h.newNetlinkRequest(unix.RTM_NEWLINK, flags) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) // TODO: make it shorter if base.Flags&net.FlagUp != 0 { msg.Change = unix.IFF_UP msg.Flags = unix.IFF_UP } if base.Flags&net.FlagBroadcast != 0 { msg.Change |= unix.IFF_BROADCAST msg.Flags |= unix.IFF_BROADCAST } if base.Flags&net.FlagLoopback != 0 { msg.Change |= unix.IFF_LOOPBACK msg.Flags |= unix.IFF_LOOPBACK } if base.Flags&net.FlagPointToPoint != 0 { msg.Change |= unix.IFF_POINTOPOINT msg.Flags |= unix.IFF_POINTOPOINT } if base.Flags&net.FlagMulticast != 0 { msg.Change |= unix.IFF_MULTICAST msg.Flags |= unix.IFF_MULTICAST } if base.Index != 0 { msg.Index = int32(base.Index) } req.AddData(msg) if base.ParentIndex != 0 { b := make([]byte, 4) native.PutUint32(b, uint32(base.ParentIndex)) data := nl.NewRtAttr(unix.IFLA_LINK, b) req.AddData(data) } else if link.Type() == "ipvlan" || link.Type() == "ipoib" { return fmt.Errorf("Can't create %s link without ParentIndex", link.Type()) } nameData := nl.NewRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(base.Name)) req.AddData(nameData) if base.Alias != "" { alias := nl.NewRtAttr(unix.IFLA_IFALIAS, []byte(base.Alias)) req.AddData(alias) } if base.MTU > 0 { mtu := nl.NewRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(base.MTU))) req.AddData(mtu) } if base.TxQLen >= 0 { qlen := nl.NewRtAttr(unix.IFLA_TXQLEN, nl.Uint32Attr(uint32(base.TxQLen))) req.AddData(qlen) } if base.HardwareAddr != nil { hwaddr := nl.NewRtAttr(unix.IFLA_ADDRESS, []byte(base.HardwareAddr)) req.AddData(hwaddr) } if base.NumTxQueues > 0 { txqueues := nl.NewRtAttr(unix.IFLA_NUM_TX_QUEUES, nl.Uint32Attr(uint32(base.NumTxQueues))) req.AddData(txqueues) } if base.NumRxQueues > 0 { rxqueues := nl.NewRtAttr(unix.IFLA_NUM_RX_QUEUES, nl.Uint32Attr(uint32(base.NumRxQueues))) req.AddData(rxqueues) } if base.GSOMaxSegs > 0 { gsoAttr := nl.NewRtAttr(unix.IFLA_GSO_MAX_SEGS, nl.Uint32Attr(base.GSOMaxSegs)) req.AddData(gsoAttr) } if base.GSOMaxSize > 0 { gsoAttr := nl.NewRtAttr(unix.IFLA_GSO_MAX_SIZE, nl.Uint32Attr(base.GSOMaxSize)) req.AddData(gsoAttr) } if base.GROMaxSize > 0 { groAttr := nl.NewRtAttr(unix.IFLA_GRO_MAX_SIZE, nl.Uint32Attr(base.GROMaxSize)) req.AddData(groAttr) } if base.GSOIPv4MaxSize > 0 { gsoAttr := nl.NewRtAttr(unix.IFLA_GSO_IPV4_MAX_SIZE, nl.Uint32Attr(base.GSOIPv4MaxSize)) req.AddData(gsoAttr) } if base.GROIPv4MaxSize > 0 { groAttr := nl.NewRtAttr(unix.IFLA_GRO_IPV4_MAX_SIZE, nl.Uint32Attr(base.GROIPv4MaxSize)) req.AddData(groAttr) } if base.Group > 0 { groupAttr := nl.NewRtAttr(unix.IFLA_GROUP, nl.Uint32Attr(base.Group)) req.AddData(groupAttr) } if base.Namespace != nil { var attr *nl.RtAttr switch ns := base.Namespace.(type) { case NsPid: val := nl.Uint32Attr(uint32(ns)) attr = nl.NewRtAttr(unix.IFLA_NET_NS_PID, val) case NsFd: val := nl.Uint32Attr(uint32(ns)) attr = nl.NewRtAttr(unix.IFLA_NET_NS_FD, val) } req.AddData(attr) } if base.Xdp != nil { addXdpAttrs(base.Xdp, req) } linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) linkInfo.AddRtAttr(nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type())) switch link := link.(type) { case *Vlan: b := make([]byte, 2) native.PutUint16(b, uint16(link.VlanId)) data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_VLAN_ID, b) if link.VlanProtocol != VLAN_PROTOCOL_UNKNOWN { data.AddRtAttr(nl.IFLA_VLAN_PROTOCOL, htons(uint16(link.VlanProtocol))) } case *Netkit: if err := addNetkitAttrs(link, linkInfo, flags); err != nil { return err } case *Veth: data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) peer := data.AddRtAttr(nl.VETH_INFO_PEER, nil) nl.NewIfInfomsgChild(peer, unix.AF_UNSPEC) peer.AddRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(link.PeerName)) if base.TxQLen >= 0 { peer.AddRtAttr(unix.IFLA_TXQLEN, nl.Uint32Attr(uint32(base.TxQLen))) } if base.NumTxQueues > 0 { peer.AddRtAttr(unix.IFLA_NUM_TX_QUEUES, nl.Uint32Attr(uint32(base.NumTxQueues))) } if base.NumRxQueues > 0 { peer.AddRtAttr(unix.IFLA_NUM_RX_QUEUES, nl.Uint32Attr(uint32(base.NumRxQueues))) } if base.MTU > 0 { peer.AddRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(base.MTU))) } if link.PeerHardwareAddr != nil { peer.AddRtAttr(unix.IFLA_ADDRESS, []byte(link.PeerHardwareAddr)) } if link.PeerNamespace != nil { switch ns := link.PeerNamespace.(type) { case NsPid: val := nl.Uint32Attr(uint32(ns)) peer.AddRtAttr(unix.IFLA_NET_NS_PID, val) case NsFd: val := nl.Uint32Attr(uint32(ns)) peer.AddRtAttr(unix.IFLA_NET_NS_FD, val) } } case *Vxlan: addVxlanAttrs(link, linkInfo) case *Bond: addBondAttrs(link, linkInfo) case *IPVlan: data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_IPVLAN_MODE, nl.Uint16Attr(uint16(link.Mode))) data.AddRtAttr(nl.IFLA_IPVLAN_FLAG, nl.Uint16Attr(uint16(link.Flag))) case *IPVtap: data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_IPVLAN_MODE, nl.Uint16Attr(uint16(link.Mode))) data.AddRtAttr(nl.IFLA_IPVLAN_FLAG, nl.Uint16Attr(uint16(link.Flag))) case *Macvlan: addMacvlanAttrs(link, linkInfo) case *Macvtap: addMacvtapAttrs(link, linkInfo) case *Geneve: addGeneveAttrs(link, linkInfo) case *Gretap: addGretapAttrs(link, linkInfo) case *Iptun: addIptunAttrs(link, linkInfo) case *Ip6tnl: addIp6tnlAttrs(link, linkInfo) case *Sittun: addSittunAttrs(link, linkInfo) case *Gretun: addGretunAttrs(link, linkInfo) case *Vti: addVtiAttrs(link, linkInfo) case *Vrf: addVrfAttrs(link, linkInfo) case *Bridge: addBridgeAttrs(link, linkInfo) case *GTP: addGTPAttrs(link, linkInfo) case *Xfrmi: addXfrmiAttrs(link, linkInfo) case *IPoIB: addIPoIBAttrs(link, linkInfo) case *BareUDP: addBareUDPAttrs(link, linkInfo) } req.AddData(linkInfo) _, err := req.Execute(unix.NETLINK_ROUTE, 0) if err != nil { return err } h.ensureIndex(base) // can't set master during create, so set it afterwards if base.MasterIndex != 0 { // TODO: verify MasterIndex is actually a bridge? return h.LinkSetMasterByIndex(link, base.MasterIndex) } return nil } // LinkDel deletes link device. Either Index or Name must be set in // the link object for it to be deleted. The other values are ignored. // Equivalent to: `ip link del $link` func LinkDel(link Link) error { return pkgHandle.LinkDel(link) } // LinkDel deletes link device. Either Index or Name must be set in // the link object for it to be deleted. The other values are ignored. // Equivalent to: `ip link del $link` func (h *Handle) LinkDel(link Link) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_DELLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func (h *Handle) linkByNameDump(name string) (Link, error) { links, err := h.LinkList() if err != nil { return nil, err } for _, link := range links { if link.Attrs().Name == name { return link, nil } // support finding interfaces also via altnames for _, altName := range link.Attrs().AltNames { if altName == name { return link, nil } } } return nil, LinkNotFoundError{fmt.Errorf("Link %s not found", name)} } func (h *Handle) linkByAliasDump(alias string) (Link, error) { links, err := h.LinkList() if err != nil { return nil, err } for _, link := range links { if link.Attrs().Alias == alias { return link, nil } } return nil, LinkNotFoundError{fmt.Errorf("Link alias %s not found", alias)} } // LinkByName finds a link by name and returns a pointer to the object. func LinkByName(name string) (Link, error) { return pkgHandle.LinkByName(name) } // LinkByName finds a link by name and returns a pointer to the object. func (h *Handle) LinkByName(name string) (Link, error) { if h.lookupByDump { return h.linkByNameDump(name) } req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(msg) attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) req.AddData(attr) nameData := nl.NewRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(name)) if len(name) > 15 { nameData = nl.NewRtAttr(unix.IFLA_ALT_IFNAME, nl.ZeroTerminated(name)) } req.AddData(nameData) link, err := execGetLink(req) if err == unix.EINVAL { // older kernels don't support looking up via IFLA_IFNAME // so fall back to dumping all links h.lookupByDump = true return h.linkByNameDump(name) } return link, err } // LinkByAlias finds a link by its alias and returns a pointer to the object. // If there are multiple links with the alias it returns the first one func LinkByAlias(alias string) (Link, error) { return pkgHandle.LinkByAlias(alias) } // LinkByAlias finds a link by its alias and returns a pointer to the object. // If there are multiple links with the alias it returns the first one func (h *Handle) LinkByAlias(alias string) (Link, error) { if h.lookupByDump { return h.linkByAliasDump(alias) } req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(msg) attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) req.AddData(attr) nameData := nl.NewRtAttr(unix.IFLA_IFALIAS, nl.ZeroTerminated(alias)) req.AddData(nameData) link, err := execGetLink(req) if err == unix.EINVAL { // older kernels don't support looking up via IFLA_IFALIAS // so fall back to dumping all links h.lookupByDump = true return h.linkByAliasDump(alias) } return link, err } // LinkByIndex finds a link by index and returns a pointer to the object. func LinkByIndex(index int) (Link, error) { return pkgHandle.LinkByIndex(index) } // LinkByIndex finds a link by index and returns a pointer to the object. func (h *Handle) LinkByIndex(index int) (Link, error) { req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(index) req.AddData(msg) attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) req.AddData(attr) return execGetLink(req) } func execGetLink(req *nl.NetlinkRequest) (Link, error) { msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) if err != nil { if errno, ok := err.(syscall.Errno); ok { if errno == unix.ENODEV { return nil, LinkNotFoundError{fmt.Errorf("Link not found")} } } return nil, err } switch { case len(msgs) == 0: return nil, LinkNotFoundError{fmt.Errorf("Link not found")} case len(msgs) == 1: return LinkDeserialize(nil, msgs[0]) default: return nil, fmt.Errorf("More than one link found") } } // LinkDeserialize deserializes a raw message received from netlink into // a link object. func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { msg := nl.DeserializeIfInfomsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } base := NewLinkAttrs() base.Index = int(msg.Index) base.RawFlags = msg.Flags base.Flags = linkFlags(msg.Flags) base.EncapType = msg.EncapType() base.NetNsID = -1 if msg.Flags&unix.IFF_ALLMULTI != 0 { base.Allmulti = 1 } if msg.Flags&unix.IFF_MULTICAST != 0 { base.Multi = 1 } var ( link Link stats32 *LinkStatistics32 stats64 *LinkStatistics64 linkType string linkSlave LinkSlave slaveType string ) for _, attr := range attrs { switch attr.Attr.Type { case unix.IFLA_LINKINFO: infos, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } for _, info := range infos { switch info.Attr.Type { case nl.IFLA_INFO_KIND: linkType = string(info.Value[:len(info.Value)-1]) switch linkType { case "dummy": link = &Dummy{} case "ifb": link = &Ifb{} case "bridge": link = &Bridge{} case "vlan": link = &Vlan{} case "netkit": link = &Netkit{} case "veth": link = &Veth{} case "wireguard": link = &Wireguard{} case "vxlan": link = &Vxlan{} case "bond": link = &Bond{} case "ipvlan": link = &IPVlan{} case "ipvtap": link = &IPVtap{} case "macvlan": link = &Macvlan{} case "macvtap": link = &Macvtap{} case "geneve": link = &Geneve{} case "gretap": link = &Gretap{} case "ip6gretap": link = &Gretap{} case "ipip": link = &Iptun{} case "ip6tnl": link = &Ip6tnl{} case "sit": link = &Sittun{} case "gre": link = &Gretun{} case "ip6gre": link = &Gretun{} case "vti", "vti6": link = &Vti{} case "vrf": link = &Vrf{} case "gtp": link = >P{} case "xfrm": link = &Xfrmi{} case "tun": link = &Tuntap{} case "ipoib": link = &IPoIB{} case "can": link = &Can{} case "bareudp": link = &BareUDP{} default: link = &GenericLink{LinkType: linkType} } case nl.IFLA_INFO_DATA: data, err := nl.ParseRouteAttr(info.Value) if err != nil { return nil, err } switch linkType { case "netkit": parseNetkitData(link, data) case "vlan": parseVlanData(link, data) case "vxlan": parseVxlanData(link, data) case "bond": parseBondData(link, data) case "ipvlan": parseIPVlanData(link, data) case "ipvtap": parseIPVtapData(link, data) case "macvlan": parseMacvlanData(link, data) case "macvtap": parseMacvtapData(link, data) case "geneve": parseGeneveData(link, data) case "gretap": parseGretapData(link, data) case "ip6gretap": parseGretapData(link, data) case "ipip": parseIptunData(link, data) case "ip6tnl": parseIp6tnlData(link, data) case "sit": parseSittunData(link, data) case "gre": parseGretunData(link, data) case "ip6gre": parseGretunData(link, data) case "vti", "vti6": parseVtiData(link, data) case "vrf": parseVrfData(link, data) case "bridge": parseBridgeData(link, data) case "gtp": parseGTPData(link, data) case "xfrm": parseXfrmiData(link, data) case "tun": parseTuntapData(link, data) case "ipoib": parseIPoIBData(link, data) case "can": parseCanData(link, data) case "bareudp": parseBareUDPData(link, data) } case nl.IFLA_INFO_SLAVE_KIND: slaveType = string(info.Value[:len(info.Value)-1]) switch slaveType { case "bond": linkSlave = &BondSlave{} case "vrf": linkSlave = &VrfSlave{} } case nl.IFLA_INFO_SLAVE_DATA: switch slaveType { case "bond": data, err := nl.ParseRouteAttr(info.Value) if err != nil { return nil, err } parseBondSlaveData(linkSlave, data) case "vrf": data, err := nl.ParseRouteAttr(info.Value) if err != nil { return nil, err } parseVrfSlaveData(linkSlave, data) } } } case unix.IFLA_ADDRESS: var nonzero bool for _, b := range attr.Value { if b != 0 { nonzero = true } } if nonzero { base.HardwareAddr = attr.Value[:] } case unix.IFLA_IFNAME: base.Name = string(attr.Value[:len(attr.Value)-1]) case unix.IFLA_MTU: base.MTU = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_PROMISCUITY: base.Promisc = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_LINK: base.ParentIndex = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_MASTER: base.MasterIndex = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_TXQLEN: base.TxQLen = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_IFALIAS: base.Alias = string(attr.Value[:len(attr.Value)-1]) case unix.IFLA_STATS: stats32 = new(LinkStatistics32) if err := binary.Read(bytes.NewBuffer(attr.Value[:]), nl.NativeEndian(), stats32); err != nil { return nil, err } case unix.IFLA_STATS64: stats64 = new(LinkStatistics64) if err := binary.Read(bytes.NewBuffer(attr.Value[:]), nl.NativeEndian(), stats64); err != nil { return nil, err } case unix.IFLA_XDP: xdp, err := parseLinkXdp(attr.Value[:]) if err != nil { return nil, err } base.Xdp = xdp case unix.IFLA_PROTINFO | unix.NLA_F_NESTED: if hdr != nil && hdr.Type == unix.RTM_NEWLINK && msg.Family == unix.AF_BRIDGE { attrs, err := nl.ParseRouteAttr(attr.Value[:]) if err != nil { return nil, err } protinfo := parseProtinfo(attrs) base.Protinfo = &protinfo } case unix.IFLA_PROP_LIST | unix.NLA_F_NESTED: attrs, err := nl.ParseRouteAttr(attr.Value[:]) if err != nil { return nil, err } base.AltNames = []string{} for _, attr := range attrs { if attr.Attr.Type == unix.IFLA_ALT_IFNAME { base.AltNames = append(base.AltNames, nl.BytesToString(attr.Value)) } } case unix.IFLA_OPERSTATE: base.OperState = LinkOperState(uint8(attr.Value[0])) case unix.IFLA_PHYS_SWITCH_ID: base.PhysSwitchID = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_LINK_NETNSID: base.NetNsID = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_TSO_MAX_SEGS: base.TSOMaxSegs = native.Uint32(attr.Value[0:4]) case unix.IFLA_TSO_MAX_SIZE: base.TSOMaxSize = native.Uint32(attr.Value[0:4]) case unix.IFLA_GSO_MAX_SEGS: base.GSOMaxSegs = native.Uint32(attr.Value[0:4]) case unix.IFLA_GSO_MAX_SIZE: base.GSOMaxSize = native.Uint32(attr.Value[0:4]) case unix.IFLA_GRO_MAX_SIZE: base.GROMaxSize = native.Uint32(attr.Value[0:4]) case unix.IFLA_GSO_IPV4_MAX_SIZE: base.GSOIPv4MaxSize = native.Uint32(attr.Value[0:4]) case unix.IFLA_GRO_IPV4_MAX_SIZE: base.GROIPv4MaxSize = native.Uint32(attr.Value[0:4]) case unix.IFLA_VFINFO_LIST: data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } vfs, err := parseVfInfoList(data) if err != nil { return nil, err } base.Vfs = vfs case unix.IFLA_NUM_TX_QUEUES: base.NumTxQueues = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_NUM_RX_QUEUES: base.NumRxQueues = int(native.Uint32(attr.Value[0:4])) case unix.IFLA_GROUP: base.Group = native.Uint32(attr.Value[0:4]) case unix.IFLA_PERM_ADDRESS: for _, b := range attr.Value { if b != 0 { base.PermHWAddr = attr.Value[:] break } } } } if stats64 != nil { base.Statistics = (*LinkStatistics)(stats64) } else if stats32 != nil { base.Statistics = (*LinkStatistics)(stats32.to64()) } // Links that don't have IFLA_INFO_KIND are hardware devices if link == nil { link = &Device{} } *link.Attrs() = base link.Attrs().Slave = linkSlave // If the tuntap attributes are not updated by netlink due to // an older driver, use sysfs if link != nil && linkType == "tun" { tuntap := link.(*Tuntap) if tuntap.Mode == 0 { ifname := tuntap.Attrs().Name if flags, err := readSysPropAsInt64(ifname, "tun_flags"); err == nil { if flags&unix.IFF_TUN != 0 { tuntap.Mode = unix.IFF_TUN } else if flags&unix.IFF_TAP != 0 { tuntap.Mode = unix.IFF_TAP } tuntap.NonPersist = false if flags&unix.IFF_PERSIST == 0 { tuntap.NonPersist = true } } // The sysfs interface for owner/group returns -1 for root user, instead of returning 0. // So explicitly check for negative value, before assigning the owner uid/gid. if owner, err := readSysPropAsInt64(ifname, "owner"); err == nil && owner > 0 { tuntap.Owner = uint32(owner) } if group, err := readSysPropAsInt64(ifname, "group"); err == nil && group > 0 { tuntap.Group = uint32(group) } } } return link, nil } func readSysPropAsInt64(ifname, prop string) (int64, error) { fname := fmt.Sprintf("/sys/class/net/%s/%s", ifname, prop) contents, err := ioutil.ReadFile(fname) if err != nil { return 0, err } num, err := strconv.ParseInt(strings.TrimSpace(string(contents)), 0, 64) if err == nil { return num, nil } return 0, err } // LinkList gets a list of link devices. // Equivalent to: `ip link show` func LinkList() ([]Link, error) { return pkgHandle.LinkList() } // LinkList gets a list of link devices. // Equivalent to: `ip link show` func (h *Handle) LinkList() ([]Link, error) { // NOTE(vish): This duplicates functionality in net/iface_linux.go, but we need // to get the message ourselves to parse link type. req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(msg) attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) req.AddData(attr) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) if err != nil { return nil, err } var res []Link for _, m := range msgs { link, err := LinkDeserialize(nil, m) if err != nil { return nil, err } res = append(res, link) } return res, nil } // LinkUpdate is used to pass information back from LinkSubscribe() type LinkUpdate struct { nl.IfInfomsg Header unix.NlMsghdr Link } // LinkSubscribe takes a chan down which notifications will be sent // when links change. Close the 'done' chan to stop subscription. func LinkSubscribe(ch chan<- LinkUpdate, done <-chan struct{}) error { return linkSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false) } // LinkSubscribeAt works like LinkSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func LinkSubscribeAt(ns netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}) error { return linkSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false) } // LinkSubscribeOptions contains a set of options to use with // LinkSubscribeWithOptions. type LinkSubscribeOptions struct { Namespace *netns.NsHandle ErrorCallback func(error) ListExisting bool ReceiveBufferSize int ReceiveBufferForceSize bool ReceiveTimeout *unix.Timeval } // LinkSubscribeWithOptions work like LinkSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error { if options.Namespace == nil { none := netns.None() options.Namespace = &none } return linkSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize) } func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce bool) error { s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_LINK) if err != nil { return err } if rcvTimeout != nil { if err := s.SetReceiveTimeout(rcvTimeout); err != nil { return err } } if rcvbuf != 0 { err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce) if err != nil { return err } } if done != nil { go func() { <-done s.Close() }() } if listExisting { req := pkgHandle.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(msg) if err := s.Send(req); err != nil { return err } } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { if cberr != nil { cberr(fmt.Errorf("Receive failed: %v", err)) } return } if from.Pid != nl.PidKernel { if cberr != nil { cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)) } continue } for _, m := range msgs { if m.Header.Type == unix.NLMSG_DONE { continue } if m.Header.Type == unix.NLMSG_ERROR { error := int32(native.Uint32(m.Data[0:4])) if error == 0 { continue } if cberr != nil { cberr(fmt.Errorf("error message: %v", syscall.Errno(-error))) } continue } ifmsg := nl.DeserializeIfInfomsg(m.Data) header := unix.NlMsghdr(m.Header) link, err := LinkDeserialize(&header, m.Data) if err != nil { if cberr != nil { cberr(err) } continue } ch <- LinkUpdate{IfInfomsg: *ifmsg, Header: header, Link: link} } } }() return nil } func LinkSetHairpin(link Link, mode bool) error { return pkgHandle.LinkSetHairpin(link, mode) } func (h *Handle) LinkSetHairpin(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_MODE) } func LinkSetGuard(link Link, mode bool) error { return pkgHandle.LinkSetGuard(link, mode) } func (h *Handle) LinkSetGuard(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_GUARD) } // LinkSetBRSlaveGroupFwdMask set the group_fwd_mask of a bridge slave interface func LinkSetBRSlaveGroupFwdMask(link Link, mask uint16) error { return pkgHandle.LinkSetBRSlaveGroupFwdMask(link, mask) } // LinkSetBRSlaveGroupFwdMask set the group_fwd_mask of a bridge slave interface func (h *Handle) LinkSetBRSlaveGroupFwdMask(link Link, mask uint16) error { return h.setProtinfoAttrRawVal(link, nl.Uint16Attr(mask), nl.IFLA_BRPORT_GROUP_FWD_MASK) } func LinkSetFastLeave(link Link, mode bool) error { return pkgHandle.LinkSetFastLeave(link, mode) } func (h *Handle) LinkSetFastLeave(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_FAST_LEAVE) } func LinkSetLearning(link Link, mode bool) error { return pkgHandle.LinkSetLearning(link, mode) } func (h *Handle) LinkSetLearning(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_LEARNING) } func LinkSetRootBlock(link Link, mode bool) error { return pkgHandle.LinkSetRootBlock(link, mode) } func (h *Handle) LinkSetRootBlock(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_PROTECT) } func LinkSetFlood(link Link, mode bool) error { return pkgHandle.LinkSetFlood(link, mode) } func (h *Handle) LinkSetFlood(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_UNICAST_FLOOD) } func LinkSetIsolated(link Link, mode bool) error { return pkgHandle.LinkSetIsolated(link, mode) } func (h *Handle) LinkSetIsolated(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_ISOLATED) } func LinkSetBrProxyArp(link Link, mode bool) error { return pkgHandle.LinkSetBrProxyArp(link, mode) } func (h *Handle) LinkSetBrProxyArp(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_PROXYARP) } func LinkSetBrProxyArpWiFi(link Link, mode bool) error { return pkgHandle.LinkSetBrProxyArpWiFi(link, mode) } func (h *Handle) LinkSetBrProxyArpWiFi(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_PROXYARP_WIFI) } func LinkSetBrNeighSuppress(link Link, mode bool) error { return pkgHandle.LinkSetBrNeighSuppress(link, mode) } func (h *Handle) LinkSetBrNeighSuppress(link Link, mode bool) error { return h.setProtinfoAttr(link, mode, nl.IFLA_BRPORT_NEIGH_SUPPRESS) } func (h *Handle) setProtinfoAttrRawVal(link Link, val []byte, attr int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) msg.Index = int32(base.Index) req.AddData(msg) br := nl.NewRtAttr(unix.IFLA_PROTINFO|unix.NLA_F_NESTED, nil) br.AddRtAttr(attr, val) req.AddData(br) _, err := req.Execute(unix.NETLINK_ROUTE, 0) if err != nil { return err } return nil } func (h *Handle) setProtinfoAttr(link Link, mode bool, attr int) error { return h.setProtinfoAttrRawVal(link, boolToByte(mode), attr) } // LinkSetTxQLen sets the transaction queue length for the link. // Equivalent to: `ip link set $link txqlen $qlen` func LinkSetTxQLen(link Link, qlen int) error { return pkgHandle.LinkSetTxQLen(link, qlen) } // LinkSetTxQLen sets the transaction queue length for the link. // Equivalent to: `ip link set $link txqlen $qlen` func (h *Handle) LinkSetTxQLen(link Link, qlen int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(qlen)) data := nl.NewRtAttr(unix.IFLA_TXQLEN, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetGroup sets the link group id which can be used to perform mass actions // with iproute2 as well use it as a reference in nft filters. // Equivalent to: `ip link set $link group $id` func LinkSetGroup(link Link, group int) error { return pkgHandle.LinkSetGroup(link, group) } // LinkSetGroup sets the link group id which can be used to perform mass actions // with iproute2 as well use it as a reference in nft filters. // Equivalent to: `ip link set $link group $id` func (h *Handle) LinkSetGroup(link Link, group int) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) b := make([]byte, 4) native.PutUint32(b, uint32(group)) data := nl.NewRtAttr(unix.IFLA_GROUP, b) req.AddData(data) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func addNetkitAttrs(nk *Netkit, linkInfo *nl.RtAttr, flag int) error { if nk.peerLinkAttrs.HardwareAddr != nil || nk.HardwareAddr != nil { return fmt.Errorf("netkit doesn't support setting Ethernet") } data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) // Kernel will return error if trying to change the mode of an existing netkit device data.AddRtAttr(nl.IFLA_NETKIT_MODE, nl.Uint32Attr(uint32(nk.Mode))) data.AddRtAttr(nl.IFLA_NETKIT_POLICY, nl.Uint32Attr(uint32(nk.Policy))) data.AddRtAttr(nl.IFLA_NETKIT_PEER_POLICY, nl.Uint32Attr(uint32(nk.PeerPolicy))) if (flag & unix.NLM_F_EXCL) == 0 { // Modifying peer link attributes will not take effect return nil } peer := data.AddRtAttr(nl.IFLA_NETKIT_PEER_INFO, nil) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) if nk.peerLinkAttrs.Flags&net.FlagUp != 0 { msg.Change = unix.IFF_UP msg.Flags = unix.IFF_UP } if nk.peerLinkAttrs.Index != 0 { msg.Index = int32(nk.peerLinkAttrs.Index) } peer.AddChild(msg) if nk.peerLinkAttrs.Name != "" { peer.AddRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(nk.peerLinkAttrs.Name)) } if nk.peerLinkAttrs.MTU > 0 { peer.AddRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(nk.peerLinkAttrs.MTU))) } if nk.peerLinkAttrs.GSOMaxSegs > 0 { peer.AddRtAttr(unix.IFLA_GSO_MAX_SEGS, nl.Uint32Attr(nk.peerLinkAttrs.GSOMaxSegs)) } if nk.peerLinkAttrs.GSOMaxSize > 0 { peer.AddRtAttr(unix.IFLA_GSO_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GSOMaxSize)) } if nk.peerLinkAttrs.GSOIPv4MaxSize > 0 { peer.AddRtAttr(unix.IFLA_GSO_IPV4_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GSOIPv4MaxSize)) } if nk.peerLinkAttrs.GROIPv4MaxSize > 0 { peer.AddRtAttr(unix.IFLA_GRO_IPV4_MAX_SIZE, nl.Uint32Attr(nk.peerLinkAttrs.GROIPv4MaxSize)) } if nk.peerLinkAttrs.Namespace != nil { switch ns := nk.peerLinkAttrs.Namespace.(type) { case NsPid: peer.AddRtAttr(unix.IFLA_NET_NS_PID, nl.Uint32Attr(uint32(ns))) case NsFd: peer.AddRtAttr(unix.IFLA_NET_NS_FD, nl.Uint32Attr(uint32(ns))) } } return nil } func parseNetkitData(link Link, data []syscall.NetlinkRouteAttr) { netkit := link.(*Netkit) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_NETKIT_PRIMARY: isPrimary := datum.Value[0:1][0] if isPrimary != 0 { netkit.isPrimary = true } case nl.IFLA_NETKIT_MODE: netkit.Mode = NetkitMode(native.Uint32(datum.Value[0:4])) case nl.IFLA_NETKIT_POLICY: netkit.Policy = NetkitPolicy(native.Uint32(datum.Value[0:4])) case nl.IFLA_NETKIT_PEER_POLICY: netkit.PeerPolicy = NetkitPolicy(native.Uint32(datum.Value[0:4])) } } } func parseVlanData(link Link, data []syscall.NetlinkRouteAttr) { vlan := link.(*Vlan) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_VLAN_ID: vlan.VlanId = int(native.Uint16(datum.Value[0:2])) case nl.IFLA_VLAN_PROTOCOL: vlan.VlanProtocol = VlanProtocol(int(ntohs(datum.Value[0:2]))) } } } func parseVxlanData(link Link, data []syscall.NetlinkRouteAttr) { vxlan := link.(*Vxlan) for _, datum := range data { // NOTE(vish): Apparently some messages can be sent with no value. // We special case GBP here to not change existing // functionality. It appears that GBP sends a datum.Value // of null. if len(datum.Value) == 0 && datum.Attr.Type != nl.IFLA_VXLAN_GBP { continue } switch datum.Attr.Type { case nl.IFLA_VXLAN_ID: vxlan.VxlanId = int(native.Uint32(datum.Value[0:4])) case nl.IFLA_VXLAN_LINK: vxlan.VtepDevIndex = int(native.Uint32(datum.Value[0:4])) case nl.IFLA_VXLAN_LOCAL: vxlan.SrcAddr = net.IP(datum.Value[0:4]) case nl.IFLA_VXLAN_LOCAL6: vxlan.SrcAddr = net.IP(datum.Value[0:16]) case nl.IFLA_VXLAN_GROUP: vxlan.Group = net.IP(datum.Value[0:4]) case nl.IFLA_VXLAN_GROUP6: vxlan.Group = net.IP(datum.Value[0:16]) case nl.IFLA_VXLAN_TTL: vxlan.TTL = int(datum.Value[0]) case nl.IFLA_VXLAN_TOS: vxlan.TOS = int(datum.Value[0]) case nl.IFLA_VXLAN_LEARNING: vxlan.Learning = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_PROXY: vxlan.Proxy = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_RSC: vxlan.RSC = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_L2MISS: vxlan.L2miss = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_L3MISS: vxlan.L3miss = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_UDP_CSUM: vxlan.UDPCSum = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_UDP_ZERO_CSUM6_TX: vxlan.UDP6ZeroCSumTx = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_UDP_ZERO_CSUM6_RX: vxlan.UDP6ZeroCSumRx = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_GBP: vxlan.GBP = true case nl.IFLA_VXLAN_FLOWBASED: vxlan.FlowBased = int8(datum.Value[0]) != 0 case nl.IFLA_VXLAN_AGEING: vxlan.Age = int(native.Uint32(datum.Value[0:4])) vxlan.NoAge = vxlan.Age == 0 case nl.IFLA_VXLAN_LIMIT: vxlan.Limit = int(native.Uint32(datum.Value[0:4])) case nl.IFLA_VXLAN_PORT: vxlan.Port = int(ntohs(datum.Value[0:2])) case nl.IFLA_VXLAN_PORT_RANGE: buf := bytes.NewBuffer(datum.Value[0:4]) var pr vxlanPortRange if binary.Read(buf, binary.BigEndian, &pr) != nil { vxlan.PortLow = int(pr.Lo) vxlan.PortHigh = int(pr.Hi) } } } } func parseBondData(link Link, data []syscall.NetlinkRouteAttr) { bond := link.(*Bond) for i := range data { switch data[i].Attr.Type { case nl.IFLA_BOND_MODE: bond.Mode = BondMode(data[i].Value[0]) case nl.IFLA_BOND_ACTIVE_SLAVE: bond.ActiveSlave = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_MIIMON: bond.Miimon = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_UPDELAY: bond.UpDelay = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_DOWNDELAY: bond.DownDelay = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_USE_CARRIER: bond.UseCarrier = int(data[i].Value[0]) case nl.IFLA_BOND_ARP_INTERVAL: bond.ArpInterval = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_ARP_IP_TARGET: bond.ArpIpTargets = parseBondArpIpTargets(data[i].Value) case nl.IFLA_BOND_ARP_VALIDATE: bond.ArpValidate = BondArpValidate(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_ARP_ALL_TARGETS: bond.ArpAllTargets = BondArpAllTargets(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_PRIMARY: bond.Primary = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_PRIMARY_RESELECT: bond.PrimaryReselect = BondPrimaryReselect(data[i].Value[0]) case nl.IFLA_BOND_FAIL_OVER_MAC: bond.FailOverMac = BondFailOverMac(data[i].Value[0]) case nl.IFLA_BOND_XMIT_HASH_POLICY: bond.XmitHashPolicy = BondXmitHashPolicy(data[i].Value[0]) case nl.IFLA_BOND_RESEND_IGMP: bond.ResendIgmp = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_NUM_PEER_NOTIF: bond.NumPeerNotif = int(data[i].Value[0]) case nl.IFLA_BOND_ALL_SLAVES_ACTIVE: bond.AllSlavesActive = int(data[i].Value[0]) case nl.IFLA_BOND_MIN_LINKS: bond.MinLinks = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_LP_INTERVAL: bond.LpInterval = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_PACKETS_PER_SLAVE: bond.PacketsPerSlave = int(native.Uint32(data[i].Value[0:4])) case nl.IFLA_BOND_AD_LACP_RATE: bond.LacpRate = BondLacpRate(data[i].Value[0]) case nl.IFLA_BOND_AD_SELECT: bond.AdSelect = BondAdSelect(data[i].Value[0]) case nl.IFLA_BOND_AD_INFO: // TODO: implement case nl.IFLA_BOND_AD_ACTOR_SYS_PRIO: bond.AdActorSysPrio = int(native.Uint16(data[i].Value[0:2])) case nl.IFLA_BOND_AD_USER_PORT_KEY: bond.AdUserPortKey = int(native.Uint16(data[i].Value[0:2])) case nl.IFLA_BOND_AD_ACTOR_SYSTEM: bond.AdActorSystem = net.HardwareAddr(data[i].Value[0:6]) case nl.IFLA_BOND_TLB_DYNAMIC_LB: bond.TlbDynamicLb = int(data[i].Value[0]) } } } func parseBondArpIpTargets(value []byte) []net.IP { data, err := nl.ParseRouteAttr(value) if err != nil { return nil } targets := []net.IP{} for i := range data { target := net.IP(data[i].Value) if ip := target.To4(); ip != nil { targets = append(targets, ip) continue } if ip := target.To16(); ip != nil { targets = append(targets, ip) } } return targets } func addBondSlaveAttrs(bondSlave *BondSlave, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_SLAVE_DATA, nil) data.AddRtAttr(nl.IFLA_BOND_SLAVE_STATE, nl.Uint8Attr(uint8(bondSlave.State))) data.AddRtAttr(nl.IFLA_BOND_SLAVE_MII_STATUS, nl.Uint8Attr(uint8(bondSlave.MiiStatus))) data.AddRtAttr(nl.IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, nl.Uint32Attr(bondSlave.LinkFailureCount)) data.AddRtAttr(nl.IFLA_BOND_SLAVE_QUEUE_ID, nl.Uint16Attr(bondSlave.QueueId)) data.AddRtAttr(nl.IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, nl.Uint16Attr(bondSlave.AggregatorId)) data.AddRtAttr(nl.IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, nl.Uint8Attr(bondSlave.AdActorOperPortState)) data.AddRtAttr(nl.IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, nl.Uint16Attr(bondSlave.AdPartnerOperPortState)) if mac := bondSlave.PermHardwareAddr; mac != nil { data.AddRtAttr(nl.IFLA_BOND_SLAVE_PERM_HWADDR, []byte(mac)) } } func parseBondSlaveData(slave LinkSlave, data []syscall.NetlinkRouteAttr) { bondSlave := slave.(*BondSlave) for i := range data { switch data[i].Attr.Type { case nl.IFLA_BOND_SLAVE_STATE: bondSlave.State = BondSlaveState(data[i].Value[0]) case nl.IFLA_BOND_SLAVE_MII_STATUS: bondSlave.MiiStatus = BondSlaveMiiStatus(data[i].Value[0]) case nl.IFLA_BOND_SLAVE_LINK_FAILURE_COUNT: bondSlave.LinkFailureCount = native.Uint32(data[i].Value[0:4]) case nl.IFLA_BOND_SLAVE_PERM_HWADDR: bondSlave.PermHardwareAddr = net.HardwareAddr(data[i].Value[0:6]) case nl.IFLA_BOND_SLAVE_QUEUE_ID: bondSlave.QueueId = native.Uint16(data[i].Value[0:2]) case nl.IFLA_BOND_SLAVE_AD_AGGREGATOR_ID: bondSlave.AggregatorId = native.Uint16(data[i].Value[0:2]) case nl.IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE: bondSlave.AdActorOperPortState = uint8(data[i].Value[0]) case nl.IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE: bondSlave.AdPartnerOperPortState = native.Uint16(data[i].Value[0:2]) } } } func parseVrfSlaveData(slave LinkSlave, data []syscall.NetlinkRouteAttr) { vrfSlave := slave.(*VrfSlave) for i := range data { switch data[i].Attr.Type { case nl.IFLA_BOND_SLAVE_STATE: vrfSlave.Table = native.Uint32(data[i].Value[0:4]) } } } func parseIPVlanData(link Link, data []syscall.NetlinkRouteAttr) { ipv := link.(*IPVlan) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPVLAN_MODE: ipv.Mode = IPVlanMode(native.Uint32(datum.Value[0:4])) case nl.IFLA_IPVLAN_FLAG: ipv.Flag = IPVlanFlag(native.Uint32(datum.Value[0:4])) } } } func parseIPVtapData(link Link, data []syscall.NetlinkRouteAttr) { ipv := link.(*IPVtap) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPVLAN_MODE: ipv.Mode = IPVlanMode(native.Uint32(datum.Value[0:4])) case nl.IFLA_IPVLAN_FLAG: ipv.Flag = IPVlanFlag(native.Uint32(datum.Value[0:4])) } } } func addMacvtapAttrs(macvtap *Macvtap, linkInfo *nl.RtAttr) { addMacvlanAttrs(&macvtap.Macvlan, linkInfo) } func parseMacvtapData(link Link, data []syscall.NetlinkRouteAttr) { macv := link.(*Macvtap) parseMacvlanData(&macv.Macvlan, data) } func addMacvlanAttrs(macvlan *Macvlan, linkInfo *nl.RtAttr) { var data *nl.RtAttr if macvlan.Mode != MACVLAN_MODE_DEFAULT || macvlan.BCQueueLen > 0 { data = linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) } if macvlan.Mode != MACVLAN_MODE_DEFAULT { data.AddRtAttr(nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[macvlan.Mode])) } if macvlan.BCQueueLen > 0 { data.AddRtAttr(nl.IFLA_MACVLAN_BC_QUEUE_LEN, nl.Uint32Attr(macvlan.BCQueueLen)) } } func parseMacvlanData(link Link, data []syscall.NetlinkRouteAttr) { macv := link.(*Macvlan) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_MACVLAN_MODE: switch native.Uint32(datum.Value[0:4]) { case nl.MACVLAN_MODE_PRIVATE: macv.Mode = MACVLAN_MODE_PRIVATE case nl.MACVLAN_MODE_VEPA: macv.Mode = MACVLAN_MODE_VEPA case nl.MACVLAN_MODE_BRIDGE: macv.Mode = MACVLAN_MODE_BRIDGE case nl.MACVLAN_MODE_PASSTHRU: macv.Mode = MACVLAN_MODE_PASSTHRU case nl.MACVLAN_MODE_SOURCE: macv.Mode = MACVLAN_MODE_SOURCE } case nl.IFLA_MACVLAN_MACADDR_COUNT: macv.MACAddrs = make([]net.HardwareAddr, 0, int(native.Uint32(datum.Value[0:4]))) case nl.IFLA_MACVLAN_MACADDR_DATA: macs, err := nl.ParseRouteAttr(datum.Value[:]) if err != nil { panic(fmt.Sprintf("failed to ParseRouteAttr for IFLA_MACVLAN_MACADDR_DATA: %v", err)) } for _, macDatum := range macs { macv.MACAddrs = append(macv.MACAddrs, net.HardwareAddr(macDatum.Value[0:6])) } case nl.IFLA_MACVLAN_BC_QUEUE_LEN: macv.BCQueueLen = native.Uint32(datum.Value[0:4]) case nl.IFLA_MACVLAN_BC_QUEUE_LEN_USED: macv.UsedBCQueueLen = native.Uint32(datum.Value[0:4]) } } } // copied from pkg/net_linux.go func linkFlags(rawFlags uint32) net.Flags { var f net.Flags if rawFlags&unix.IFF_UP != 0 { f |= net.FlagUp } if rawFlags&unix.IFF_BROADCAST != 0 { f |= net.FlagBroadcast } if rawFlags&unix.IFF_LOOPBACK != 0 { f |= net.FlagLoopback } if rawFlags&unix.IFF_POINTOPOINT != 0 { f |= net.FlagPointToPoint } if rawFlags&unix.IFF_MULTICAST != 0 { f |= net.FlagMulticast } return f } func addGeneveAttrs(geneve *Geneve, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if geneve.InnerProtoInherit { data.AddRtAttr(nl.IFLA_GENEVE_INNER_PROTO_INHERIT, []byte{}) } if geneve.FlowBased { geneve.ID = 0 data.AddRtAttr(nl.IFLA_GENEVE_COLLECT_METADATA, []byte{}) } if ip := geneve.Remote; ip != nil { if ip4 := ip.To4(); ip4 != nil { data.AddRtAttr(nl.IFLA_GENEVE_REMOTE, ip.To4()) } else { data.AddRtAttr(nl.IFLA_GENEVE_REMOTE6, []byte(ip)) } } if geneve.ID != 0 { data.AddRtAttr(nl.IFLA_GENEVE_ID, nl.Uint32Attr(geneve.ID)) } if geneve.Dport != 0 { data.AddRtAttr(nl.IFLA_GENEVE_PORT, htons(geneve.Dport)) } if geneve.Ttl != 0 { data.AddRtAttr(nl.IFLA_GENEVE_TTL, nl.Uint8Attr(geneve.Ttl)) } if geneve.Tos != 0 { data.AddRtAttr(nl.IFLA_GENEVE_TOS, nl.Uint8Attr(geneve.Tos)) } data.AddRtAttr(nl.IFLA_GENEVE_DF, nl.Uint8Attr(uint8(geneve.Df))) } func parseGeneveData(link Link, data []syscall.NetlinkRouteAttr) { geneve := link.(*Geneve) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_GENEVE_ID: geneve.ID = native.Uint32(datum.Value[0:4]) case nl.IFLA_GENEVE_REMOTE, nl.IFLA_GENEVE_REMOTE6: geneve.Remote = datum.Value case nl.IFLA_GENEVE_PORT: geneve.Dport = ntohs(datum.Value[0:2]) case nl.IFLA_GENEVE_TTL: geneve.Ttl = uint8(datum.Value[0]) case nl.IFLA_GENEVE_TOS: geneve.Tos = uint8(datum.Value[0]) case nl.IFLA_GENEVE_COLLECT_METADATA: geneve.FlowBased = true case nl.IFLA_GENEVE_INNER_PROTO_INHERIT: geneve.InnerProtoInherit = true } } } func addGretapAttrs(gretap *Gretap, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if gretap.FlowBased { // In flow based mode, no other attributes need to be configured data.AddRtAttr(nl.IFLA_GRE_COLLECT_METADATA, []byte{}) return } if ip := gretap.Local; ip != nil { if ip.To4() != nil { ip = ip.To4() } data.AddRtAttr(nl.IFLA_GRE_LOCAL, []byte(ip)) } if ip := gretap.Remote; ip != nil { if ip.To4() != nil { ip = ip.To4() } data.AddRtAttr(nl.IFLA_GRE_REMOTE, []byte(ip)) } if gretap.IKey != 0 { data.AddRtAttr(nl.IFLA_GRE_IKEY, htonl(gretap.IKey)) gretap.IFlags |= uint16(nl.GRE_KEY) } if gretap.OKey != 0 { data.AddRtAttr(nl.IFLA_GRE_OKEY, htonl(gretap.OKey)) gretap.OFlags |= uint16(nl.GRE_KEY) } data.AddRtAttr(nl.IFLA_GRE_IFLAGS, htons(gretap.IFlags)) data.AddRtAttr(nl.IFLA_GRE_OFLAGS, htons(gretap.OFlags)) if gretap.Link != 0 { data.AddRtAttr(nl.IFLA_GRE_LINK, nl.Uint32Attr(gretap.Link)) } data.AddRtAttr(nl.IFLA_GRE_PMTUDISC, nl.Uint8Attr(gretap.PMtuDisc)) data.AddRtAttr(nl.IFLA_GRE_TTL, nl.Uint8Attr(gretap.Ttl)) data.AddRtAttr(nl.IFLA_GRE_TOS, nl.Uint8Attr(gretap.Tos)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_TYPE, nl.Uint16Attr(gretap.EncapType)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_FLAGS, nl.Uint16Attr(gretap.EncapFlags)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_SPORT, htons(gretap.EncapSport)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_DPORT, htons(gretap.EncapDport)) } func parseGretapData(link Link, data []syscall.NetlinkRouteAttr) { gre := link.(*Gretap) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_GRE_OKEY: gre.IKey = ntohl(datum.Value[0:4]) case nl.IFLA_GRE_IKEY: gre.OKey = ntohl(datum.Value[0:4]) case nl.IFLA_GRE_LOCAL: gre.Local = net.IP(datum.Value) case nl.IFLA_GRE_REMOTE: gre.Remote = net.IP(datum.Value) case nl.IFLA_GRE_ENCAP_SPORT: gre.EncapSport = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_ENCAP_DPORT: gre.EncapDport = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_IFLAGS: gre.IFlags = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_OFLAGS: gre.OFlags = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_TTL: gre.Ttl = uint8(datum.Value[0]) case nl.IFLA_GRE_TOS: gre.Tos = uint8(datum.Value[0]) case nl.IFLA_GRE_PMTUDISC: gre.PMtuDisc = uint8(datum.Value[0]) case nl.IFLA_GRE_ENCAP_TYPE: gre.EncapType = native.Uint16(datum.Value[0:2]) case nl.IFLA_GRE_ENCAP_FLAGS: gre.EncapFlags = native.Uint16(datum.Value[0:2]) case nl.IFLA_GRE_COLLECT_METADATA: gre.FlowBased = true } } } func addGretunAttrs(gre *Gretun, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if gre.FlowBased { // In flow based mode, no other attributes need to be configured data.AddRtAttr(nl.IFLA_GRE_COLLECT_METADATA, []byte{}) return } if ip := gre.Local; ip != nil { if ip.To4() != nil { ip = ip.To4() } data.AddRtAttr(nl.IFLA_GRE_LOCAL, []byte(ip)) } if ip := gre.Remote; ip != nil { if ip.To4() != nil { ip = ip.To4() } data.AddRtAttr(nl.IFLA_GRE_REMOTE, []byte(ip)) } if gre.IKey != 0 { data.AddRtAttr(nl.IFLA_GRE_IKEY, htonl(gre.IKey)) gre.IFlags |= uint16(nl.GRE_KEY) } if gre.OKey != 0 { data.AddRtAttr(nl.IFLA_GRE_OKEY, htonl(gre.OKey)) gre.OFlags |= uint16(nl.GRE_KEY) } data.AddRtAttr(nl.IFLA_GRE_IFLAGS, htons(gre.IFlags)) data.AddRtAttr(nl.IFLA_GRE_OFLAGS, htons(gre.OFlags)) if gre.Link != 0 { data.AddRtAttr(nl.IFLA_GRE_LINK, nl.Uint32Attr(gre.Link)) } data.AddRtAttr(nl.IFLA_GRE_PMTUDISC, nl.Uint8Attr(gre.PMtuDisc)) data.AddRtAttr(nl.IFLA_GRE_TTL, nl.Uint8Attr(gre.Ttl)) data.AddRtAttr(nl.IFLA_GRE_TOS, nl.Uint8Attr(gre.Tos)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_TYPE, nl.Uint16Attr(gre.EncapType)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_FLAGS, nl.Uint16Attr(gre.EncapFlags)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_SPORT, htons(gre.EncapSport)) data.AddRtAttr(nl.IFLA_GRE_ENCAP_DPORT, htons(gre.EncapDport)) } func parseGretunData(link Link, data []syscall.NetlinkRouteAttr) { gre := link.(*Gretun) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_GRE_IKEY: gre.IKey = ntohl(datum.Value[0:4]) case nl.IFLA_GRE_OKEY: gre.OKey = ntohl(datum.Value[0:4]) case nl.IFLA_GRE_LOCAL: gre.Local = net.IP(datum.Value) case nl.IFLA_GRE_REMOTE: gre.Remote = net.IP(datum.Value) case nl.IFLA_GRE_IFLAGS: gre.IFlags = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_OFLAGS: gre.OFlags = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_TTL: gre.Ttl = uint8(datum.Value[0]) case nl.IFLA_GRE_TOS: gre.Tos = uint8(datum.Value[0]) case nl.IFLA_GRE_PMTUDISC: gre.PMtuDisc = uint8(datum.Value[0]) case nl.IFLA_GRE_ENCAP_TYPE: gre.EncapType = native.Uint16(datum.Value[0:2]) case nl.IFLA_GRE_ENCAP_FLAGS: gre.EncapFlags = native.Uint16(datum.Value[0:2]) case nl.IFLA_GRE_ENCAP_SPORT: gre.EncapSport = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_ENCAP_DPORT: gre.EncapDport = ntohs(datum.Value[0:2]) case nl.IFLA_GRE_COLLECT_METADATA: gre.FlowBased = true } } } func addXdpAttrs(xdp *LinkXdp, req *nl.NetlinkRequest) { attrs := nl.NewRtAttr(unix.IFLA_XDP|unix.NLA_F_NESTED, nil) b := make([]byte, 4) native.PutUint32(b, uint32(xdp.Fd)) attrs.AddRtAttr(nl.IFLA_XDP_FD, b) if xdp.Flags != 0 { b := make([]byte, 4) native.PutUint32(b, xdp.Flags) attrs.AddRtAttr(nl.IFLA_XDP_FLAGS, b) } req.AddData(attrs) } func parseLinkXdp(data []byte) (*LinkXdp, error) { attrs, err := nl.ParseRouteAttr(data) if err != nil { return nil, err } xdp := &LinkXdp{} for _, attr := range attrs { switch attr.Attr.Type { case nl.IFLA_XDP_FD: xdp.Fd = int(native.Uint32(attr.Value[0:4])) case nl.IFLA_XDP_ATTACHED: xdp.AttachMode = uint32(attr.Value[0]) xdp.Attached = xdp.AttachMode != 0 case nl.IFLA_XDP_FLAGS: xdp.Flags = native.Uint32(attr.Value[0:4]) case nl.IFLA_XDP_PROG_ID: xdp.ProgId = native.Uint32(attr.Value[0:4]) } } return xdp, nil } func addIptunAttrs(iptun *Iptun, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if iptun.FlowBased { // In flow based mode, no other attributes need to be configured data.AddRtAttr(nl.IFLA_IPTUN_COLLECT_METADATA, []byte{}) return } ip := iptun.Local.To4() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_LOCAL, []byte(ip)) } ip = iptun.Remote.To4() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_REMOTE, []byte(ip)) } if iptun.Link != 0 { data.AddRtAttr(nl.IFLA_IPTUN_LINK, nl.Uint32Attr(iptun.Link)) } data.AddRtAttr(nl.IFLA_IPTUN_PMTUDISC, nl.Uint8Attr(iptun.PMtuDisc)) data.AddRtAttr(nl.IFLA_IPTUN_TTL, nl.Uint8Attr(iptun.Ttl)) data.AddRtAttr(nl.IFLA_IPTUN_TOS, nl.Uint8Attr(iptun.Tos)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_TYPE, nl.Uint16Attr(iptun.EncapType)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_FLAGS, nl.Uint16Attr(iptun.EncapFlags)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_SPORT, htons(iptun.EncapSport)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_DPORT, htons(iptun.EncapDport)) data.AddRtAttr(nl.IFLA_IPTUN_PROTO, nl.Uint8Attr(iptun.Proto)) } func parseIptunData(link Link, data []syscall.NetlinkRouteAttr) { iptun := link.(*Iptun) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPTUN_LOCAL: iptun.Local = net.IP(datum.Value[0:4]) case nl.IFLA_IPTUN_REMOTE: iptun.Remote = net.IP(datum.Value[0:4]) case nl.IFLA_IPTUN_TTL: iptun.Ttl = uint8(datum.Value[0]) case nl.IFLA_IPTUN_TOS: iptun.Tos = uint8(datum.Value[0]) case nl.IFLA_IPTUN_PMTUDISC: iptun.PMtuDisc = uint8(datum.Value[0]) case nl.IFLA_IPTUN_ENCAP_SPORT: iptun.EncapSport = ntohs(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_DPORT: iptun.EncapDport = ntohs(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_TYPE: iptun.EncapType = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_FLAGS: iptun.EncapFlags = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_COLLECT_METADATA: iptun.FlowBased = true case nl.IFLA_IPTUN_PROTO: iptun.Proto = datum.Value[0] } } } func addIp6tnlAttrs(ip6tnl *Ip6tnl, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if ip6tnl.FlowBased { // In flow based mode, no other attributes need to be configured data.AddRtAttr(nl.IFLA_IPTUN_COLLECT_METADATA, []byte{}) return } if ip6tnl.Link != 0 { data.AddRtAttr(nl.IFLA_IPTUN_LINK, nl.Uint32Attr(ip6tnl.Link)) } ip := ip6tnl.Local.To16() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_LOCAL, []byte(ip)) } ip = ip6tnl.Remote.To16() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_REMOTE, []byte(ip)) } data.AddRtAttr(nl.IFLA_IPTUN_TTL, nl.Uint8Attr(ip6tnl.Ttl)) data.AddRtAttr(nl.IFLA_IPTUN_TOS, nl.Uint8Attr(ip6tnl.Tos)) data.AddRtAttr(nl.IFLA_IPTUN_FLAGS, nl.Uint32Attr(ip6tnl.Flags)) data.AddRtAttr(nl.IFLA_IPTUN_PROTO, nl.Uint8Attr(ip6tnl.Proto)) data.AddRtAttr(nl.IFLA_IPTUN_FLOWINFO, nl.Uint32Attr(ip6tnl.FlowInfo)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_LIMIT, nl.Uint8Attr(ip6tnl.EncapLimit)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_TYPE, nl.Uint16Attr(ip6tnl.EncapType)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_FLAGS, nl.Uint16Attr(ip6tnl.EncapFlags)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_SPORT, htons(ip6tnl.EncapSport)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_DPORT, htons(ip6tnl.EncapDport)) } func parseIp6tnlData(link Link, data []syscall.NetlinkRouteAttr) { ip6tnl := link.(*Ip6tnl) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPTUN_LOCAL: ip6tnl.Local = net.IP(datum.Value[:16]) case nl.IFLA_IPTUN_REMOTE: ip6tnl.Remote = net.IP(datum.Value[:16]) case nl.IFLA_IPTUN_TTL: ip6tnl.Ttl = datum.Value[0] case nl.IFLA_IPTUN_TOS: ip6tnl.Tos = datum.Value[0] case nl.IFLA_IPTUN_FLAGS: ip6tnl.Flags = native.Uint32(datum.Value[:4]) case nl.IFLA_IPTUN_PROTO: ip6tnl.Proto = datum.Value[0] case nl.IFLA_IPTUN_FLOWINFO: ip6tnl.FlowInfo = native.Uint32(datum.Value[:4]) case nl.IFLA_IPTUN_ENCAP_LIMIT: ip6tnl.EncapLimit = datum.Value[0] case nl.IFLA_IPTUN_ENCAP_TYPE: ip6tnl.EncapType = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_FLAGS: ip6tnl.EncapFlags = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_SPORT: ip6tnl.EncapSport = ntohs(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_DPORT: ip6tnl.EncapDport = ntohs(datum.Value[0:2]) case nl.IFLA_IPTUN_COLLECT_METADATA: ip6tnl.FlowBased = true } } } func addSittunAttrs(sittun *Sittun, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if sittun.Link != 0 { data.AddRtAttr(nl.IFLA_IPTUN_LINK, nl.Uint32Attr(sittun.Link)) } ip := sittun.Local.To4() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_LOCAL, []byte(ip)) } ip = sittun.Remote.To4() if ip != nil { data.AddRtAttr(nl.IFLA_IPTUN_REMOTE, []byte(ip)) } if sittun.Ttl > 0 { // Would otherwise fail on 3.10 kernel data.AddRtAttr(nl.IFLA_IPTUN_TTL, nl.Uint8Attr(sittun.Ttl)) } data.AddRtAttr(nl.IFLA_IPTUN_PROTO, nl.Uint8Attr(sittun.Proto)) data.AddRtAttr(nl.IFLA_IPTUN_TOS, nl.Uint8Attr(sittun.Tos)) data.AddRtAttr(nl.IFLA_IPTUN_PMTUDISC, nl.Uint8Attr(sittun.PMtuDisc)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_LIMIT, nl.Uint8Attr(sittun.EncapLimit)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_TYPE, nl.Uint16Attr(sittun.EncapType)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_FLAGS, nl.Uint16Attr(sittun.EncapFlags)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_SPORT, htons(sittun.EncapSport)) data.AddRtAttr(nl.IFLA_IPTUN_ENCAP_DPORT, htons(sittun.EncapDport)) } func parseSittunData(link Link, data []syscall.NetlinkRouteAttr) { sittun := link.(*Sittun) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPTUN_LOCAL: sittun.Local = net.IP(datum.Value[0:4]) case nl.IFLA_IPTUN_REMOTE: sittun.Remote = net.IP(datum.Value[0:4]) case nl.IFLA_IPTUN_TTL: sittun.Ttl = datum.Value[0] case nl.IFLA_IPTUN_TOS: sittun.Tos = datum.Value[0] case nl.IFLA_IPTUN_PMTUDISC: sittun.PMtuDisc = datum.Value[0] case nl.IFLA_IPTUN_PROTO: sittun.Proto = datum.Value[0] case nl.IFLA_IPTUN_ENCAP_TYPE: sittun.EncapType = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_FLAGS: sittun.EncapFlags = native.Uint16(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_SPORT: sittun.EncapSport = ntohs(datum.Value[0:2]) case nl.IFLA_IPTUN_ENCAP_DPORT: sittun.EncapDport = ntohs(datum.Value[0:2]) } } } func addVtiAttrs(vti *Vti, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) family := FAMILY_V4 if vti.Local.To4() == nil { family = FAMILY_V6 } var ip net.IP if family == FAMILY_V4 { ip = vti.Local.To4() } else { ip = vti.Local } if ip != nil { data.AddRtAttr(nl.IFLA_VTI_LOCAL, []byte(ip)) } if family == FAMILY_V4 { ip = vti.Remote.To4() } else { ip = vti.Remote } if ip != nil { data.AddRtAttr(nl.IFLA_VTI_REMOTE, []byte(ip)) } if vti.Link != 0 { data.AddRtAttr(nl.IFLA_VTI_LINK, nl.Uint32Attr(vti.Link)) } data.AddRtAttr(nl.IFLA_VTI_IKEY, htonl(vti.IKey)) data.AddRtAttr(nl.IFLA_VTI_OKEY, htonl(vti.OKey)) } func parseVtiData(link Link, data []syscall.NetlinkRouteAttr) { vti := link.(*Vti) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_VTI_LOCAL: vti.Local = net.IP(datum.Value) case nl.IFLA_VTI_REMOTE: vti.Remote = net.IP(datum.Value) case nl.IFLA_VTI_IKEY: vti.IKey = ntohl(datum.Value[0:4]) case nl.IFLA_VTI_OKEY: vti.OKey = ntohl(datum.Value[0:4]) } } } func addVrfAttrs(vrf *Vrf, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) b := make([]byte, 4) native.PutUint32(b, uint32(vrf.Table)) data.AddRtAttr(nl.IFLA_VRF_TABLE, b) } func parseVrfData(link Link, data []syscall.NetlinkRouteAttr) { vrf := link.(*Vrf) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_VRF_TABLE: vrf.Table = native.Uint32(datum.Value[0:4]) } } } func addBridgeAttrs(bridge *Bridge, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) if bridge.MulticastSnooping != nil { data.AddRtAttr(nl.IFLA_BR_MCAST_SNOOPING, boolToByte(*bridge.MulticastSnooping)) } if bridge.AgeingTime != nil { data.AddRtAttr(nl.IFLA_BR_AGEING_TIME, nl.Uint32Attr(*bridge.AgeingTime)) } if bridge.HelloTime != nil { data.AddRtAttr(nl.IFLA_BR_HELLO_TIME, nl.Uint32Attr(*bridge.HelloTime)) } if bridge.VlanFiltering != nil { data.AddRtAttr(nl.IFLA_BR_VLAN_FILTERING, boolToByte(*bridge.VlanFiltering)) } if bridge.VlanDefaultPVID != nil { data.AddRtAttr(nl.IFLA_BR_VLAN_DEFAULT_PVID, nl.Uint16Attr(*bridge.VlanDefaultPVID)) } if bridge.GroupFwdMask != nil { data.AddRtAttr(nl.IFLA_BR_GROUP_FWD_MASK, nl.Uint16Attr(*bridge.GroupFwdMask)) } } func parseBridgeData(bridge Link, data []syscall.NetlinkRouteAttr) { br := bridge.(*Bridge) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_BR_AGEING_TIME: ageingTime := native.Uint32(datum.Value[0:4]) br.AgeingTime = &ageingTime case nl.IFLA_BR_HELLO_TIME: helloTime := native.Uint32(datum.Value[0:4]) br.HelloTime = &helloTime case nl.IFLA_BR_MCAST_SNOOPING: mcastSnooping := datum.Value[0] == 1 br.MulticastSnooping = &mcastSnooping case nl.IFLA_BR_VLAN_FILTERING: vlanFiltering := datum.Value[0] == 1 br.VlanFiltering = &vlanFiltering case nl.IFLA_BR_VLAN_DEFAULT_PVID: vlanDefaultPVID := native.Uint16(datum.Value[0:2]) br.VlanDefaultPVID = &vlanDefaultPVID case nl.IFLA_BR_GROUP_FWD_MASK: mask := native.Uint16(datum.Value[0:2]) br.GroupFwdMask = &mask } } } func addGTPAttrs(gtp *GTP, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_GTP_FD0, nl.Uint32Attr(uint32(gtp.FD0))) data.AddRtAttr(nl.IFLA_GTP_FD1, nl.Uint32Attr(uint32(gtp.FD1))) data.AddRtAttr(nl.IFLA_GTP_PDP_HASHSIZE, nl.Uint32Attr(131072)) if gtp.Role != nl.GTP_ROLE_GGSN { data.AddRtAttr(nl.IFLA_GTP_ROLE, nl.Uint32Attr(uint32(gtp.Role))) } } func parseGTPData(link Link, data []syscall.NetlinkRouteAttr) { gtp := link.(*GTP) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_GTP_FD0: gtp.FD0 = int(native.Uint32(datum.Value)) case nl.IFLA_GTP_FD1: gtp.FD1 = int(native.Uint32(datum.Value)) case nl.IFLA_GTP_PDP_HASHSIZE: gtp.PDPHashsize = int(native.Uint32(datum.Value)) case nl.IFLA_GTP_ROLE: gtp.Role = int(native.Uint32(datum.Value)) } } } func parseVfInfoList(data []syscall.NetlinkRouteAttr) ([]VfInfo, error) { var vfs []VfInfo for i, element := range data { if element.Attr.Type != nl.IFLA_VF_INFO { return nil, fmt.Errorf("Incorrect element type in vf info list: %d", element.Attr.Type) } vfAttrs, err := nl.ParseRouteAttr(element.Value) if err != nil { return nil, err } vf, err := parseVfInfo(vfAttrs, i) if err != nil { return nil, err } vfs = append(vfs, vf) } return vfs, nil } func parseVfInfo(data []syscall.NetlinkRouteAttr, id int) (VfInfo, error) { vf := VfInfo{ID: id} for _, element := range data { switch element.Attr.Type { case nl.IFLA_VF_MAC: mac := nl.DeserializeVfMac(element.Value[:]) vf.Mac = mac.Mac[:6] case nl.IFLA_VF_VLAN: vl := nl.DeserializeVfVlan(element.Value[:]) vf.Vlan = int(vl.Vlan) vf.Qos = int(vl.Qos) case nl.IFLA_VF_VLAN_LIST: vfVlanInfoList, err := nl.DeserializeVfVlanList(element.Value[:]) if err != nil { return vf, err } vf.VlanProto = int(vfVlanInfoList[0].VlanProto) case nl.IFLA_VF_TX_RATE: txr := nl.DeserializeVfTxRate(element.Value[:]) vf.TxRate = int(txr.Rate) case nl.IFLA_VF_SPOOFCHK: sp := nl.DeserializeVfSpoofchk(element.Value[:]) vf.Spoofchk = sp.Setting != 0 case nl.IFLA_VF_LINK_STATE: ls := nl.DeserializeVfLinkState(element.Value[:]) vf.LinkState = ls.LinkState case nl.IFLA_VF_RATE: vfr := nl.DeserializeVfRate(element.Value[:]) vf.MaxTxRate = vfr.MaxTxRate vf.MinTxRate = vfr.MinTxRate case nl.IFLA_VF_STATS: vfstats := nl.DeserializeVfStats(element.Value[:]) vf.RxPackets = vfstats.RxPackets vf.TxPackets = vfstats.TxPackets vf.RxBytes = vfstats.RxBytes vf.TxBytes = vfstats.TxBytes vf.Multicast = vfstats.Multicast vf.Broadcast = vfstats.Broadcast vf.RxDropped = vfstats.RxDropped vf.TxDropped = vfstats.TxDropped case nl.IFLA_VF_RSS_QUERY_EN: result := nl.DeserializeVfRssQueryEn(element.Value) vf.RssQuery = result.Setting case nl.IFLA_VF_TRUST: result := nl.DeserializeVfTrust(element.Value) vf.Trust = result.Setting } } return vf, nil } func addXfrmiAttrs(xfrmi *Xfrmi, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_XFRM_LINK, nl.Uint32Attr(uint32(xfrmi.ParentIndex))) if xfrmi.Ifid != 0 { data.AddRtAttr(nl.IFLA_XFRM_IF_ID, nl.Uint32Attr(xfrmi.Ifid)) } } func parseXfrmiData(link Link, data []syscall.NetlinkRouteAttr) { xfrmi := link.(*Xfrmi) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_XFRM_LINK: xfrmi.ParentIndex = int(native.Uint32(datum.Value)) case nl.IFLA_XFRM_IF_ID: xfrmi.Ifid = native.Uint32(datum.Value) } } } func ioctlBondSlave(cmd uintptr, link Link, master *Bond) error { fd, err := getSocketUDP() if err != nil { return err } defer syscall.Close(fd) ifreq := newIocltSlaveReq(link.Attrs().Name, master.Attrs().Name) _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), cmd, uintptr(unsafe.Pointer(ifreq))) if errno != 0 { return fmt.Errorf("errno=%v", errno) } return nil } // LinkSetBondSlaveActive sets specified slave to ACTIVE in an `active-backup` bond link via ioctl interface. // // Multiple calls keeps the status unchanged(shown in the unit test). func LinkSetBondSlaveActive(link Link, master *Bond) error { err := ioctlBondSlave(unix.SIOCBONDCHANGEACTIVE, link, master) if err != nil { return fmt.Errorf("Failed to set slave %q active in %q, %v", link.Attrs().Name, master.Attrs().Name, err) } return nil } // LinkSetBondSlave add slave to bond link via ioctl interface. func LinkSetBondSlave(link Link, master *Bond) error { err := ioctlBondSlave(unix.SIOCBONDENSLAVE, link, master) if err != nil { return fmt.Errorf("Failed to enslave %q to %q, %v", link.Attrs().Name, master.Attrs().Name, err) } return nil } // LinkSetBondSlave removes specified slave from bond link via ioctl interface. func LinkDelBondSlave(link Link, master *Bond) error { err := ioctlBondSlave(unix.SIOCBONDRELEASE, link, master) if err != nil { return fmt.Errorf("Failed to del slave %q from %q, %v", link.Attrs().Name, master.Attrs().Name, err) } return nil } // LinkSetBondSlaveQueueId modify bond slave queue-id. func (h *Handle) LinkSetBondSlaveQueueId(link Link, queueId uint16) error { base := link.Attrs() h.ensureIndex(base) req := h.newNetlinkRequest(unix.RTM_SETLINK, unix.NLM_F_ACK) msg := nl.NewIfInfomsg(unix.AF_UNSPEC) msg.Index = int32(base.Index) req.AddData(msg) linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) data := linkInfo.AddRtAttr(nl.IFLA_INFO_SLAVE_DATA, nil) data.AddRtAttr(nl.IFLA_BOND_SLAVE_QUEUE_ID, nl.Uint16Attr(queueId)) req.AddData(linkInfo) _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // LinkSetBondSlaveQueueId modify bond slave queue-id. func LinkSetBondSlaveQueueId(link Link, queueId uint16) error { return pkgHandle.LinkSetBondSlaveQueueId(link, queueId) } func vethStatsSerialize(stats ethtoolStats) ([]byte, error) { statsSize := int(unsafe.Sizeof(stats)) + int(stats.nStats)*int(unsafe.Sizeof(uint64(0))) b := make([]byte, 0, statsSize) buf := bytes.NewBuffer(b) err := binary.Write(buf, nl.NativeEndian(), stats) return buf.Bytes()[:statsSize], err } type vethEthtoolStats struct { Cmd uint32 NStats uint32 Peer uint64 // Newer kernels have XDP stats in here, but we only care // to extract the peer ifindex here. } func vethStatsDeserialize(b []byte) (vethEthtoolStats, error) { var stats = vethEthtoolStats{} err := binary.Read(bytes.NewReader(b), nl.NativeEndian(), &stats) return stats, err } // VethPeerIndex get veth peer index. func VethPeerIndex(link *Veth) (int, error) { fd, err := getSocketUDP() if err != nil { return -1, err } defer syscall.Close(fd) ifreq, sSet := newIocltStringSetReq(link.Name) _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCETHTOOL, uintptr(unsafe.Pointer(ifreq))) if errno != 0 { return -1, fmt.Errorf("SIOCETHTOOL request for %q failed, errno=%v", link.Attrs().Name, errno) } stats := ethtoolStats{ cmd: ETHTOOL_GSTATS, nStats: sSet.data[0], } buffer, err := vethStatsSerialize(stats) if err != nil { return -1, err } ifreq.Data = uintptr(unsafe.Pointer(&buffer[0])) _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCETHTOOL, uintptr(unsafe.Pointer(ifreq))) if errno != 0 { return -1, fmt.Errorf("SIOCETHTOOL request for %q failed, errno=%v", link.Attrs().Name, errno) } vstats, err := vethStatsDeserialize(buffer) if err != nil { return -1, err } return int(vstats.Peer), nil } func parseTuntapData(link Link, data []syscall.NetlinkRouteAttr) { tuntap := link.(*Tuntap) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_TUN_OWNER: tuntap.Owner = native.Uint32(datum.Value) case nl.IFLA_TUN_GROUP: tuntap.Group = native.Uint32(datum.Value) case nl.IFLA_TUN_TYPE: tuntap.Mode = TuntapMode(uint8(datum.Value[0])) case nl.IFLA_TUN_PERSIST: tuntap.NonPersist = false if uint8(datum.Value[0]) == 0 { tuntap.NonPersist = true } } } } func parseIPoIBData(link Link, data []syscall.NetlinkRouteAttr) { ipoib := link.(*IPoIB) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_IPOIB_PKEY: ipoib.Pkey = uint16(native.Uint16(datum.Value)) case nl.IFLA_IPOIB_MODE: ipoib.Mode = IPoIBMode(native.Uint16(datum.Value)) case nl.IFLA_IPOIB_UMCAST: ipoib.Umcast = uint16(native.Uint16(datum.Value)) } } } func parseCanData(link Link, data []syscall.NetlinkRouteAttr) { can := link.(*Can) for _, datum := range data { switch datum.Attr.Type { case nl.IFLA_CAN_BITTIMING: can.BitRate = native.Uint32(datum.Value) can.SamplePoint = native.Uint32(datum.Value[4:]) can.TimeQuanta = native.Uint32(datum.Value[8:]) can.PropagationSegment = native.Uint32(datum.Value[12:]) can.PhaseSegment1 = native.Uint32(datum.Value[16:]) can.PhaseSegment2 = native.Uint32(datum.Value[20:]) can.SyncJumpWidth = native.Uint32(datum.Value[24:]) can.BitRatePreScaler = native.Uint32(datum.Value[28:]) case nl.IFLA_CAN_BITTIMING_CONST: can.Name = string(datum.Value[:16]) can.TimeSegment1Min = native.Uint32(datum.Value[16:]) can.TimeSegment1Max = native.Uint32(datum.Value[20:]) can.TimeSegment2Min = native.Uint32(datum.Value[24:]) can.TimeSegment2Max = native.Uint32(datum.Value[28:]) can.SyncJumpWidthMax = native.Uint32(datum.Value[32:]) can.BitRatePreScalerMin = native.Uint32(datum.Value[36:]) can.BitRatePreScalerMax = native.Uint32(datum.Value[40:]) can.BitRatePreScalerInc = native.Uint32(datum.Value[44:]) case nl.IFLA_CAN_CLOCK: can.ClockFrequency = native.Uint32(datum.Value) case nl.IFLA_CAN_STATE: can.State = native.Uint32(datum.Value) case nl.IFLA_CAN_CTRLMODE: can.Mask = native.Uint32(datum.Value) can.Flags = native.Uint32(datum.Value[4:]) case nl.IFLA_CAN_BERR_COUNTER: can.TxError = native.Uint16(datum.Value) can.RxError = native.Uint16(datum.Value[2:]) case nl.IFLA_CAN_RESTART_MS: can.RestartMs = native.Uint32(datum.Value) case nl.IFLA_CAN_DATA_BITTIMING_CONST: case nl.IFLA_CAN_RESTART: case nl.IFLA_CAN_DATA_BITTIMING: case nl.IFLA_CAN_TERMINATION: case nl.IFLA_CAN_TERMINATION_CONST: case nl.IFLA_CAN_BITRATE_CONST: case nl.IFLA_CAN_DATA_BITRATE_CONST: case nl.IFLA_CAN_BITRATE_MAX: } } } func addIPoIBAttrs(ipoib *IPoIB, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_IPOIB_PKEY, nl.Uint16Attr(uint16(ipoib.Pkey))) data.AddRtAttr(nl.IFLA_IPOIB_MODE, nl.Uint16Attr(uint16(ipoib.Mode))) data.AddRtAttr(nl.IFLA_IPOIB_UMCAST, nl.Uint16Attr(uint16(ipoib.Umcast))) } func addBareUDPAttrs(bareudp *BareUDP, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_BAREUDP_PORT, nl.Uint16Attr(nl.Swap16(bareudp.Port))) data.AddRtAttr(nl.IFLA_BAREUDP_ETHERTYPE, nl.Uint16Attr(nl.Swap16(bareudp.EtherType))) if bareudp.SrcPortMin != 0 { data.AddRtAttr(nl.IFLA_BAREUDP_SRCPORT_MIN, nl.Uint16Attr(bareudp.SrcPortMin)) } if bareudp.MultiProto { data.AddRtAttr(nl.IFLA_BAREUDP_MULTIPROTO_MODE, []byte{}) } } func parseBareUDPData(link Link, data []syscall.NetlinkRouteAttr) { bareudp := link.(*BareUDP) for _, attr := range data { switch attr.Attr.Type { case nl.IFLA_BAREUDP_PORT: bareudp.Port = binary.BigEndian.Uint16(attr.Value) case nl.IFLA_BAREUDP_ETHERTYPE: bareudp.EtherType = binary.BigEndian.Uint16(attr.Value) case nl.IFLA_BAREUDP_SRCPORT_MIN: bareudp.SrcPortMin = native.Uint16(attr.Value) case nl.IFLA_BAREUDP_MULTIPROTO_MODE: bareudp.MultiProto = true } } } netlink-1.3.0/link_test.go000066400000000000000000002333211466216277000155210ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "bytes" "errors" "fmt" "net" "os" "os/exec" "sort" "strings" "syscall" "testing" "time" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) const ( testTxQLen int = 100 defaultTxQLen int = 1000 testTxQueues int = 4 testRxQueues int = 8 ) func testLinkAddDel(t *testing.T, link Link) { _, err := LinkList() if err != nil { t.Fatal(err) } if err := LinkAdd(link); err != nil { t.Fatal(err) } base := link.Attrs() result, err := LinkByName(base.Name) if err != nil { t.Fatal(err) } rBase := result.Attrs() if base.Index != 0 { if base.Index != rBase.Index { t.Fatalf("index is %d, should be %d", rBase.Index, base.Index) } } if base.Group > 0 { if base.Group != rBase.Group { t.Fatalf("group is %d, should be %d", rBase.Group, base.Group) } } if vlan, ok := link.(*Vlan); ok { other, ok := result.(*Vlan) if !ok { t.Fatal("Result of create is not a vlan") } if vlan.VlanId != other.VlanId { t.Fatal("Link.VlanId id doesn't match") } } if resultPrimary, ok := result.(*Netkit); ok { if inputPrimary, ok := link.(*Netkit); ok { if resultPrimary.Policy != inputPrimary.Policy { t.Fatalf("Policy is %d, should be %d", int(resultPrimary.Policy), int(inputPrimary.Policy)) } if resultPrimary.PeerPolicy != inputPrimary.PeerPolicy { t.Fatalf("Peer Policy is %d, should be %d", int(resultPrimary.PeerPolicy), int(inputPrimary.PeerPolicy)) } if resultPrimary.Mode != inputPrimary.Mode { t.Fatalf("Mode is %d, should be %d", int(resultPrimary.Mode), int(inputPrimary.Mode)) } if inputPrimary.peerLinkAttrs.Name != "" { var resultPeer *Netkit pLink, err := LinkByName(inputPrimary.peerLinkAttrs.Name) if err != nil { t.Fatalf("Failed to get Peer netkit %s", inputPrimary.peerLinkAttrs.Name) } if resultPeer, ok = pLink.(*Netkit); !ok { t.Fatalf("Peer %s is incorrect type", inputPrimary.peerLinkAttrs.Name) } if resultPrimary.PeerPolicy != resultPeer.Policy { t.Fatalf("Peer Policy from primary is %d, should be %d", int(resultPrimary.PeerPolicy), int(resultPeer.Policy)) } if resultPeer.PeerPolicy != resultPrimary.Policy { t.Fatalf("PeerPolicy from peer is %d, should be %d", int(resultPeer.PeerPolicy), int(resultPrimary.Policy)) } if resultPrimary.Mode != resultPeer.Mode { t.Fatalf("Peer Mode from primary is %d, should be %d", int(resultPrimary.Mode), int(resultPeer.Mode)) } if resultPrimary.IsPrimary() == resultPeer.IsPrimary() { t.Fatalf("Both primary and peer device has the same value in IsPrimary() %t", resultPrimary.IsPrimary()) } } } } if veth, ok := result.(*Veth); ok { if rBase.TxQLen != base.TxQLen { t.Fatalf("qlen is %d, should be %d", rBase.TxQLen, base.TxQLen) } if rBase.NumTxQueues != base.NumTxQueues { t.Fatalf("txQueues is %d, should be %d", rBase.NumTxQueues, base.NumTxQueues) } if rBase.NumRxQueues != base.NumRxQueues { t.Fatalf("rxQueues is %d, should be %d", rBase.NumRxQueues, base.NumRxQueues) } if rBase.MTU != base.MTU { t.Fatalf("MTU is %d, should be %d", rBase.MTU, base.MTU) } if original, ok := link.(*Veth); ok { if original.PeerName != "" { var peer *Veth other, err := LinkByName(original.PeerName) if err != nil { t.Fatalf("Peer %s not created", veth.PeerName) } if peer, ok = other.(*Veth); !ok { t.Fatalf("Peer %s is incorrect type", veth.PeerName) } if peer.TxQLen != testTxQLen { t.Fatalf("TxQLen of peer is %d, should be %d", peer.TxQLen, testTxQLen) } if peer.NumTxQueues != testTxQueues { t.Fatalf("NumTxQueues of peer is %d, should be %d", peer.NumTxQueues, testTxQueues) } if peer.NumRxQueues != testRxQueues { t.Fatalf("NumRxQueues of peer is %d, should be %d", peer.NumRxQueues, testRxQueues) } if !bytes.Equal(peer.Attrs().HardwareAddr, original.PeerHardwareAddr) { t.Fatalf("Peer MAC addr is %s, should be %s", peer.Attrs().HardwareAddr, original.PeerHardwareAddr) } } } } if _, ok := result.(*Veth); !ok { if _, ok := result.(*Netkit); !ok { // recent kernels set the parent index for veths/netkit in the response if rBase.ParentIndex == 0 && base.ParentIndex != 0 { t.Fatalf("Created link doesn't have parent %d but it should", base.ParentIndex) } else if rBase.ParentIndex != 0 && base.ParentIndex == 0 { t.Fatalf("Created link has parent %d but it shouldn't", rBase.ParentIndex) } else if rBase.ParentIndex != 0 && base.ParentIndex != 0 { if rBase.ParentIndex != base.ParentIndex { t.Fatalf("Link.ParentIndex doesn't match %d != %d", rBase.ParentIndex, base.ParentIndex) } } } } if _, ok := link.(*Wireguard); ok { _, ok := result.(*Wireguard) if !ok { t.Fatal("Result of create is not a wireguard") } } if vxlan, ok := link.(*Vxlan); ok { other, ok := result.(*Vxlan) if !ok { t.Fatal("Result of create is not a vxlan") } compareVxlan(t, vxlan, other) } if ipv, ok := link.(*IPVlan); ok { other, ok := result.(*IPVlan) if !ok { t.Fatal("Result of create is not a ipvlan") } if ipv.Mode != other.Mode { t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, ipv.Mode) } if ipv.Flag != other.Flag { t.Fatalf("Got unexpected flag: %d, expected: %d", other.Flag, ipv.Flag) } } if macv, ok := link.(*Macvlan); ok { other, ok := result.(*Macvlan) if !ok { t.Fatal("Result of create is not a macvlan") } if macv.Mode != other.Mode { t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, macv.Mode) } if other.BCQueueLen > 0 || other.UsedBCQueueLen > 0 { if other.UsedBCQueueLen < other.BCQueueLen { t.Fatalf("UsedBCQueueLen (%d) is smaller than BCQueueLen (%d)", other.UsedBCQueueLen, other.BCQueueLen) } } if macv.BCQueueLen > 0 { if macv.BCQueueLen != other.BCQueueLen { t.Fatalf("BCQueueLen not set correctly: %d, expected: %d", other.BCQueueLen, macv.BCQueueLen) } } } if macv, ok := link.(*Macvtap); ok { other, ok := result.(*Macvtap) if !ok { t.Fatal("Result of create is not a macvtap") } if macv.Mode != other.Mode { t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, macv.Mode) } if other.BCQueueLen > 0 || other.UsedBCQueueLen > 0 { if other.UsedBCQueueLen < other.BCQueueLen { t.Fatalf("UsedBCQueueLen (%d) is smaller than BCQueueLen (%d)", other.UsedBCQueueLen, other.BCQueueLen) } } if macv.BCQueueLen > 0 { if macv.BCQueueLen != other.BCQueueLen { t.Fatalf("BCQueueLen not set correctly: %d, expected: %d", other.BCQueueLen, macv.BCQueueLen) } } } if _, ok := link.(*Vti); ok { _, ok := result.(*Vti) if !ok { t.Fatal("Result of create is not a vti") } } if bond, ok := link.(*Bond); ok { other, ok := result.(*Bond) if !ok { t.Fatal("Result of create is not a bond") } if bond.Mode != other.Mode { t.Fatalf("Got unexpected mode: %d, expected: %d", other.Mode, bond.Mode) } if bond.ArpIpTargets != nil { if other.ArpIpTargets == nil { t.Fatalf("Got unexpected ArpIpTargets: nil") } if len(bond.ArpIpTargets) != len(other.ArpIpTargets) { t.Fatalf("Got unexpected ArpIpTargets len: %d, expected: %d", len(other.ArpIpTargets), len(bond.ArpIpTargets)) } for i := range bond.ArpIpTargets { if !bond.ArpIpTargets[i].Equal(other.ArpIpTargets[i]) { t.Fatalf("Got unexpected ArpIpTargets: %s, expected: %s", other.ArpIpTargets[i], bond.ArpIpTargets[i]) } } } switch mode := bondModeToString[bond.Mode]; mode { case "802.3ad": if bond.AdSelect != other.AdSelect { t.Fatalf("Got unexpected AdSelect: %d, expected: %d", other.AdSelect, bond.AdSelect) } if bond.AdActorSysPrio != other.AdActorSysPrio { t.Fatalf("Got unexpected AdActorSysPrio: %d, expected: %d", other.AdActorSysPrio, bond.AdActorSysPrio) } if bond.AdUserPortKey != other.AdUserPortKey { t.Fatalf("Got unexpected AdUserPortKey: %d, expected: %d", other.AdUserPortKey, bond.AdUserPortKey) } if !bytes.Equal(bond.AdActorSystem, other.AdActorSystem) { t.Fatalf("Got unexpected AdActorSystem: %d, expected: %d", other.AdActorSystem, bond.AdActorSystem) } case "balance-tlb": if bond.TlbDynamicLb != other.TlbDynamicLb { t.Fatalf("Got unexpected TlbDynamicLb: %d, expected: %d", other.TlbDynamicLb, bond.TlbDynamicLb) } } } if iptun, ok := link.(*Iptun); ok { other, ok := result.(*Iptun) if !ok { t.Fatal("Result of create is not a iptun") } if iptun.FlowBased != other.FlowBased { t.Fatal("Iptun.FlowBased doesn't match") } } if ip6tnl, ok := link.(*Ip6tnl); ok { other, ok := result.(*Ip6tnl) if !ok { t.Fatal("Result of create is not a ip6tnl") } if ip6tnl.FlowBased != other.FlowBased { t.Fatal("Ip6tnl.FlowBased doesn't match") } } if _, ok := link.(*Sittun); ok { _, ok := result.(*Sittun) if !ok { t.Fatal("Result of create is not a sittun") } } if geneve, ok := link.(*Geneve); ok { other, ok := result.(*Geneve) if !ok { t.Fatal("Result of create is not a Geneve") } compareGeneve(t, geneve, other) } if gretap, ok := link.(*Gretap); ok { other, ok := result.(*Gretap) if !ok { t.Fatal("Result of create is not a Gretap") } compareGretap(t, gretap, other) } if gretun, ok := link.(*Gretun); ok { other, ok := result.(*Gretun) if !ok { t.Fatal("Result of create is not a Gretun") } compareGretun(t, gretun, other) } if xfrmi, ok := link.(*Xfrmi); ok { other, ok := result.(*Xfrmi) if !ok { t.Fatal("Result of create is not a xfrmi") } compareXfrmi(t, xfrmi, other) } if tuntap, ok := link.(*Tuntap); ok { other, ok := result.(*Tuntap) if !ok { t.Fatal("Result of create is not a tuntap") } compareTuntap(t, tuntap, other) } if bareudp, ok := link.(*BareUDP); ok { other, ok := result.(*BareUDP) if !ok { t.Fatal("Result of create is not a BareUDP") } compareBareUDP(t, bareudp, other) } if err = LinkDel(link); err != nil { t.Fatal(err) } links, err := LinkList() if err != nil { t.Fatal(err) } for _, l := range links { if l.Attrs().Name == link.Attrs().Name { t.Fatal("Link not removed properly") } } } func compareGeneve(t *testing.T, expected, actual *Geneve) { if actual.ID != expected.ID { t.Fatalf("Geneve.ID doesn't match: %d %d", actual.ID, expected.ID) } // set the Dport to 6081 (the linux default) if it wasn't specified at creation if expected.Dport == 0 { expected.Dport = 6081 } if actual.Dport != expected.Dport { t.Fatal("Geneve.Dport doesn't match") } if actual.Ttl != expected.Ttl { t.Fatal("Geneve.Ttl doesn't match") } if actual.Tos != expected.Tos { t.Fatal("Geneve.Tos doesn't match") } if !actual.Remote.Equal(expected.Remote) { t.Fatalf("Geneve.Remote is not equal: %s!=%s", actual.Remote, expected.Remote) } if actual.FlowBased != expected.FlowBased { t.Fatal("Geneve.FlowBased doesn't match") } if actual.InnerProtoInherit != expected.InnerProtoInherit { t.Fatal("Geneve.InnerProtoInherit doesn't match") } // TODO: we should implement the rest of the geneve methods } func compareGretap(t *testing.T, expected, actual *Gretap) { if actual.IKey != expected.IKey { t.Fatal("Gretap.IKey doesn't match") } if actual.OKey != expected.OKey { t.Fatal("Gretap.OKey doesn't match") } if actual.EncapSport != expected.EncapSport { t.Fatal("Gretap.EncapSport doesn't match") } if actual.EncapDport != expected.EncapDport { t.Fatal("Gretap.EncapDport doesn't match") } if expected.Local != nil && !actual.Local.Equal(expected.Local) { t.Fatal("Gretap.Local doesn't match") } if expected.Remote != nil && !actual.Remote.Equal(expected.Remote) { t.Fatal("Gretap.Remote doesn't match") } if actual.IFlags != expected.IFlags { t.Fatal("Gretap.IFlags doesn't match") } if actual.OFlags != expected.OFlags { t.Fatal("Gretap.OFlags doesn't match") } if actual.PMtuDisc != expected.PMtuDisc { t.Fatal("Gretap.PMtuDisc doesn't match") } if actual.Ttl != expected.Ttl { t.Fatal("Gretap.Ttl doesn't match") } if actual.Tos != expected.Tos { t.Fatal("Gretap.Tos doesn't match") } if actual.EncapType != expected.EncapType { t.Fatal("Gretap.EncapType doesn't match") } if actual.EncapFlags != expected.EncapFlags { t.Fatal("Gretap.EncapFlags doesn't match") } if actual.Link != expected.Link { t.Fatal("Gretap.Link doesn't match") } if actual.FlowBased != expected.FlowBased { t.Fatal("Gretap.FlowBased doesn't match") } } func compareGretun(t *testing.T, expected, actual *Gretun) { if actual.Link != expected.Link { t.Fatal("Gretun.Link doesn't match") } if actual.IFlags != expected.IFlags { t.Fatal("Gretun.IFlags doesn't match") } if actual.OFlags != expected.OFlags { t.Fatal("Gretun.OFlags doesn't match") } if actual.IKey != expected.IKey { t.Fatal("Gretun.IKey doesn't match") } if actual.OKey != expected.OKey { t.Fatal("Gretun.OKey doesn't match") } if expected.Local != nil && !actual.Local.Equal(expected.Local) { t.Fatal("Gretun.Local doesn't match") } if expected.Remote != nil && !actual.Remote.Equal(expected.Remote) { t.Fatal("Gretun.Remote doesn't match") } if actual.Ttl != expected.Ttl { t.Fatal("Gretun.Ttl doesn't match") } if actual.Tos != expected.Tos { t.Fatal("Gretun.Tos doesn't match") } if actual.PMtuDisc != expected.PMtuDisc { t.Fatal("Gretun.PMtuDisc doesn't match") } if actual.EncapType != expected.EncapType { t.Fatal("Gretun.EncapType doesn't match") } if actual.EncapFlags != expected.EncapFlags { t.Fatal("Gretun.EncapFlags doesn't match") } if actual.EncapSport != expected.EncapSport { t.Fatal("Gretun.EncapSport doesn't match") } if actual.EncapDport != expected.EncapDport { t.Fatal("Gretun.EncapDport doesn't match") } if actual.FlowBased != expected.FlowBased { t.Fatal("Gretun.FlowBased doesn't match") } } func compareVxlan(t *testing.T, expected, actual *Vxlan) { if actual.VxlanId != expected.VxlanId { t.Fatal("Vxlan.VxlanId doesn't match") } if expected.SrcAddr != nil && !actual.SrcAddr.Equal(expected.SrcAddr) { t.Fatal("Vxlan.SrcAddr doesn't match") } if expected.Group != nil && !actual.Group.Equal(expected.Group) { t.Fatal("Vxlan.Group doesn't match") } if expected.TTL != -1 && actual.TTL != expected.TTL { t.Fatal("Vxlan.TTL doesn't match") } if expected.TOS != -1 && actual.TOS != expected.TOS { t.Fatal("Vxlan.TOS doesn't match") } if actual.Learning != expected.Learning { t.Fatal("Vxlan.Learning doesn't match") } if actual.Proxy != expected.Proxy { t.Fatal("Vxlan.Proxy doesn't match") } if actual.RSC != expected.RSC { t.Fatal("Vxlan.RSC doesn't match") } if actual.L2miss != expected.L2miss { t.Fatal("Vxlan.L2miss doesn't match") } if actual.L3miss != expected.L3miss { t.Fatal("Vxlan.L3miss doesn't match") } if actual.GBP != expected.GBP { t.Fatal("Vxlan.GBP doesn't match") } if actual.FlowBased != expected.FlowBased { t.Fatal("Vxlan.FlowBased doesn't match") } if actual.UDP6ZeroCSumTx != expected.UDP6ZeroCSumTx { t.Fatal("Vxlan.UDP6ZeroCSumTx doesn't match") } if actual.UDP6ZeroCSumRx != expected.UDP6ZeroCSumRx { t.Fatal("Vxlan.UDP6ZeroCSumRx doesn't match") } if expected.NoAge { if !actual.NoAge { t.Fatal("Vxlan.NoAge doesn't match") } } else if expected.Age > 0 && actual.Age != expected.Age { t.Fatal("Vxlan.Age doesn't match") } if expected.Limit > 0 && actual.Limit != expected.Limit { t.Fatal("Vxlan.Limit doesn't match") } if expected.Port > 0 && actual.Port != expected.Port { t.Fatal("Vxlan.Port doesn't match") } if expected.PortLow > 0 || expected.PortHigh > 0 { if actual.PortLow != expected.PortLow { t.Fatal("Vxlan.PortLow doesn't match") } if actual.PortHigh != expected.PortHigh { t.Fatal("Vxlan.PortHigh doesn't match") } } } func compareXfrmi(t *testing.T, expected, actual *Xfrmi) { if expected.Ifid != actual.Ifid { t.Fatal("Xfrmi.Ifid doesn't match") } } func compareTuntap(t *testing.T, expected, actual *Tuntap) { if expected.Mode != actual.Mode { t.Fatalf("Tuntap.Mode doesn't match: expected : %+v, got %+v", expected.Mode, actual.Mode) } if expected.Owner != actual.Owner { t.Fatal("Tuntap.Owner doesn't match") } if expected.Group != actual.Group { t.Fatal("Tuntap.Group doesn't match") } if expected.NonPersist != actual.NonPersist { t.Fatal("Tuntap.Group doesn't match") } } func compareBareUDP(t *testing.T, expected, actual *BareUDP) { // set the Port to 6635 (the linux default) if it wasn't specified at creation if expected.Port == 0 { expected.Port = 6635 } if actual.Port != expected.Port { t.Fatalf("BareUDP.Port doesn't match: %d %d", actual.Port, expected.Port) } if actual.EtherType != expected.EtherType { t.Fatalf("BareUDP.EtherType doesn't match: %x %x", actual.EtherType, expected.EtherType) } if actual.SrcPortMin != expected.SrcPortMin { t.Fatalf("BareUDP.SrcPortMin doesn't match: %d %d", actual.SrcPortMin, expected.SrcPortMin) } if actual.MultiProto != expected.MultiProto { t.Fatal("BareUDP.MultiProto doesn't match") } } func TestLinkAddDelWithIndex(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Dummy{LinkAttrs{Index: 1000, Name: "foo"}}) } func TestLinkAddDelDummy(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Dummy{LinkAttrs{Name: "foo"}}) } func TestLinkAddDelDummyWithGroup(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Dummy{LinkAttrs{Name: "foo", Group: 42}}) } func TestLinkModify(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() linkName := "foo" originalMTU := 1500 updatedMTU := 1442 link := &Dummy{LinkAttrs{Name: linkName, MTU: originalMTU}} base := link.Attrs() if err := LinkAdd(link); err != nil { t.Fatal(err) } link.MTU = updatedMTU if err := LinkModify(link); err != nil { t.Fatal(err) } result, err := LinkByName(linkName) if err != nil { t.Fatal(err) } rBase := result.Attrs() if rBase.MTU != updatedMTU { t.Fatalf("MTU is %d, should be %d", rBase.MTU, base.MTU) } } func TestLinkAddDelIfb(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Ifb{LinkAttrs{Name: "foo"}}) } func TestLinkAddDelBridge(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Bridge{LinkAttrs: LinkAttrs{Name: "foo", MTU: 1400}}) } func TestLinkAddDelGeneve(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Geneve{ LinkAttrs: LinkAttrs{Name: "foo4", EncapType: "geneve"}, ID: 0x1000, Remote: net.IPv4(127, 0, 0, 1)}) testLinkAddDel(t, &Geneve{ LinkAttrs: LinkAttrs{Name: "foo6", EncapType: "geneve"}, ID: 0x1000, Remote: net.ParseIP("2001:db8:ef33::2")}) } func TestLinkAddDelGeneveFlowBased(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Geneve{ LinkAttrs: LinkAttrs{Name: "foo"}, Dport: 1234, FlowBased: true}) } func TestGeneveCompareToIP(t *testing.T) { ns, tearDown := setUpNamedNetlinkTest(t) defer tearDown() expected := &Geneve{ ID: 0x764332, // 23 bits Remote: net.ParseIP("1.2.3.4"), Dport: 6081, } // Create interface cmd := exec.Command("ip", "netns", "exec", ns, "ip", "link", "add", "gen0", "type", "geneve", "vni", fmt.Sprint(expected.ID), "remote", expected.Remote.String(), // TODO: unit tests are currently done on ubuntu 16, and the version of iproute2 there doesn't support dstport // We can still do most of the testing by verifying that we do read the default port // "dstport", fmt.Sprint(expected.Dport), ) out := &bytes.Buffer{} cmd.Stdout = out cmd.Stderr = out if rc := cmd.Run(); rc != nil { t.Fatal("failed creating link:", rc, out.String()) } link, err := LinkByName("gen0") if err != nil { t.Fatal("Failed getting link: ", err) } actual, ok := link.(*Geneve) if !ok { t.Fatalf("resulted interface is not geneve: %T", link) } compareGeneve(t, expected, actual) } func TestLinkAddDelGretap(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Gretap{ LinkAttrs: LinkAttrs{Name: "foo4"}, IKey: 0x101, OKey: 0x101, PMtuDisc: 1, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) testLinkAddDel(t, &Gretap{ LinkAttrs: LinkAttrs{Name: "foo6"}, IKey: 0x101, OKey: 0x101, Local: net.ParseIP("2001:db8:abcd::1"), Remote: net.ParseIP("2001:db8:ef33::2")}) } func TestLinkAddDelGretun(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Gretun{ LinkAttrs: LinkAttrs{Name: "foo4"}, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) testLinkAddDel(t, &Gretun{ LinkAttrs: LinkAttrs{Name: "foo6"}, Local: net.ParseIP("2001:db8:abcd::1"), Remote: net.ParseIP("2001:db8:ef33::2")}) } func TestLinkAddDelGretunPointToMultiPoint(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Gretun{ LinkAttrs: LinkAttrs{Name: "foo"}, Local: net.IPv4(127, 0, 0, 1), IKey: 1234, OKey: 1234}) testLinkAddDel(t, &Gretun{ LinkAttrs: LinkAttrs{Name: "foo6"}, Local: net.ParseIP("2001:db8:1234::4"), IKey: 5678, OKey: 7890}) } func TestLinkAddDelGretunFlowBased(t *testing.T) { minKernelRequired(t, 4, 3) tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Gretun{ LinkAttrs: LinkAttrs{Name: "foo"}, FlowBased: true}) } func TestLinkAddDelGretapFlowBased(t *testing.T) { minKernelRequired(t, 4, 3) tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Gretap{ LinkAttrs: LinkAttrs{Name: "foo"}, FlowBased: true}) } func TestLinkAddDelVlan(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } testLinkAddDel(t, &Vlan{LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, 900, VLAN_PROTOCOL_8021Q}) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkAddDelMacvlan(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } testLinkAddDel(t, &Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_PRIVATE, }) testLinkAddDel(t, &Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_BRIDGE, }) testLinkAddDel(t, &Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_VEPA, }) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkAddDelMacvtap(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } testLinkAddDel(t, &Macvtap{ Macvlan: Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_PRIVATE, }, }) testLinkAddDel(t, &Macvtap{ Macvlan: Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_BRIDGE, }, }) testLinkAddDel(t, &Macvtap{ Macvlan: Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_VEPA, }, }) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkMacvBCQueueLen(t *testing.T) { minKernelRequired(t, 5, 11) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } testLinkAddDel(t, &Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_PRIVATE, BCQueueLen: 10000, }) testLinkAddDel(t, &Macvtap{ Macvlan: Macvlan{ LinkAttrs: LinkAttrs{Name: "bar", ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_PRIVATE, BCQueueLen: 10000, }, }) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestNetkitPeerNs(t *testing.T) { minKernelRequired(t, 6, 7) tearDown := setUpNetlinkTest(t) defer tearDown() basens, err := netns.Get() if err != nil { t.Fatal("Failed to get basens") } defer basens.Close() nsOne, err := netns.New() if err != nil { t.Fatal("Failed to create nsOne") } defer nsOne.Close() nsTwo, err := netns.New() if err != nil { t.Fatal("Failed to create nsTwo") } defer nsTwo.Close() netkit := &Netkit{ LinkAttrs: LinkAttrs{ Name: "foo", Namespace: NsFd(basens), }, Mode: NETKIT_MODE_L2, Policy: NETKIT_POLICY_FORWARD, PeerPolicy: NETKIT_POLICY_BLACKHOLE, } peerAttr := &LinkAttrs{ Name: "bar", Namespace: NsFd(nsOne), } netkit.SetPeerAttrs(peerAttr) if err := LinkAdd(netkit); err != nil { t.Fatal(err) } _, err = LinkByName("bar") if err == nil { t.Fatal("netkit link bar is in nsTwo") } _, err = LinkByName("foo") if err == nil { t.Fatal("netkit link foo is in nsTwo") } err = netns.Set(basens) if err != nil { t.Fatal("Failed to set basens") } _, err = LinkByName("foo") if err != nil { t.Fatal("netkit link foo is not in basens") } err = netns.Set(nsOne) if err != nil { t.Fatal("Failed to set nsOne") } _, err = LinkByName("bar") if err != nil { t.Fatal("netkit link bar is not in nsOne") } } func TestLinkAddDelNetkit(t *testing.T) { minKernelRequired(t, 6, 7) tearDown := setUpNetlinkTest(t) defer tearDown() netkit := &Netkit{ LinkAttrs: LinkAttrs{ Name: "foo", }, Mode: NETKIT_MODE_L2, Policy: NETKIT_POLICY_FORWARD, PeerPolicy: NETKIT_POLICY_BLACKHOLE, } peerAttr := &LinkAttrs{ Name: "bar", } netkit.SetPeerAttrs(peerAttr) testLinkAddDel(t, netkit) } func TestLinkAddDelVeth(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() peerMAC, _ := net.ParseMAC("00:12:34:56:78:02") veth := &Veth{ LinkAttrs: LinkAttrs{ Name: "foo", TxQLen: testTxQLen, MTU: 1400, NumTxQueues: testTxQueues, NumRxQueues: testRxQueues, }, PeerName: "bar", PeerHardwareAddr: peerMAC, } testLinkAddDel(t, veth) } func TestLinkAddDelBond(t *testing.T) { minKernelRequired(t, 3, 13) tearDown := setUpNetlinkTest(t) defer tearDown() modes := []string{"802.3ad", "balance-tlb"} for _, mode := range modes { bond := NewLinkBond(LinkAttrs{Name: "foo"}) bond.Mode = StringToBondModeMap[mode] switch mode { case "802.3ad": bond.AdSelect = BondAdSelect(BOND_AD_SELECT_BANDWIDTH) bond.AdActorSysPrio = 1 bond.AdUserPortKey = 1 bond.AdActorSystem, _ = net.ParseMAC("06:aa:bb:cc:dd:ee") bond.ArpIpTargets = []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("1.1.1.2")} case "balance-tlb": bond.TlbDynamicLb = 1 bond.ArpIpTargets = []net.IP{net.ParseIP("1.1.1.2"), net.ParseIP("1.1.1.1")} } testLinkAddDel(t, bond) } } func TestLinkAddVethWithDefaultTxQLen(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() la := NewLinkAttrs() la.Name = "foo" veth := &Veth{LinkAttrs: la, PeerName: "bar"} if err := LinkAdd(veth); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if veth, ok := link.(*Veth); !ok { t.Fatalf("unexpected link type: %T", link) } else { if veth.TxQLen != defaultTxQLen { t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, defaultTxQLen) } } peer, err := LinkByName("bar") if err != nil { t.Fatal(err) } if veth, ok := peer.(*Veth); !ok { t.Fatalf("unexpected link type: %T", link) } else { if veth.TxQLen != defaultTxQLen { t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, defaultTxQLen) } } } func TestLinkAddVethWithZeroTxQLen(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() la := NewLinkAttrs() la.Name = "foo" la.TxQLen = 0 veth := &Veth{LinkAttrs: la, PeerName: "bar"} if err := LinkAdd(veth); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if veth, ok := link.(*Veth); !ok { t.Fatalf("unexpected link type: %T", link) } else { if veth.TxQLen != 0 { t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, 0) } } peer, err := LinkByName("bar") if err != nil { t.Fatal(err) } if veth, ok := peer.(*Veth); !ok { t.Fatalf("unexpected link type: %T", link) } else { if veth.TxQLen != 0 { t.Fatalf("TxQLen is %d, should be %d", veth.TxQLen, 0) } } } func TestLinkAddDelDummyWithGSO(t *testing.T) { const ( gsoMaxSegs = 16 gsoMaxSize = 1 << 14 ) minKernelRequired(t, 4, 16) tearDown := setUpNetlinkTest(t) defer tearDown() dummy := &Dummy{LinkAttrs: LinkAttrs{Name: "foo", GSOMaxSize: gsoMaxSize, GSOMaxSegs: gsoMaxSegs}} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } dummy, ok := link.(*Dummy) if !ok { t.Fatalf("unexpected link type: %T", link) } if dummy.GSOMaxSize != gsoMaxSize { t.Fatalf("GSOMaxSize is %d, should be %d", dummy.GSOMaxSize, gsoMaxSize) } if dummy.GSOMaxSegs != gsoMaxSegs { t.Fatalf("GSOMaxSeg is %d, should be %d", dummy.GSOMaxSegs, gsoMaxSegs) } } func TestLinkAddDelDummyWithGRO(t *testing.T) { const ( groMaxSize = 1 << 14 ) minKernelRequired(t, 5, 19) tearDown := setUpNetlinkTest(t) defer tearDown() dummy := &Dummy{LinkAttrs: LinkAttrs{Name: "foo", GROMaxSize: groMaxSize}} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } dummy, ok := link.(*Dummy) if !ok { t.Fatalf("unexpected link type: %T", link) } if dummy.GROMaxSize != groMaxSize { t.Fatalf("GROMaxSize is %d, should be %d", dummy.GROMaxSize, groMaxSize) } } func TestLinkAddDummyWithTxQLen(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() la := NewLinkAttrs() la.Name = "foo" la.TxQLen = 1500 dummy := &Dummy{LinkAttrs: la} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if dummy, ok := link.(*Dummy); !ok { t.Fatalf("unexpected link type: %T", link) } else { if dummy.TxQLen != 1500 { t.Fatalf("TxQLen is %d, should be %d", dummy.TxQLen, 1500) } } } func TestLinkAddDelBridgeMaster(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() master := &Bridge{LinkAttrs: LinkAttrs{Name: "foo"}} if err := LinkAdd(master); err != nil { t.Fatal(err) } testLinkAddDel(t, &Dummy{LinkAttrs{Name: "bar", MasterIndex: master.Attrs().Index}}) if err := LinkDel(master); err != nil { t.Fatal(err) } } func testLinkSetUnsetResetMaster(t *testing.T, master, newmaster Link) { slave := &Dummy{LinkAttrs{Name: "baz"}} if err := LinkAdd(slave); err != nil { t.Fatal(err) } nonexistsmaster := &Bridge{LinkAttrs: LinkAttrs{Name: "foobar"}} if err := LinkSetMaster(slave, nonexistsmaster); err == nil { t.Fatal("error expected") } if err := LinkSetMaster(slave, master); err != nil { t.Fatal(err) } link, err := LinkByName("baz") if err != nil { t.Fatal(err) } if link.Attrs().MasterIndex != master.Attrs().Index { t.Fatal("Master not set properly") } if err := LinkSetMaster(slave, newmaster); err != nil { t.Fatal(err) } link, err = LinkByName("baz") if err != nil { t.Fatal(err) } if link.Attrs().MasterIndex != newmaster.Attrs().Index { t.Fatal("Master not reset properly") } if err := LinkSetNoMaster(slave); err != nil { t.Fatal(err) } link, err = LinkByName("baz") if err != nil { t.Fatal(err) } if link.Attrs().MasterIndex != 0 { t.Fatal("Master not unset properly") } if err := LinkDel(slave); err != nil { t.Fatal(err) } } func TestLinkSetUnsetResetMaster(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() master := &Bridge{LinkAttrs: LinkAttrs{Name: "foo"}} if err := LinkAdd(master); err != nil { t.Fatal(err) } newmaster := &Bridge{LinkAttrs: LinkAttrs{Name: "bar"}} if err := LinkAdd(newmaster); err != nil { t.Fatal(err) } testLinkSetUnsetResetMaster(t, master, newmaster) if err := LinkDel(newmaster); err != nil { t.Fatal(err) } if err := LinkDel(master); err != nil { t.Fatal(err) } } func TestLinkSetUnsetResetMasterBond(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() master := NewLinkBond(LinkAttrs{Name: "foo"}) master.Mode = BOND_MODE_BALANCE_RR if err := LinkAdd(master); err != nil { t.Fatal(err) } newmaster := NewLinkBond(LinkAttrs{Name: "bar"}) newmaster.Mode = BOND_MODE_BALANCE_RR if err := LinkAdd(newmaster); err != nil { t.Fatal(err) } testLinkSetUnsetResetMaster(t, master, newmaster) if err := LinkDel(newmaster); err != nil { t.Fatal(err) } if err := LinkDel(master); err != nil { t.Fatal(err) } } func TestLinkSetNs(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() basens, err := netns.Get() if err != nil { t.Fatal("Failed to get basens") } defer basens.Close() newns, err := netns.New() if err != nil { t.Fatal("Failed to create newns") } defer newns.Close() link := &Veth{LinkAttrs{Name: "foo"}, "bar", nil, nil} if err := LinkAdd(link); err != nil { t.Fatal(err) } peer, err := LinkByName("bar") if err != nil { t.Fatal(err) } LinkSetNsFd(peer, int(basens)) if err != nil { t.Fatal("Failed to set newns for link") } _, err = LinkByName("bar") if err == nil { t.Fatal("Link bar is still in newns") } err = netns.Set(basens) if err != nil { t.Fatal("Failed to set basens") } peer, err = LinkByName("bar") if err != nil { t.Fatal("Link is not in basens") } if err := LinkDel(peer); err != nil { t.Fatal(err) } err = netns.Set(newns) if err != nil { t.Fatal("Failed to set newns") } _, err = LinkByName("foo") if err == nil { t.Fatal("Other half of veth pair not deleted") } } func TestLinkAddDelWireguard(t *testing.T) { minKernelRequired(t, 5, 6) tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Wireguard{LinkAttrs: LinkAttrs{Name: "wg0"}}) } func TestVethPeerNs(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() basens, err := netns.Get() if err != nil { t.Fatal("Failed to get basens") } defer basens.Close() newns, err := netns.New() if err != nil { t.Fatal("Failed to create newns") } defer newns.Close() link := &Veth{LinkAttrs{Name: "foo"}, "bar", nil, NsFd(basens)} if err := LinkAdd(link); err != nil { t.Fatal(err) } _, err = LinkByName("bar") if err == nil { t.Fatal("Link bar is in newns") } err = netns.Set(basens) if err != nil { t.Fatal("Failed to set basens") } _, err = LinkByName("bar") if err != nil { t.Fatal("Link bar is not in basens") } err = netns.Set(newns) if err != nil { t.Fatal("Failed to set newns") } _, err = LinkByName("foo") if err != nil { t.Fatal("Link foo is not in newns") } } func TestVethPeerNs2(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() basens, err := netns.Get() if err != nil { t.Fatal("Failed to get basens") } defer basens.Close() onens, err := netns.New() if err != nil { t.Fatal("Failed to create newns") } defer onens.Close() twons, err := netns.New() if err != nil { t.Fatal("Failed to create twons") } defer twons.Close() link := &Veth{LinkAttrs{Name: "foo", Namespace: NsFd(onens)}, "bar", nil, NsFd(basens)} if err := LinkAdd(link); err != nil { t.Fatal(err) } _, err = LinkByName("foo") if err == nil { t.Fatal("Link foo is in twons") } _, err = LinkByName("bar") if err == nil { t.Fatal("Link bar is in twons") } err = netns.Set(basens) if err != nil { t.Fatal("Failed to set basens") } _, err = LinkByName("bar") if err != nil { t.Fatal("Link bar is not in basens") } err = netns.Set(onens) if err != nil { t.Fatal("Failed to set onens") } _, err = LinkByName("foo") if err != nil { t.Fatal("Link foo is not in onens") } } func TestLinkAddDelVxlan(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{ LinkAttrs{Name: "foo"}, } if err := LinkAdd(parent); err != nil { t.Fatal(err) } vxlan := Vxlan{ LinkAttrs: LinkAttrs{ Name: "bar", }, VxlanId: 10, VtepDevIndex: parent.Index, Learning: true, L2miss: true, L3miss: true, } testLinkAddDel(t, &vxlan) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkAddDelVxlanUdpCSum6(t *testing.T) { minKernelRequired(t, 3, 16) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{ LinkAttrs{Name: "foo"}, } if err := LinkAdd(parent); err != nil { t.Fatal(err) } vxlan := Vxlan{ LinkAttrs: LinkAttrs{ Name: "bar", }, VxlanId: 10, VtepDevIndex: parent.Index, Learning: true, L2miss: true, L3miss: true, UDP6ZeroCSumTx: true, UDP6ZeroCSumRx: true, } testLinkAddDel(t, &vxlan) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkAddDelVxlanGbp(t *testing.T) { minKernelRequired(t, 4, 0) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{ LinkAttrs{Name: "foo"}, } if err := LinkAdd(parent); err != nil { t.Fatal(err) } vxlan := Vxlan{ LinkAttrs: LinkAttrs{ Name: "bar", }, VxlanId: 10, VtepDevIndex: parent.Index, Learning: true, L2miss: true, L3miss: true, UDP6ZeroCSumTx: true, UDP6ZeroCSumRx: true, GBP: true, } testLinkAddDel(t, &vxlan) if err := LinkDel(parent); err != nil { t.Fatal(err) } } func TestLinkAddDelVxlanFlowBased(t *testing.T) { minKernelRequired(t, 4, 3) tearDown := setUpNetlinkTest(t) defer tearDown() vxlan := Vxlan{ LinkAttrs: LinkAttrs{ Name: "foo", }, Learning: false, FlowBased: true, } testLinkAddDel(t, &vxlan) } func TestLinkAddDelBareUDP(t *testing.T) { minKernelRequired(t, 5, 1) setUpNetlinkTestWithKModule(t, "bareudp") tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &BareUDP{ LinkAttrs: LinkAttrs{Name: "foo99"}, Port: 6635, EtherType: syscall.ETH_P_MPLS_UC, SrcPortMin: 12345, MultiProto: true, }) testLinkAddDel(t, &BareUDP{ LinkAttrs: LinkAttrs{Name: "foo100"}, Port: 6635, EtherType: syscall.ETH_P_IP, SrcPortMin: 12345, MultiProto: true, }) } func TestBareUDPCompareToIP(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI due to old iproute2") } // requires iproute2 >= 5.10 minKernelRequired(t, 5, 9) setUpNetlinkTestWithKModule(t, "bareudp") ns, tearDown := setUpNamedNetlinkTest(t) defer tearDown() expected := &BareUDP{ Port: uint16(6635), EtherType: syscall.ETH_P_MPLS_UC, SrcPortMin: 12345, MultiProto: true, } // Create interface cmd := exec.Command("ip", "netns", "exec", ns, "ip", "link", "add", "b0", "type", "bareudp", "dstport", fmt.Sprint(expected.Port), "ethertype", "mpls_uc", "srcportmin", fmt.Sprint(expected.SrcPortMin), "multiproto", ) out := &bytes.Buffer{} cmd.Stdout = out cmd.Stderr = out if rc := cmd.Run(); rc != nil { t.Fatal("failed creating link:", rc, out.String()) } link, err := LinkByName("b0") if err != nil { t.Fatal("Failed getting link: ", err) } actual, ok := link.(*BareUDP) if !ok { t.Fatalf("resulted interface is not BareUDP: %T", link) } compareBareUDP(t, expected, actual) } func TestLinkAddDelIPVlanL2(t *testing.T) { minKernelRequired(t, 4, 2) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } ipv := IPVlan{ LinkAttrs: LinkAttrs{ Name: "bar", ParentIndex: parent.Index, }, Mode: IPVLAN_MODE_L2, } testLinkAddDel(t, &ipv) } func TestLinkAddDelIPVlanL3(t *testing.T) { minKernelRequired(t, 4, 2) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } ipv := IPVlan{ LinkAttrs: LinkAttrs{ Name: "bar", ParentIndex: parent.Index, }, Mode: IPVLAN_MODE_L3, } testLinkAddDel(t, &ipv) } func TestLinkAddDelIPVlanVepa(t *testing.T) { minKernelRequired(t, 4, 15) tearDown := setUpNetlinkTest(t) defer tearDown() parent := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } ipv := IPVlan{ LinkAttrs: LinkAttrs{ Name: "bar", ParentIndex: parent.Index, }, Mode: IPVLAN_MODE_L3, Flag: IPVLAN_FLAG_VEPA, } testLinkAddDel(t, &ipv) } func TestLinkAddDelIPVlanNoParent(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ipv := IPVlan{ LinkAttrs: LinkAttrs{ Name: "bar", }, Mode: IPVLAN_MODE_L3, } err := LinkAdd(&ipv) if err == nil { t.Fatal("Add should fail if ipvlan creating without ParentIndex") } if err.Error() != "Can't create ipvlan link without ParentIndex" { t.Fatalf("Error should be about missing ParentIndex, got %q", err) } } func TestLinkByIndex(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := &Dummy{LinkAttrs{Name: "dummy"}} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } found, err := LinkByIndex(dummy.Index) if err != nil { t.Fatal(err) } if found.Attrs().Index != dummy.Attrs().Index { t.Fatalf("Indices don't match: %v != %v", found.Attrs().Index, dummy.Attrs().Index) } LinkDel(dummy) // test not found _, err = LinkByIndex(dummy.Attrs().Index) if err == nil { t.Fatalf("LinkByIndex(%v) found deleted link", err) } } func TestLinkSet(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Dummy{LinkAttrs{Name: "foo"}} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetName(link, "bar") if err != nil { t.Fatalf("Could not change interface name: %v", err) } link, err = LinkByName("bar") if err != nil { t.Fatalf("Interface name not changed: %v", err) } err = LinkSetMTU(link, 1400) if err != nil { t.Fatalf("Could not set MTU: %v", err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } if link.Attrs().MTU != 1400 { t.Fatal("MTU not changed") } err = LinkSetTxQLen(link, 500) if err != nil { t.Fatalf("Could not set txqlen: %v", err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } if link.Attrs().TxQLen != 500 { t.Fatal("txqlen not changed") } addr, err := net.ParseMAC("00:12:34:56:78:AB") if err != nil { t.Fatal(err) } err = LinkSetHardwareAddr(link, addr) if err != nil { t.Fatal(err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } if !bytes.Equal(link.Attrs().HardwareAddr, addr) { t.Fatalf("hardware address not changed") } err = LinkSetAlias(link, "barAlias") if err != nil { t.Fatalf("Could not set alias: %v", err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } if link.Attrs().Alias != "barAlias" { t.Fatalf("alias not changed") } link, err = LinkByAlias("barAlias") if err != nil { t.Fatal(err) } err = LinkSetGroup(link, 42) if err != nil { t.Fatalf("Could not set group: %v", err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } if link.Attrs().Group != 42 { t.Fatal("Link group not changed") } } func TestLinkAltName(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Dummy{LinkAttrs{Name: "bar"}} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("bar") if err != nil { t.Fatal(err) } altNames := []string{"altname", "altname2", "some_longer_altname"} sort.Strings(altNames) altNamesStr := strings.Join(altNames, ",") for _, altname := range altNames { err = LinkAddAltName(link, altname) if err != nil { t.Fatalf("Could not add %s: %v", altname, err) } } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } sort.Strings(link.Attrs().AltNames) linkAltNamesStr := strings.Join(link.Attrs().AltNames, ",") if altNamesStr != linkAltNamesStr { t.Fatalf("Expected %s AltNames, got %s", altNamesStr, linkAltNamesStr) } for _, altname := range altNames { link, err = LinkByName(altname) if err != nil { t.Fatal(err) } } for idx, altName := range altNames { err = LinkDelAltName(link, altName) if err != nil { t.Fatalf("Could not delete %s: %v", altName, err) } link, err = LinkByName("bar") if err != nil { t.Fatal(err) } sort.Strings(link.Attrs().AltNames) linkAltNamesStr := strings.Join(link.Attrs().AltNames, ",") altNamesStr := strings.Join(altNames[idx+1:], ",") if linkAltNamesStr != altNamesStr { t.Fatalf("Expected %s AltNames, got %s", altNamesStr, linkAltNamesStr) } } } func TestLinkSetARP(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "banana"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetARPOff(link) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().RawFlags&unix.IFF_NOARP != uint32(unix.IFF_NOARP) { t.Fatalf("NOARP was not set") } err = LinkSetARPOn(link) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().RawFlags&unix.IFF_NOARP != 0 { t.Fatalf("NOARP is still set") } } func expectLinkUpdate(ch <-chan LinkUpdate, ifaceName string, up bool) bool { for { timeout := time.After(time.Minute) select { case update := <-ch: if ifaceName == update.Link.Attrs().Name && (update.IfInfomsg.Flags&unix.IFF_UP != 0) == up { return true } case <-timeout: return false } } } func TestLinkSubscribe(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan LinkUpdate) done := make(chan struct{}) defer close(done) if err := LinkSubscribe(ch, done); err != nil { t.Fatal(err) } link := &Veth{LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1400}, "bar", nil, nil} if err := LinkAdd(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "foo", false) { t.Fatal("Add update not received as expected") } if err := LinkSetUp(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "foo", true) { t.Fatal("Link Up update not received as expected") } if err := LinkDel(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "foo", false) { t.Fatal("Del update not received as expected") } } func TestLinkSubscribeWithOptions(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan LinkUpdate) done := make(chan struct{}) defer close(done) var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := LinkSubscribeWithOptions(ch, done, LinkSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, }); err != nil { t.Fatal(err) } link := &Veth{LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1400}, "bar", nil, nil} if err := LinkAdd(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "foo", false) { t.Fatal("Add update not received as expected") } } func TestLinkSubscribeAt(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() // Subscribe for Link events on the custom netns ch := make(chan LinkUpdate) done := make(chan struct{}) defer close(done) if err := LinkSubscribeAt(newNs, ch, done); err != nil { t.Fatal(err) } link := &Veth{LinkAttrs{Name: "test", TxQLen: testTxQLen, MTU: 1400}, "bar", nil, nil} if err := nh.LinkAdd(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", false) { t.Fatal("Add update not received as expected") } if err := nh.LinkSetUp(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", true) { t.Fatal("Link Up update not received as expected") } if err := nh.LinkDel(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", false) { t.Fatal("Del update not received as expected") } } func TestLinkSubscribeListExisting(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() link := &Veth{LinkAttrs{Name: "test", TxQLen: testTxQLen, MTU: 1400}, "bar", nil, nil} if err := nh.LinkAdd(link); err != nil { t.Fatal(err) } // Subscribe for Link events on the custom netns ch := make(chan LinkUpdate) done := make(chan struct{}) defer close(done) if err := LinkSubscribeWithOptions(ch, done, LinkSubscribeOptions{ Namespace: &newNs, ListExisting: true}, ); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", false) { t.Fatal("Add update not received as expected") } if err := nh.LinkSetUp(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", true) { t.Fatal("Link Up update not received as expected") } if err := nh.LinkDel(link); err != nil { t.Fatal(err) } if !expectLinkUpdate(ch, "test", false) { t.Fatal("Del update not received as expected") } } func TestLinkStats(t *testing.T) { defer setUpNetlinkTest(t)() // Create a veth pair and verify the cross-stats once both // ends are brought up and some ICMPv6 packets are exchanged v0 := "v0" v1 := "v1" vethLink := &Veth{LinkAttrs: LinkAttrs{Name: v0}, PeerName: v1} if err := LinkAdd(vethLink); err != nil { t.Fatal(err) } veth0, err := LinkByName(v0) if err != nil { t.Fatal(err) } if err := LinkSetUp(veth0); err != nil { t.Fatal(err) } veth1, err := LinkByName(v1) if err != nil { t.Fatal(err) } if err := LinkSetUp(veth1); err != nil { t.Fatal(err) } time.Sleep(2 * time.Second) // verify statistics veth0, err = LinkByName(v0) if err != nil { t.Fatal(err) } veth1, err = LinkByName(v1) if err != nil { t.Fatal(err) } v0Stats := veth0.Attrs().Statistics v1Stats := veth1.Attrs().Statistics if v0Stats.RxPackets != v1Stats.TxPackets || v0Stats.TxPackets != v1Stats.RxPackets || v0Stats.RxBytes != v1Stats.TxBytes || v0Stats.TxBytes != v1Stats.RxBytes { t.Fatalf("veth ends counters differ:\n%v\n%v", v0Stats, v1Stats) } } func TestLinkXdp(t *testing.T) { links, err := LinkList() if err != nil { t.Fatal(err) } var testXdpLink Link for _, link := range links { if link.Attrs().Xdp != nil && !link.Attrs().Xdp.Attached { testXdpLink = link break } } if testXdpLink == nil { t.Skipf("No link supporting XDP found") } fd, err := loadSimpleBpf(BPF_PROG_TYPE_XDP, 2 /*XDP_PASS*/) if err != nil { t.Skipf("Loading bpf program failed: %s", err) } if err := LinkSetXdpFd(testXdpLink, fd); err != nil { t.Fatal(err) } if err := LinkSetXdpFdWithFlags(testXdpLink, fd, nl.XDP_FLAGS_UPDATE_IF_NOEXIST); !errors.Is(err, unix.EBUSY) { t.Fatal(err) } if err := LinkSetXdpFd(testXdpLink, -1); err != nil { t.Fatal(err) } } func TestLinkAddDelIptun(t *testing.T) { minKernelRequired(t, 4, 9) tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Iptun{ LinkAttrs: LinkAttrs{Name: "iptunfoo"}, PMtuDisc: 1, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) } func TestLinkAddDelIptunFlowBased(t *testing.T) { minKernelRequired(t, 4, 9) tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Iptun{ LinkAttrs: LinkAttrs{Name: "iptunflowfoo"}, FlowBased: true, }) } func TestLinkAddDelIp6tnl(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Ip6tnl{ LinkAttrs: LinkAttrs{Name: "ip6tnltest"}, Local: net.ParseIP("2001:db8::100"), Remote: net.ParseIP("2001:db8::200"), }) } func TestLinkAddDelIp6tnlFlowbased(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Ip6tnl{ LinkAttrs: LinkAttrs{Name: "ip6tnltest"}, FlowBased: true, }) } func TestLinkAddDelSittun(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Sittun{ LinkAttrs: LinkAttrs{Name: "sittunfoo"}, PMtuDisc: 1, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) } func TestLinkAddDelVti(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() testLinkAddDel(t, &Vti{ LinkAttrs: LinkAttrs{Name: "vtifoo"}, IKey: 0x101, OKey: 0x101, Local: net.IPv4(127, 0, 0, 1), Remote: net.IPv4(127, 0, 0, 1)}) testLinkAddDel(t, &Vti{ LinkAttrs: LinkAttrs{Name: "vtibar"}, IKey: 0x101, OKey: 0x101, Local: net.IPv6loopback, Remote: net.IPv6loopback}) } func TestLinkSetGSOMaxSize(t *testing.T) { minKernelRequired(t, 5, 19) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetGSOMaxSize(link, 32768) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().GSOMaxSize != 32768 { t.Fatalf("GSO max size was not modified") } } func TestLinkSetGSOMaxSegs(t *testing.T) { minKernelRequired(t, 5, 19) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetGSOMaxSegs(link, 16) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().GSOMaxSegs != 16 { t.Fatalf("GSO max segments was not modified") } } func TestLinkSetGROMaxSize(t *testing.T) { minKernelRequired(t, 5, 19) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetGROMaxSize(link, 32768) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().GROMaxSize != 32768 { t.Fatalf("GRO max size was not modified") } } func TestLinkGetTSOMax(t *testing.T) { minKernelRequired(t, 5, 19) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().TSOMaxSize != 524280 || link.Attrs().TSOMaxSegs != 65535 { t.Fatalf("TSO max size and segments could not be retrieved") } } func TestLinkSetGSOIPv4MaxSize(t *testing.T) { minKernelRequired(t, 6, 3) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetGSOIPv4MaxSize(link, 32768) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().GSOIPv4MaxSize != 32768 { t.Fatalf("GSO max size was not modified") } } func TestLinkSetGROIPv4MaxSize(t *testing.T) { minKernelRequired(t, 6, 3) tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo", TxQLen: testTxQLen, MTU: 1500}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } err = LinkSetGROIPv4MaxSize(link, 32768) if err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().GROIPv4MaxSize != 32768 { t.Fatalf("GRO max size was not modified") } } func TestBridgeCreationWithMulticastSnooping(t *testing.T) { minKernelRequired(t, 4, 4) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeWithDefaultMcastSnoopName := "foo" bridgeWithDefaultMcastSnoop := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultMcastSnoopName}} if err := LinkAdd(bridgeWithDefaultMcastSnoop); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeWithDefaultMcastSnoopName, true) if err := LinkDel(bridgeWithDefaultMcastSnoop); err != nil { t.Fatal(err) } mcastSnoop := true bridgeWithMcastSnoopOnName := "bar" bridgeWithMcastSnoopOn := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithMcastSnoopOnName}, MulticastSnooping: &mcastSnoop} if err := LinkAdd(bridgeWithMcastSnoopOn); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeWithMcastSnoopOnName, true) if err := LinkDel(bridgeWithMcastSnoopOn); err != nil { t.Fatal(err) } mcastSnoop = false bridgeWithMcastSnoopOffName := "foobar" bridgeWithMcastSnoopOff := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithMcastSnoopOffName}, MulticastSnooping: &mcastSnoop} if err := LinkAdd(bridgeWithMcastSnoopOff); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeWithMcastSnoopOffName, false) if err := LinkDel(bridgeWithMcastSnoopOff); err != nil { t.Fatal(err) } } func TestBridgeSetMcastSnoop(t *testing.T) { minKernelRequired(t, 4, 4) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeName := "foo" bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} if err := LinkAdd(bridge); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeName, true) if err := BridgeSetMcastSnoop(bridge, false); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeName, false) if err := BridgeSetMcastSnoop(bridge, true); err != nil { t.Fatal(err) } expectMcastSnooping(t, bridgeName, true) if err := LinkDel(bridge); err != nil { t.Fatal(err) } } func expectMcastSnooping(t *testing.T, linkName string, expected bool) { bridge, err := LinkByName(linkName) if err != nil { t.Fatal(err) } if actual := *bridge.(*Bridge).MulticastSnooping; actual != expected { t.Fatalf("expected %t got %t", expected, actual) } } func TestBridgeSetVlanFiltering(t *testing.T) { minKernelRequired(t, 4, 4) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeName := "foo" bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} if err := LinkAdd(bridge); err != nil { t.Fatal(err) } expectVlanFiltering(t, bridgeName, false) if err := BridgeSetVlanFiltering(bridge, true); err != nil { t.Fatal(err) } expectVlanFiltering(t, bridgeName, true) if err := BridgeSetVlanFiltering(bridge, false); err != nil { t.Fatal(err) } expectVlanFiltering(t, bridgeName, false) if err := LinkDel(bridge); err != nil { t.Fatal(err) } } func TestBridgeDefaultPVID(t *testing.T) { minKernelRequired(t, 4, 4) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeName := "foo" bridge := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeName}} if err := LinkAdd(bridge); err != nil { t.Fatal(err) } expectVlanDefaultPVID(t, bridgeName, 1) if err := BridgeSetVlanDefaultPVID(bridge, 100); err != nil { t.Fatal(err) } expectVlanDefaultPVID(t, bridgeName, 100) if err := BridgeSetVlanDefaultPVID(bridge, 0); err != nil { t.Fatal(err) } expectVlanDefaultPVID(t, bridgeName, 0) if err := LinkDel(bridge); err != nil { t.Fatal(err) } } func expectVlanFiltering(t *testing.T, linkName string, expected bool) { bridge, err := LinkByName(linkName) if err != nil { t.Fatal(err) } if actual := *bridge.(*Bridge).VlanFiltering; actual != expected { t.Fatalf("expected %t got %t", expected, actual) } } func expectVlanDefaultPVID(t *testing.T, linkName string, expected uint16) { bridge, err := LinkByName(linkName) if err != nil { t.Fatal(err) } if actual := *bridge.(*Bridge).VlanDefaultPVID; actual != expected { t.Fatalf("expected %d got %d", expected, actual) } } func TestBridgeCreationWithAgeingTime(t *testing.T) { minKernelRequired(t, 3, 18) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeWithSpecifiedAgeingTimeName := "foo" ageingTime := uint32(20000) bridgeWithSpecifiedAgeingTime := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedAgeingTimeName}, AgeingTime: &ageingTime} if err := LinkAdd(bridgeWithSpecifiedAgeingTime); err != nil { t.Fatal(err) } retrievedBridge, err := LinkByName(bridgeWithSpecifiedAgeingTimeName) if err != nil { t.Fatal(err) } actualAgeingTime := *retrievedBridge.(*Bridge).AgeingTime if actualAgeingTime != ageingTime { t.Fatalf("expected %d got %d", ageingTime, actualAgeingTime) } if err := LinkDel(bridgeWithSpecifiedAgeingTime); err != nil { t.Fatal(err) } bridgeWithDefaultAgeingTimeName := "bar" bridgeWithDefaultAgeingTime := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultAgeingTimeName}} if err := LinkAdd(bridgeWithDefaultAgeingTime); err != nil { t.Fatal(err) } retrievedBridge, err = LinkByName(bridgeWithDefaultAgeingTimeName) if err != nil { t.Fatal(err) } actualAgeingTime = *retrievedBridge.(*Bridge).AgeingTime if actualAgeingTime != 30000 { t.Fatalf("expected %d got %d", 30000, actualAgeingTime) } if err := LinkDel(bridgeWithDefaultAgeingTime); err != nil { t.Fatal(err) } } func TestBridgeCreationWithHelloTime(t *testing.T) { minKernelRequired(t, 3, 18) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeWithSpecifiedHelloTimeName := "foo" helloTime := uint32(300) bridgeWithSpecifiedHelloTime := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithSpecifiedHelloTimeName}, HelloTime: &helloTime} if err := LinkAdd(bridgeWithSpecifiedHelloTime); err != nil { t.Fatal(err) } retrievedBridge, err := LinkByName(bridgeWithSpecifiedHelloTimeName) if err != nil { t.Fatal(err) } actualHelloTime := *retrievedBridge.(*Bridge).HelloTime if actualHelloTime != helloTime { t.Fatalf("expected %d got %d", helloTime, actualHelloTime) } if err := LinkDel(bridgeWithSpecifiedHelloTime); err != nil { t.Fatal(err) } bridgeWithDefaultHelloTimeName := "bar" bridgeWithDefaultHelloTime := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultHelloTimeName}} if err := LinkAdd(bridgeWithDefaultHelloTime); err != nil { t.Fatal(err) } retrievedBridge, err = LinkByName(bridgeWithDefaultHelloTimeName) if err != nil { t.Fatal(err) } actualHelloTime = *retrievedBridge.(*Bridge).HelloTime if actualHelloTime != 200 { t.Fatalf("expected %d got %d", 200, actualHelloTime) } if err := LinkDel(bridgeWithDefaultHelloTime); err != nil { t.Fatal(err) } } func TestBridgeCreationWithVlanFiltering(t *testing.T) { minKernelRequired(t, 3, 18) tearDown := setUpNetlinkTest(t) defer tearDown() bridgeWithVlanFilteringEnabledName := "foo" vlanFiltering := true bridgeWithVlanFilteringEnabled := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithVlanFilteringEnabledName}, VlanFiltering: &vlanFiltering} if err := LinkAdd(bridgeWithVlanFilteringEnabled); err != nil { t.Fatal(err) } retrievedBridge, err := LinkByName(bridgeWithVlanFilteringEnabledName) if err != nil { t.Fatal(err) } retrievedVlanFilteringState := *retrievedBridge.(*Bridge).VlanFiltering if retrievedVlanFilteringState != vlanFiltering { t.Fatalf("expected %t got %t", vlanFiltering, retrievedVlanFilteringState) } if err := LinkDel(bridgeWithVlanFilteringEnabled); err != nil { t.Fatal(err) } bridgeWithDefaultVlanFilteringName := "bar" bridgeWIthDefaultVlanFiltering := &Bridge{LinkAttrs: LinkAttrs{Name: bridgeWithDefaultVlanFilteringName}} if err := LinkAdd(bridgeWIthDefaultVlanFiltering); err != nil { t.Fatal(err) } retrievedBridge, err = LinkByName(bridgeWithDefaultVlanFilteringName) if err != nil { t.Fatal(err) } retrievedVlanFilteringState = *retrievedBridge.(*Bridge).VlanFiltering if retrievedVlanFilteringState != false { t.Fatalf("expected %t got %t", false, retrievedVlanFilteringState) } if err := LinkDel(bridgeWIthDefaultVlanFiltering); err != nil { t.Fatal(err) } } func TestLinkSubscribeWithProtinfo(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() master := &Bridge{LinkAttrs: LinkAttrs{Name: "foo"}} if err := LinkAdd(master); err != nil { t.Fatal(err) } slave := &Veth{ LinkAttrs: LinkAttrs{ Name: "bar", TxQLen: testTxQLen, MTU: 1400, MasterIndex: master.Attrs().Index, }, PeerName: "bar-peer", } if err := LinkAdd(slave); err != nil { t.Fatal(err) } ch := make(chan LinkUpdate) done := make(chan struct{}) defer close(done) if err := LinkSubscribe(ch, done); err != nil { t.Fatal(err) } if err := LinkSetHairpin(slave, true); err != nil { t.Fatal(err) } select { case update := <-ch: if !(update.Attrs().Name == "bar" && update.Attrs().Protinfo != nil && update.Attrs().Protinfo.Hairpin) { t.Fatal("Hairpin update not received as expected") } case <-time.After(time.Minute): t.Fatal("Hairpin update timed out") } if err := LinkDel(slave); err != nil { t.Fatal(err) } if err := LinkDel(master); err != nil { t.Fatal(err) } } func testGTPLink(t *testing.T) *GTP { conn1, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.ParseIP("0.0.0.0"), Port: 3386, }) if err != nil { t.Fatal(err) } conn2, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.ParseIP("0.0.0.0"), Port: 2152, }) if err != nil { t.Fatal(err) } fd1, _ := conn1.File() fd2, _ := conn2.File() return >P{ LinkAttrs: LinkAttrs{ Name: "gtp0", }, FD0: int(fd1.Fd()), FD1: int(fd2.Fd()), } } func TestLinkAddDelGTP(t *testing.T) { tearDown := setUpNetlinkTestWithKModule(t, "gtp") defer tearDown() gtp := testGTPLink(t) testLinkAddDel(t, gtp) } func TestLinkAddDelXfrmi(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() lo, _ := LinkByName("lo") testLinkAddDel(t, &Xfrmi{ LinkAttrs: LinkAttrs{Name: "xfrm123", ParentIndex: lo.Attrs().Index}, Ifid: 123}) } func TestLinkAddDelXfrmiNoId(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() lo, _ := LinkByName("lo") err := LinkAdd(&Xfrmi{ LinkAttrs: LinkAttrs{Name: "xfrm0", ParentIndex: lo.Attrs().Index}}) if !errors.Is(err, unix.EINVAL) { t.Errorf("Error returned expected to be EINVAL") } } func TestLinkByNameWhenLinkIsNotFound(t *testing.T) { _, err := LinkByName("iammissing") if err == nil { t.Fatal("Link not expected to found") } _, ok := err.(LinkNotFoundError) if !ok { t.Errorf("Error returned expected to of LinkNotFoundError type: %v", err) } } func TestLinkByAliasWhenLinkIsNotFound(t *testing.T) { _, err := LinkByAlias("iammissing") if err == nil { t.Fatal("Link not expected to found") } _, ok := err.(LinkNotFoundError) if !ok { t.Errorf("Error returned expected to of LinkNotFoundError type: %v", err) } } func TestLinkAddDelTuntap(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // Mount sysfs so that sysfs gets the namespace tag of the current network namespace // This is necessary so that /sys shows the network interfaces of the current namespace. if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY, ""); err != nil { t.Fatal("Cannot mount sysfs") } defer func() { if err := syscall.Unmount("/sys", 0); err != nil { t.Fatal("Cannot umount /sys") } }() testLinkAddDel(t, &Tuntap{ LinkAttrs: LinkAttrs{Name: "foo"}, Mode: TUNTAP_MODE_TAP}) } func TestLinkAddDelTuntapMq(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY, ""); err != nil { t.Fatal("Cannot mount sysfs") } defer func() { if err := syscall.Unmount("/sys", 0); err != nil { t.Fatal("Cannot umount /sys") } }() testLinkAddDel(t, &Tuntap{ LinkAttrs: LinkAttrs{Name: "foo"}, Mode: TUNTAP_MODE_TAP, Queues: 4}) testLinkAddDel(t, &Tuntap{ LinkAttrs: LinkAttrs{Name: "foo"}, Mode: TUNTAP_MODE_TAP, Queues: 4, Flags: TUNTAP_MULTI_QUEUE_DEFAULTS | TUNTAP_VNET_HDR}) } func TestLinkAddDelTuntapOwnerGroup(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY, ""); err != nil { t.Fatal("Cannot mount sysfs") } defer func() { if err := syscall.Unmount("/sys", 0); err != nil { t.Fatal("Cannot umount /sys") } }() testLinkAddDel(t, &Tuntap{ LinkAttrs: LinkAttrs{Name: "foo"}, Mode: TUNTAP_MODE_TAP, Owner: 0, Group: 0, }) } func TestVethPeerIndex(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() const ( vethPeer1 = "vethOne" vethPeer2 = "vethTwo" ) link := &Veth{ LinkAttrs: LinkAttrs{ Name: vethPeer1, MTU: 1500, Flags: net.FlagUp, }, PeerName: vethPeer2, } if err := LinkAdd(link); err != nil { t.Fatal(err) } linkOne, err := LinkByName("vethOne") if err != nil { t.Fatal(err) } linkTwo, err := LinkByName("vethTwo") if err != nil { t.Fatal(err) } peerIndexOne, err := VethPeerIndex(&Veth{LinkAttrs: *linkOne.Attrs()}) if err != nil { t.Fatal(err) } peerIndexTwo, err := VethPeerIndex(&Veth{LinkAttrs: *linkTwo.Attrs()}) if err != nil { t.Fatal(err) } if peerIndexOne != linkTwo.Attrs().Index { t.Errorf("VethPeerIndex(%s) mismatch %d != %d", linkOne.Attrs().Name, peerIndexOne, linkTwo.Attrs().Index) } if peerIndexTwo != linkOne.Attrs().Index { t.Errorf("VethPeerIndex(%s) mismatch %d != %d", linkTwo.Attrs().Name, peerIndexTwo, linkOne.Attrs().Index) } } func TestLinkSlaveBond(t *testing.T) { minKernelRequired(t, 3, 13) tearDown := setUpNetlinkTest(t) defer tearDown() const ( bondName = "foo" slaveName = "fooFoo" ) bond := NewLinkBond(LinkAttrs{Name: bondName}) bond.Mode = BOND_MODE_BALANCE_RR if err := LinkAdd(bond); err != nil { t.Fatal(err) } defer LinkDel(bond) slaveDummy := &Dummy{LinkAttrs{Name: slaveName}} if err := LinkAdd(slaveDummy); err != nil { t.Fatal(err) } defer LinkDel(slaveDummy) if err := LinkSetBondSlave(slaveDummy, bond); err != nil { t.Fatal(err) } slaveLink, err := LinkByName(slaveName) if err != nil { t.Fatal(err) } slave := slaveLink.Attrs().Slave if slave == nil { t.Errorf("for %s expected slave is not nil.", slaveName) } if slaveType := slave.SlaveType(); slaveType != "bond" { t.Errorf("for %s expected slave type is 'bond', but '%s'", slaveName, slaveType) } } func TestLinkSetBondSlaveQueueId(t *testing.T) { minKernelRequired(t, 3, 13) tearDown := setUpNetlinkTest(t) defer tearDown() const ( bondName = "foo" slave1Name = "fooFoo" ) bond := NewLinkBond(LinkAttrs{Name: bondName}) if err := LinkAdd(bond); err != nil { t.Fatal(err) } defer LinkDel(bond) slave := &Dummy{LinkAttrs{Name: slave1Name}} if err := LinkAdd(slave); err != nil { t.Fatal(err) } defer LinkDel(slave) if err := LinkSetBondSlave(slave, bond); err != nil { t.Fatal(err) } if err := pkgHandle.LinkSetBondSlaveQueueId(slave, 1); err != nil { t.Fatal(err) } } func TestLinkSetBondSlave(t *testing.T) { minKernelRequired(t, 3, 13) tearDown := setUpNetlinkTest(t) defer tearDown() const ( bondName = "foo" slaveOneName = "fooFoo" slaveTwoName = "fooBar" ) bond := NewLinkBond(LinkAttrs{Name: bondName}) bond.Mode = StringToBondModeMap["802.3ad"] bond.AdSelect = BondAdSelect(BOND_AD_SELECT_BANDWIDTH) bond.AdActorSysPrio = 1 bond.AdUserPortKey = 1 bond.AdActorSystem, _ = net.ParseMAC("06:aa:bb:cc:dd:ee") if err := LinkAdd(bond); err != nil { t.Fatal(err) } bondLink, err := LinkByName(bondName) if err != nil { t.Fatal(err) } defer LinkDel(bondLink) if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveOneName}}); err != nil { t.Fatal(err) } slaveOneLink, err := LinkByName(slaveOneName) if err != nil { t.Fatal(err) } defer LinkDel(slaveOneLink) if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveTwoName}}); err != nil { t.Fatal(err) } slaveTwoLink, err := LinkByName(slaveTwoName) if err != nil { t.Fatal(err) } defer LinkDel(slaveTwoLink) if err := LinkSetBondSlave(slaveOneLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { t.Fatal(err) } if err := LinkSetBondSlave(slaveTwoLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { t.Fatal(err) } // Update info about interfaces slaveOneLink, err = LinkByName(slaveOneName) if err != nil { t.Fatal(err) } slaveTwoLink, err = LinkByName(slaveTwoName) if err != nil { t.Fatal(err) } if slaveOneLink.Attrs().MasterIndex != bondLink.Attrs().Index { t.Errorf("For %s expected %s to be master", slaveOneLink.Attrs().Name, bondLink.Attrs().Name) } if slaveTwoLink.Attrs().MasterIndex != bondLink.Attrs().Index { t.Errorf("For %s expected %s to be master", slaveTwoLink.Attrs().Name, bondLink.Attrs().Name) } } func testFailover(t *testing.T, slaveName, bondName string) { slaveLink, err := LinkByName(slaveName) if err != nil { t.Fatal(err) } bondLink, err := LinkByName(bondName) if err != nil { t.Fatal(err) } err = LinkSetBondSlaveActive(slaveLink, &Bond{LinkAttrs: *bondLink.Attrs()}) if err != nil { t.Errorf("set slave link active failed: %v", err) return } bondLink, err = LinkByName(bondName) if err != nil { t.Fatal(err) } bond := bondLink.(*Bond) if bond.ActiveSlave != slaveLink.Attrs().Index { t.Errorf("the current active slave %d is not expected as %d", bond.ActiveSlave, slaveLink.Attrs().Index) } } func TestLinkFailover(t *testing.T) { minKernelRequired(t, 3, 13) tearDown := setUpNetlinkTest(t) defer tearDown() const ( bondName = "foo" slaveOneName = "fooFoo" slaveTwoName = "fooBar" ) bond := NewLinkBond(LinkAttrs{Name: bondName}) bond.Mode = StringToBondModeMap["active-backup"] bond.Miimon = 100 if err := LinkAdd(bond); err != nil { t.Fatal(err) } bondLink, err := LinkByName(bondName) if err != nil { t.Fatal(err) } defer LinkDel(bondLink) if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveOneName}}); err != nil { t.Fatal(err) } slaveOneLink, err := LinkByName(slaveOneName) if err != nil { t.Fatal(err) } defer LinkDel(slaveOneLink) if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveTwoName}}); err != nil { t.Fatal(err) } slaveTwoLink, err := LinkByName(slaveTwoName) if err != nil { t.Fatal(err) } defer LinkDel(slaveTwoLink) if err := LinkSetBondSlave(slaveOneLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { t.Fatal(err) } if err := LinkSetBondSlave(slaveTwoLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { t.Fatal(err) } testFailover(t, slaveOneName, bondName) testFailover(t, slaveTwoName, bondName) testFailover(t, slaveTwoName, bondName) // del slave from bond slaveOneLink, err = LinkByName(slaveOneName) if err != nil { t.Fatal(err) } err = LinkDelBondSlave(slaveOneLink, &Bond{LinkAttrs: *bondLink.Attrs()}) if err != nil { t.Errorf("Remove slave %s from bond failed: %v", slaveOneName, err) } slaveOneLink, err = LinkByName(slaveOneName) if err != nil { t.Fatal(err) } if slaveOneLink.Attrs().MasterIndex > 0 { t.Errorf("The nic %s is still a slave of %d", slaveOneName, slaveOneLink.Attrs().MasterIndex) } } func TestLinkSetAllmulticast(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo"}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetAllmulticastOn(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().Allmulti != 1 { t.Fatal("IFF_ALLMULTI was not set") } if err := LinkSetAllmulticastOff(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().Allmulti != 0 { t.Fatal("IFF_ALLMULTI is still set") } } func TestLinkSetMulticast(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() iface := &Veth{LinkAttrs: LinkAttrs{Name: "foo"}, PeerName: "bar"} if err := LinkAdd(iface); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetMulticastOn(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().Multi != 1 { t.Fatal("IFF_MULTICAST was not set") } if err := LinkSetMulticastOff(link); err != nil { t.Fatal(err) } link, err = LinkByName("foo") if err != nil { t.Fatal(err) } if link.Attrs().Multi != 0 { t.Fatal("IFF_MULTICAST is still set") } } func TestLinkSetMacvlanMode(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() const ( parentName = "foo" macvlanName = "fooFoo" macvtapName = "fooBar" ) parent := &Dummy{LinkAttrs{Name: parentName}} if err := LinkAdd(parent); err != nil { t.Fatal(err) } defer LinkDel(parent) testMacvlanMode := func(link Link, mode MacvlanMode) { if err := LinkSetMacvlanMode(link, mode); err != nil { t.Fatal(err) } name := link.Attrs().Name result, err := LinkByName(name) if err != nil { t.Fatal(err) } var actual MacvlanMode switch l := result.(type) { case *Macvlan: actual = l.Mode case *Macvtap: actual = l.Macvlan.Mode } if actual != mode { t.Fatalf("expected %v got %v for %+v", mode, actual, link) } } macvlan := &Macvlan{ LinkAttrs: LinkAttrs{Name: macvlanName, ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_BRIDGE, } if err := LinkAdd(macvlan); err != nil { t.Fatal(err) } defer LinkDel(macvlan) testMacvlanMode(macvlan, MACVLAN_MODE_VEPA) testMacvlanMode(macvlan, MACVLAN_MODE_PRIVATE) testMacvlanMode(macvlan, MACVLAN_MODE_SOURCE) testMacvlanMode(macvlan, MACVLAN_MODE_BRIDGE) macvtap := &Macvtap{ Macvlan: Macvlan{ LinkAttrs: LinkAttrs{Name: macvtapName, ParentIndex: parent.Attrs().Index}, Mode: MACVLAN_MODE_BRIDGE, }, } if err := LinkAdd(macvtap); err != nil { t.Fatal(err) } defer LinkDel(macvtap) testMacvlanMode(macvtap, MACVLAN_MODE_VEPA) testMacvlanMode(macvtap, MACVLAN_MODE_PRIVATE) testMacvlanMode(macvtap, MACVLAN_MODE_SOURCE) testMacvlanMode(macvtap, MACVLAN_MODE_BRIDGE) } netlink-1.3.0/link_tuntap_linux.go000066400000000000000000000004321466216277000172670ustar00rootroot00000000000000package netlink // ideally golang.org/x/sys/unix would define IfReq but it only has // IFNAMSIZ, hence this minimalistic implementation const ( SizeOfIfReq = 40 IFNAMSIZ = 16 ) type ifReq struct { Name [IFNAMSIZ]byte Flags uint16 pad [SizeOfIfReq - IFNAMSIZ - 2]byte } netlink-1.3.0/neigh.go000066400000000000000000000012201466216277000146060ustar00rootroot00000000000000package netlink import ( "fmt" "net" ) // Neigh represents a link layer neighbor from netlink. type Neigh struct { LinkIndex int Family int State int Type int Flags int FlagsExt int IP net.IP HardwareAddr net.HardwareAddr LLIPAddr net.IP //Used in the case of NHRP Vlan int VNI int MasterIndex int } // String returns $ip/$hwaddr $label func (neigh *Neigh) String() string { return fmt.Sprintf("%s %s", neigh.IP, neigh.HardwareAddr) } // NeighUpdate is sent when a neighbor changes - type is RTM_NEWNEIGH or RTM_DELNEIGH. type NeighUpdate struct { Type uint16 Neigh } netlink-1.3.0/neigh_linux.go000066400000000000000000000300201466216277000160250ustar00rootroot00000000000000package netlink import ( "fmt" "net" "syscall" "unsafe" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) const ( NDA_UNSPEC = iota NDA_DST NDA_LLADDR NDA_CACHEINFO NDA_PROBES NDA_VLAN NDA_PORT NDA_VNI NDA_IFINDEX NDA_MASTER NDA_LINK_NETNSID NDA_SRC_VNI NDA_PROTOCOL NDA_NH_ID NDA_FDB_EXT_ATTRS NDA_FLAGS_EXT NDA_MAX = NDA_FLAGS_EXT ) // Neighbor Cache Entry States. const ( NUD_NONE = 0x00 NUD_INCOMPLETE = 0x01 NUD_REACHABLE = 0x02 NUD_STALE = 0x04 NUD_DELAY = 0x08 NUD_PROBE = 0x10 NUD_FAILED = 0x20 NUD_NOARP = 0x40 NUD_PERMANENT = 0x80 ) // Neighbor Flags const ( NTF_USE = 0x01 NTF_SELF = 0x02 NTF_MASTER = 0x04 NTF_PROXY = 0x08 NTF_EXT_LEARNED = 0x10 NTF_OFFLOADED = 0x20 NTF_STICKY = 0x40 NTF_ROUTER = 0x80 ) // Extended Neighbor Flags const ( NTF_EXT_MANAGED = 0x00000001 ) // Ndmsg is for adding, removing or receiving information about a neighbor table entry type Ndmsg struct { Family uint8 Index uint32 State uint16 Flags uint8 Type uint8 } func deserializeNdmsg(b []byte) *Ndmsg { var dummy Ndmsg return (*Ndmsg)(unsafe.Pointer(&b[0:unsafe.Sizeof(dummy)][0])) } func (msg *Ndmsg) Serialize() []byte { return (*(*[unsafe.Sizeof(*msg)]byte)(unsafe.Pointer(msg)))[:] } func (msg *Ndmsg) Len() int { return int(unsafe.Sizeof(*msg)) } // NeighAdd will add an IP to MAC mapping to the ARP table // Equivalent to: `ip neigh add ....` func NeighAdd(neigh *Neigh) error { return pkgHandle.NeighAdd(neigh) } // NeighAdd will add an IP to MAC mapping to the ARP table // Equivalent to: `ip neigh add ....` func (h *Handle) NeighAdd(neigh *Neigh) error { return h.neighAdd(neigh, unix.NLM_F_CREATE|unix.NLM_F_EXCL) } // NeighSet will add or replace an IP to MAC mapping to the ARP table // Equivalent to: `ip neigh replace....` func NeighSet(neigh *Neigh) error { return pkgHandle.NeighSet(neigh) } // NeighSet will add or replace an IP to MAC mapping to the ARP table // Equivalent to: `ip neigh replace....` func (h *Handle) NeighSet(neigh *Neigh) error { return h.neighAdd(neigh, unix.NLM_F_CREATE|unix.NLM_F_REPLACE) } // NeighAppend will append an entry to FDB // Equivalent to: `bridge fdb append...` func NeighAppend(neigh *Neigh) error { return pkgHandle.NeighAppend(neigh) } // NeighAppend will append an entry to FDB // Equivalent to: `bridge fdb append...` func (h *Handle) NeighAppend(neigh *Neigh) error { return h.neighAdd(neigh, unix.NLM_F_CREATE|unix.NLM_F_APPEND) } // NeighAppend will append an entry to FDB // Equivalent to: `bridge fdb append...` func neighAdd(neigh *Neigh, mode int) error { return pkgHandle.neighAdd(neigh, mode) } // NeighAppend will append an entry to FDB // Equivalent to: `bridge fdb append...` func (h *Handle) neighAdd(neigh *Neigh, mode int) error { req := h.newNetlinkRequest(unix.RTM_NEWNEIGH, mode|unix.NLM_F_ACK) return neighHandle(neigh, req) } // NeighDel will delete an IP address from a link device. // Equivalent to: `ip addr del $addr dev $link` func NeighDel(neigh *Neigh) error { return pkgHandle.NeighDel(neigh) } // NeighDel will delete an IP address from a link device. // Equivalent to: `ip addr del $addr dev $link` func (h *Handle) NeighDel(neigh *Neigh) error { req := h.newNetlinkRequest(unix.RTM_DELNEIGH, unix.NLM_F_ACK) return neighHandle(neigh, req) } func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error { var family int if neigh.Family > 0 { family = neigh.Family } else { family = nl.GetIPFamily(neigh.IP) } msg := Ndmsg{ Family: uint8(family), Index: uint32(neigh.LinkIndex), State: uint16(neigh.State), Type: uint8(neigh.Type), Flags: uint8(neigh.Flags), } req.AddData(&msg) ipData := neigh.IP.To4() if ipData == nil { ipData = neigh.IP.To16() } dstData := nl.NewRtAttr(NDA_DST, ipData) req.AddData(dstData) if neigh.LLIPAddr != nil { llIPData := nl.NewRtAttr(NDA_LLADDR, neigh.LLIPAddr.To4()) req.AddData(llIPData) } else if neigh.HardwareAddr != nil { hwData := nl.NewRtAttr(NDA_LLADDR, []byte(neigh.HardwareAddr)) req.AddData(hwData) } if neigh.FlagsExt != 0 { flagsExtData := nl.NewRtAttr(NDA_FLAGS_EXT, nl.Uint32Attr(uint32(neigh.FlagsExt))) req.AddData(flagsExtData) } if neigh.Vlan != 0 { vlanData := nl.NewRtAttr(NDA_VLAN, nl.Uint16Attr(uint16(neigh.Vlan))) req.AddData(vlanData) } if neigh.VNI != 0 { vniData := nl.NewRtAttr(NDA_VNI, nl.Uint32Attr(uint32(neigh.VNI))) req.AddData(vniData) } if neigh.MasterIndex != 0 { masterData := nl.NewRtAttr(NDA_MASTER, nl.Uint32Attr(uint32(neigh.MasterIndex))) req.AddData(masterData) } _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // NeighList returns a list of IP-MAC mappings in the system (ARP table). // Equivalent to: `ip neighbor show`. // The list can be filtered by link and ip family. func NeighList(linkIndex, family int) ([]Neigh, error) { return pkgHandle.NeighList(linkIndex, family) } // NeighProxyList returns a list of neighbor proxies in the system. // Equivalent to: `ip neighbor show proxy`. // The list can be filtered by link and ip family. func NeighProxyList(linkIndex, family int) ([]Neigh, error) { return pkgHandle.NeighProxyList(linkIndex, family) } // NeighList returns a list of IP-MAC mappings in the system (ARP table). // Equivalent to: `ip neighbor show`. // The list can be filtered by link and ip family. func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) { return h.NeighListExecute(Ndmsg{ Family: uint8(family), Index: uint32(linkIndex), }) } // NeighProxyList returns a list of neighbor proxies in the system. // Equivalent to: `ip neighbor show proxy`. // The list can be filtered by link, ip family. func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) { return h.NeighListExecute(Ndmsg{ Family: uint8(family), Index: uint32(linkIndex), Flags: NTF_PROXY, }) } // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. func NeighListExecute(msg Ndmsg) ([]Neigh, error) { return pkgHandle.NeighListExecute(msg) } // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) { req := h.newNetlinkRequest(unix.RTM_GETNEIGH, unix.NLM_F_DUMP) req.AddData(&msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNEIGH) if err != nil { return nil, err } var res []Neigh for _, m := range msgs { ndm := deserializeNdmsg(m) if msg.Index != 0 && ndm.Index != msg.Index { // Ignore messages from other interfaces continue } if msg.Family != 0 && ndm.Family != msg.Family { continue } if msg.State != 0 && ndm.State != msg.State { continue } if msg.Type != 0 && ndm.Type != msg.Type { continue } if msg.Flags != 0 && ndm.Flags != msg.Flags { continue } neigh, err := NeighDeserialize(m) if err != nil { continue } res = append(res, *neigh) } return res, nil } func NeighDeserialize(m []byte) (*Neigh, error) { msg := deserializeNdmsg(m) neigh := Neigh{ LinkIndex: int(msg.Index), Family: int(msg.Family), State: int(msg.State), Type: int(msg.Type), Flags: int(msg.Flags), } attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } for _, attr := range attrs { switch attr.Attr.Type { case NDA_DST: neigh.IP = net.IP(attr.Value) case NDA_LLADDR: // BUG: Is this a bug in the netlink library? // #define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len)) // #define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0)) attrLen := attr.Attr.Len - unix.SizeofRtAttr if attrLen == 4 { neigh.LLIPAddr = net.IP(attr.Value) } else if attrLen == 16 { // Can be IPv6 or FireWire HWAddr link, err := LinkByIndex(neigh.LinkIndex) if err == nil && link.Attrs().EncapType == "tunnel6" { neigh.IP = net.IP(attr.Value) } else { neigh.HardwareAddr = net.HardwareAddr(attr.Value) } } else { neigh.HardwareAddr = net.HardwareAddr(attr.Value) } case NDA_FLAGS_EXT: neigh.FlagsExt = int(native.Uint32(attr.Value[0:4])) case NDA_VLAN: neigh.Vlan = int(native.Uint16(attr.Value[0:2])) case NDA_VNI: neigh.VNI = int(native.Uint32(attr.Value[0:4])) case NDA_MASTER: neigh.MasterIndex = int(native.Uint32(attr.Value[0:4])) } } return &neigh, nil } // NeighSubscribe takes a chan down which notifications will be sent // when neighbors are added or deleted. Close the 'done' chan to stop subscription. func NeighSubscribe(ch chan<- NeighUpdate, done <-chan struct{}) error { return neighSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false) } // NeighSubscribeAt works like NeighSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func NeighSubscribeAt(ns netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}) error { return neighSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false) } // NeighSubscribeOptions contains a set of options to use with // NeighSubscribeWithOptions. type NeighSubscribeOptions struct { Namespace *netns.NsHandle ErrorCallback func(error) ListExisting bool // max size is based on value of /proc/sys/net/core/rmem_max ReceiveBufferSize int ReceiveBufferForceSize bool ReceiveTimeout *unix.Timeval } // NeighSubscribeWithOptions work like NeighSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, options NeighSubscribeOptions) error { if options.Namespace == nil { none := netns.None() options.Namespace = &none } return neighSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize) } func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce bool) error { s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_NEIGH) makeRequest := func(family int) error { req := pkgHandle.newNetlinkRequest(unix.RTM_GETNEIGH, unix.NLM_F_DUMP) ndmsg := &Ndmsg{Family: uint8(family)} req.AddData(ndmsg) if err := s.Send(req); err != nil { return err } return nil } if err != nil { return err } if rcvTimeout != nil { if err := s.SetReceiveTimeout(rcvTimeout); err != nil { return err } } if rcvbuf != 0 { err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce) if err != nil { return err } } if done != nil { go func() { <-done s.Close() }() } if listExisting { if err := makeRequest(unix.AF_UNSPEC); err != nil { return err } // We have to wait for NLMSG_DONE before making AF_BRIDGE request } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { if cberr != nil { cberr(err) } return } if from.Pid != nl.PidKernel { if cberr != nil { cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)) } continue } for _, m := range msgs { if m.Header.Type == unix.NLMSG_DONE { if listExisting { // This will be called after handling AF_UNSPEC // list request, we have to wait for NLMSG_DONE // before making another request if err := makeRequest(unix.AF_BRIDGE); err != nil { if cberr != nil { cberr(err) } return } listExisting = false } continue } if m.Header.Type == unix.NLMSG_ERROR { nError := int32(native.Uint32(m.Data[0:4])) if nError == 0 { continue } if cberr != nil { cberr(syscall.Errno(-nError)) } return } neigh, err := NeighDeserialize(m.Data) if err != nil { if cberr != nil { cberr(err) } return } ch <- NeighUpdate{Type: m.Header.Type, Neigh: *neigh} } } }() return nil } netlink-1.3.0/neigh_test.go000066400000000000000000000333741466216277000156640ustar00rootroot00000000000000// +build linux package netlink import ( "net" "syscall" "testing" "time" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) type arpEntry struct { ip net.IP mac net.HardwareAddr } type proxyEntry struct { ip net.IP dev int } func parseMAC(s string) net.HardwareAddr { m, err := net.ParseMAC(s) if err != nil { panic(err) } return m } func dumpContains(dump []Neigh, e arpEntry) bool { for _, n := range dump { if n.IP.Equal(e.ip) && (n.State&NUD_INCOMPLETE) == 0 { return true } } return false } func dumpContainsNeigh(dump []Neigh, ne Neigh) bool { for _, n := range dump { if n.IP.Equal(ne.IP) && n.LLIPAddr.Equal(ne.LLIPAddr) { return true } } return false } func dumpContainsState(dump []Neigh, e arpEntry, s uint16) bool { for _, n := range dump { if n.IP.Equal(e.ip) && uint16(n.State) == s { return true } } return false } func dumpContainsProxy(dump []Neigh, p proxyEntry) bool { for _, n := range dump { if n.IP.Equal(p.ip) && (n.LinkIndex == p.dev) && (n.Flags&NTF_PROXY) == NTF_PROXY { return true } } return false } func TestNeighAddDelLLIPAddr(t *testing.T) { setUpNetlinkTestWithKModule(t, "ip_gre") tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Gretun{ LinkAttrs: LinkAttrs{Name: "neigh0"}, Local: net.IPv4(127, 0, 0, 1), IKey: 1234, OKey: 1234} if err := LinkAdd(&dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) entry := Neigh{ LinkIndex: dummy.Index, State: NUD_PERMANENT, IP: net.IPv4(198, 51, 100, 2), LLIPAddr: net.IPv4(198, 51, 100, 1), } err := NeighAdd(&entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } // Dump and see that all added entries are there dump, err := NeighList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } if !dumpContainsNeigh(dump, entry) { t.Errorf("Dump does not contain: %v: %v", entry, dump) } // Delete the entry err = NeighDel(&entry) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } func TestNeighAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(&dummy); err != nil { t.Fatal(err) } ensureIndex(dummy.Attrs()) arpTable := []arpEntry{ {net.ParseIP("10.99.0.1"), parseMAC("aa:bb:cc:dd:00:01")}, {net.ParseIP("10.99.0.2"), parseMAC("aa:bb:cc:dd:00:02")}, {net.ParseIP("10.99.0.3"), parseMAC("aa:bb:cc:dd:00:03")}, {net.ParseIP("10.99.0.4"), parseMAC("aa:bb:cc:dd:00:04")}, {net.ParseIP("10.99.0.5"), parseMAC("aa:bb:cc:dd:00:05")}, } // Add the arpTable for _, entry := range arpTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Dump and see that all added entries are there dump, err := NeighList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range arpTable { if !dumpContains(dump, entry) { t.Errorf("Dump does not contain: %v", entry) } } // Delete the arpTable for _, entry := range arpTable { err := NeighDel(&Neigh{ LinkIndex: dummy.Index, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } } // TODO: seems not working because of cache //// Dump and see that none of deleted entries are there //dump, err = NeighList(dummy.Index, 0) //if err != nil { //t.Errorf("Failed to NeighList: %v", err) //} //for _, entry := range arpTable { //if dumpContains(dump, entry) { //t.Errorf("Dump contains: %v", entry) //} //} if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } func TestNeighAddDelProxy(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(&dummy); err != nil { t.Fatal(err) } ensureIndex(dummy.Attrs()) proxyTable := []proxyEntry{ {net.ParseIP("10.99.0.1"), dummy.Index}, {net.ParseIP("10.99.0.2"), dummy.Index}, {net.ParseIP("10.99.0.3"), dummy.Index}, {net.ParseIP("10.99.0.4"), dummy.Index}, {net.ParseIP("10.99.0.5"), dummy.Index}, } // Add the proxyTable for _, entry := range proxyTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, Flags: NTF_PROXY, IP: entry.ip, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Dump and see that all added entries are there dump, err := NeighProxyList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range proxyTable { if !dumpContainsProxy(dump, entry) { t.Errorf("Dump does not contain: %v", entry) } } // Delete the proxyTable for _, entry := range proxyTable { err := NeighDel(&Neigh{ LinkIndex: dummy.Index, Flags: NTF_PROXY, IP: entry.ip, }) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } } // Dump and see that none of deleted entries are there dump, err = NeighProxyList(dummy.Index, 0) if err != nil { t.Errorf("Failed to NeighList: %v", err) } for _, entry := range proxyTable { if dumpContainsProxy(dump, entry) { t.Errorf("Dump contains: %v", entry) } } if err := LinkDel(&dummy); err != nil { t.Fatal(err) } } // expectNeighUpdate returns whether the expected updates are received within one second. func expectNeighUpdate(ch <-chan NeighUpdate, expected []NeighUpdate) bool { for { timeout := time.After(time.Second) select { case update := <-ch: var toDelete []int for index, elem := range expected { if update.Type == elem.Type && update.Neigh.State == elem.Neigh.State && update.Neigh.IP != nil && update.Neigh.IP.Equal(elem.Neigh.IP) { toDelete = append(toDelete, index) } } for done, index := range toDelete { expected = append(expected[:index-done], expected[index-done+1:]...) } if len(expected) == 0 { return true } case <-timeout: return false } } } func TestNeighSubscribe(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := LinkDel(dummy); err != nil { t.Fatal(err) } }() ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribe(ch, done); err != nil { t.Fatal(err) } entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(10, 99, 0, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } if err := NeighAdd(entry); err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entry, }}) { t.Fatalf("Add update not received as expected") } if err := NeighDel(entry); err != nil { t.Fatal(err) } if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: Neigh{ State: NUD_FAILED, IP: entry.IP}, }}) { t.Fatalf("Del update not received as expected") } } func TestNeighSubscribeWithOptions(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, }); err != nil { t.Fatal(err) } dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := LinkDel(dummy); err != nil { t.Fatal(err) } }() entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(10, 99, 0, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } err := NeighAdd(entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entry, }}) { t.Fatalf("Add update not received as expected") } } func TestNeighSubscribeAt(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() // Subscribe for Neigh events on the custom netns ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribeAt(newNs, ch, done); err != nil { t.Fatal(err) } dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := nh.LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := nh.LinkDel(dummy); err != nil { t.Fatal(err) } }() entry := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(198, 51, 100, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } err = nh.NeighAdd(entry) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entry, }}) { t.Fatalf("Add update not received as expected") } } func TestNeighSubscribeListExisting(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() dummy := &Dummy{LinkAttrs{Name: "neigh0"}} if err := nh.LinkAdd(dummy); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(dummy.Attrs()) defer func() { if err := nh.LinkDel(dummy); err != nil { t.Fatal(err) } }() vxlani := &Vxlan{LinkAttrs: LinkAttrs{Name: "neigh1"}, VxlanId: 1} if err := nh.LinkAdd(vxlani); err != nil { t.Errorf("Failed to create link: %v", err) } ensureIndex(vxlani.Attrs()) defer func() { if err := nh.LinkDel(vxlani); err != nil { t.Fatal(err) } }() entry1 := &Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: net.IPv4(198, 51, 100, 1), HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"), } entryBr := &Neigh{ Family: syscall.AF_BRIDGE, LinkIndex: vxlani.Index, State: NUD_PERMANENT, Flags: NTF_SELF, IP: net.IPv4(198, 51, 100, 3), HardwareAddr: parseMAC("aa:bb:cc:dd:00:03"), } err = nh.NeighAdd(entry1) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } err = nh.NeighAppend(entryBr) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } // Subscribe for Neigh events including existing neighbors ch := make(chan NeighUpdate) done := make(chan struct{}) defer close(done) if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{ Namespace: &newNs, ListExisting: true}, ); err != nil { t.Fatal(err) } if !expectNeighUpdate(ch, []NeighUpdate{ NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entry1, }, NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entryBr, }, }) { t.Fatalf("Existing add update not received as expected") } entry2 := &Neigh{ LinkIndex: dummy.Index, State: NUD_PERMANENT, IP: net.IPv4(198, 51, 100, 2), HardwareAddr: parseMAC("aa:bb:cc:dd:00:02"), } err = nh.NeighAdd(entry2) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } if !expectNeighUpdate(ch, []NeighUpdate{NeighUpdate{ Type: unix.RTM_NEWNEIGH, Neigh: *entry2, }}) { t.Fatalf("Existing add update not received as expected") } } func TestNeighListExecuteStateFilter(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // Create dummy iface dummy := Dummy{LinkAttrs{Name: "neigh0"}} if err := LinkAdd(&dummy); err != nil { t.Fatal(err) } ensureIndex(dummy.Attrs()) // Define some entries reachArpTable := []arpEntry{ {net.ParseIP("198.51.100.1"), parseMAC("44:bb:cc:dd:00:01")}, {net.ParseIP("2001:db8::1"), parseMAC("66:bb:cc:dd:00:02")}, } staleArpTable := []arpEntry{ {net.ParseIP("198.51.100.10"), parseMAC("44:bb:cc:dd:00:10")}, {net.ParseIP("2001:db8::10"), parseMAC("66:bb:cc:dd:00:10")}, } entries := append(reachArpTable, staleArpTable...) // Add reachable neigh entries for _, entry := range reachArpTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, State: NUD_REACHABLE, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Add stale neigh entries for _, entry := range staleArpTable { err := NeighAdd(&Neigh{ LinkIndex: dummy.Index, State: NUD_STALE, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighAdd: %v", err) } } // Dump reachable and see that all added reachable entries are present and there are no stale entries dump, err := NeighListExecute(Ndmsg{ Index: uint32(dummy.Index), State: NUD_REACHABLE, }) if err != nil { t.Errorf("Failed to NeighListExecute: %v", err) } for _, entry := range reachArpTable { if !dumpContainsState(dump, entry, NUD_REACHABLE) { t.Errorf("Dump does not contains: %v", entry) } } for _, entry := range staleArpTable { if dumpContainsState(dump, entry, NUD_STALE) { t.Errorf("Dump contains: %v", entry) } } // Delete all neigh entries for _, entry := range entries { err := NeighDel(&Neigh{ LinkIndex: dummy.Index, IP: entry.ip, HardwareAddr: entry.mac, }) if err != nil { t.Errorf("Failed to NeighDel: %v", err) } } } netlink-1.3.0/netlink.go000066400000000000000000000025351466216277000151720ustar00rootroot00000000000000// Package netlink provides a simple library for netlink. Netlink is // the interface a user-space program in linux uses to communicate with // the kernel. It can be used to add and remove interfaces, set up ip // addresses and routes, and confiugre ipsec. Netlink communication // requires elevated privileges, so in most cases this code needs to // be run as root. The low level primitives for netlink are contained // in the nl subpackage. This package attempts to provide a high-level // interface that is loosly modeled on the iproute2 cli. package netlink import ( "errors" "net" ) var ( // ErrNotImplemented is returned when a requested feature is not implemented. ErrNotImplemented = errors.New("not implemented") ) // ParseIPNet parses a string in ip/net format and returns a net.IPNet. // This is valuable because addresses in netlink are often IPNets and // ParseCIDR returns an IPNet with the IP part set to the base IP of the // range. func ParseIPNet(s string) (*net.IPNet, error) { ip, ipNet, err := net.ParseCIDR(s) if err != nil { return nil, err } ipNet.IP = ip return ipNet, nil } // NewIPNet generates an IPNet from an ip address using a netmask of 32 or 128. func NewIPNet(ip net.IP) *net.IPNet { if ip.To4() != nil { return &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 32)} } return &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)} } netlink-1.3.0/netlink_linux.go000066400000000000000000000003251466216277000164040ustar00rootroot00000000000000package netlink import "github.com/vishvananda/netlink/nl" // Family type definitions const ( FAMILY_ALL = nl.FAMILY_ALL FAMILY_V4 = nl.FAMILY_V4 FAMILY_V6 = nl.FAMILY_V6 FAMILY_MPLS = nl.FAMILY_MPLS ) netlink-1.3.0/netlink_test.go000066400000000000000000000142531466216277000162310ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "bytes" "crypto/rand" "encoding/hex" "fmt" "io/ioutil" "log" "os" "os/exec" "runtime" "strings" "testing" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) type tearDownNetlinkTest func() func skipUnlessRoot(t testing.TB) { t.Helper() if os.Getuid() != 0 { t.Skip("Test requires root privileges.") } } func skipUnlessKModuleLoaded(t *testing.T, moduleNames ...string) { t.Helper() file, err := ioutil.ReadFile("/proc/modules") if err != nil { t.Fatal("Failed to open /proc/modules", err) } foundRequiredMods := make(map[string]bool) lines := strings.Split(string(file), "\n") for _, name := range moduleNames { foundRequiredMods[name] = false for _, line := range lines { n := strings.Split(line, " ")[0] if n == name { foundRequiredMods[name] = true break } } } failed := false for _, name := range moduleNames { if found, _ := foundRequiredMods[name]; !found { t.Logf("Test requires missing kmodule %q.", name) failed = true } } if failed { t.SkipNow() } } func setUpNetlinkTest(t testing.TB) tearDownNetlinkTest { skipUnlessRoot(t) // new temporary namespace so we don't pollute the host // lock thread since the namespace is thread local runtime.LockOSThread() var err error ns, err := netns.New() if err != nil { t.Fatal("Failed to create newns", ns) } return func() { ns.Close() runtime.UnlockOSThread() } } // setUpNamedNetlinkTest create a temporary named names space with a random name func setUpNamedNetlinkTest(t *testing.T) (string, tearDownNetlinkTest) { skipUnlessRoot(t) origNS, err := netns.Get() if err != nil { t.Fatal("Failed saving orig namespace") } // create a random name rnd := make([]byte, 4) if _, err := rand.Read(rnd); err != nil { t.Fatal("failed creating random ns name") } name := "netlinktest-" + hex.EncodeToString(rnd) ns, err := netns.NewNamed(name) if err != nil { t.Fatal("Failed to create new ns", err) } runtime.LockOSThread() cleanup := func() { ns.Close() netns.DeleteNamed(name) netns.Set(origNS) runtime.UnlockOSThread() } if err := netns.Set(ns); err != nil { cleanup() t.Fatal("Failed entering new namespace", err) } return name, cleanup } func setUpNetlinkTestWithLoopback(t *testing.T) tearDownNetlinkTest { skipUnlessRoot(t) runtime.LockOSThread() ns, err := netns.New() if err != nil { t.Fatal("Failed to create new netns", ns) } link, err := LinkByName("lo") if err != nil { t.Fatalf("Failed to find \"lo\" in new netns: %v", err) } if err := LinkSetUp(link); err != nil { t.Fatalf("Failed to bring up \"lo\" in new netns: %v", err) } return func() { ns.Close() runtime.UnlockOSThread() } } func setUpF(t *testing.T, path, value string) { file, err := os.Create(path) if err != nil { t.Fatalf("Failed to open %s: %s", path, err) } defer file.Close() file.WriteString(value) } func setUpMPLSNetlinkTest(t *testing.T) tearDownNetlinkTest { if _, err := os.Stat("/proc/sys/net/mpls/platform_labels"); err != nil { t.Skip("Test requires MPLS support.") } f := setUpNetlinkTest(t) setUpF(t, "/proc/sys/net/mpls/platform_labels", "1024") setUpF(t, "/proc/sys/net/mpls/conf/lo/input", "1") return f } func setUpSEG6NetlinkTest(t *testing.T) tearDownNetlinkTest { // check if SEG6 options are enabled in Kernel Config cmd := exec.Command("uname", "-r") var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { t.Fatal("Failed to run: uname -r") } s := []string{"/boot/config-", strings.TrimRight(out.String(), "\n")} filename := strings.Join(s, "") grepKey := func(key, fname string) (string, error) { cmd := exec.Command("grep", key, filename) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() // "err != nil" if no line matched with grep return strings.TrimRight(out.String(), "\n"), err } key := string("CONFIG_IPV6_SEG6_LWTUNNEL=y") if _, err := grepKey(key, filename); err != nil { msg := "Skipped test because it requires SEG6_LWTUNNEL support." log.Println(msg) t.Skip(msg) } // Add CONFIG_IPV6_SEG6_HMAC to support seg6_hamc // key := string("CONFIG_IPV6_SEG6_HMAC=y") return setUpNetlinkTest(t) } func setUpNetlinkTestWithKModule(t *testing.T, moduleNames ...string) tearDownNetlinkTest { skipUnlessKModuleLoaded(t, moduleNames...) return setUpNetlinkTest(t) } func setUpNamedNetlinkTestWithKModule(t *testing.T, moduleNames ...string) (string, tearDownNetlinkTest) { file, err := ioutil.ReadFile("/proc/modules") if err != nil { t.Fatal("Failed to open /proc/modules", err) } foundRequiredMods := make(map[string]bool) lines := strings.Split(string(file), "\n") for _, name := range moduleNames { foundRequiredMods[name] = false for _, line := range lines { n := strings.Split(line, " ")[0] if n == name { foundRequiredMods[name] = true break } } } failed := false for _, name := range moduleNames { if found, _ := foundRequiredMods[name]; !found { t.Logf("Test requires missing kmodule %q.", name) failed = true } } if failed { t.SkipNow() } return setUpNamedNetlinkTest(t) } func remountSysfs() error { if err := unix.Mount("", "/", "none", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { return err } if err := unix.Unmount("/sys", unix.MNT_DETACH); err != nil { return err } return unix.Mount("", "/sys", "sysfs", 0, "") } func minKernelRequired(t *testing.T, kernel, major int) { t.Helper() k, m, err := KernelVersion() if err != nil { t.Fatal(err) } if k < kernel || k == kernel && m < major { t.Skipf("Host Kernel (%d.%d) does not meet test's minimum required version: (%d.%d)", k, m, kernel, major) } } func KernelVersion() (kernel, major int, err error) { uts := unix.Utsname{} if err = unix.Uname(&uts); err != nil { return } ba := make([]byte, 0, len(uts.Release)) for _, b := range uts.Release { if b == 0 { break } ba = append(ba, byte(b)) } var rest string if n, _ := fmt.Sscanf(string(ba), "%d.%d%s", &kernel, &major, &rest); n < 2 { err = fmt.Errorf("can't parse kernel version in %q", string(ba)) } return } func TestMain(m *testing.M) { nl.EnableErrorMessageReporting = true os.Exit(m.Run()) } netlink-1.3.0/netlink_unspecified.go000066400000000000000000000130441466216277000175450ustar00rootroot00000000000000// +build !linux package netlink import "net" func LinkSetUp(link Link) error { return ErrNotImplemented } func LinkSetDown(link Link) error { return ErrNotImplemented } func LinkSetMTU(link Link, mtu int) error { return ErrNotImplemented } func LinkSetMaster(link Link, master Link) error { return ErrNotImplemented } func LinkSetNsPid(link Link, nspid int) error { return ErrNotImplemented } func LinkSetNsFd(link Link, fd int) error { return ErrNotImplemented } func LinkSetName(link Link, name string) error { return ErrNotImplemented } func LinkSetAlias(link Link, name string) error { return ErrNotImplemented } func LinkSetHardwareAddr(link Link, hwaddr net.HardwareAddr) error { return ErrNotImplemented } func LinkSetVfHardwareAddr(link Link, vf int, hwaddr net.HardwareAddr) error { return ErrNotImplemented } func LinkSetVfVlan(link Link, vf, vlan int) error { return ErrNotImplemented } func LinkSetVfVlanQos(link Link, vf, vlan, qos int) error { return ErrNotImplemented } func LinkSetVfVlanQosProto(link Link, vf, vlan, qos, proto int) error { return ErrNotImplemented } func LinkSetVfTxRate(link Link, vf, rate int) error { return ErrNotImplemented } func LinkSetVfRate(link Link, vf, minRate, maxRate int) error { return ErrNotImplemented } func LinkSetNoMaster(link Link) error { return ErrNotImplemented } func LinkSetMasterByIndex(link Link, masterIndex int) error { return ErrNotImplemented } func LinkSetXdpFd(link Link, fd int) error { return ErrNotImplemented } func LinkSetXdpFdWithFlags(link Link, fd, flags int) error { return ErrNotImplemented } func LinkSetARPOff(link Link) error { return ErrNotImplemented } func LinkSetARPOn(link Link) error { return ErrNotImplemented } func LinkByName(name string) (Link, error) { return nil, ErrNotImplemented } func LinkByAlias(alias string) (Link, error) { return nil, ErrNotImplemented } func LinkByIndex(index int) (Link, error) { return nil, ErrNotImplemented } func LinkSetHairpin(link Link, mode bool) error { return ErrNotImplemented } func LinkSetGuard(link Link, mode bool) error { return ErrNotImplemented } func LinkSetFastLeave(link Link, mode bool) error { return ErrNotImplemented } func LinkSetLearning(link Link, mode bool) error { return ErrNotImplemented } func LinkSetRootBlock(link Link, mode bool) error { return ErrNotImplemented } func LinkSetFlood(link Link, mode bool) error { return ErrNotImplemented } func LinkSetTxQLen(link Link, qlen int) error { return ErrNotImplemented } func LinkSetGSOMaxSize(link Link, maxSize int) error { return ErrNotImplemented } func LinkSetGROMaxSize(link Link, maxSize int) error { return ErrNotImplemented } func LinkSetGSOIPv4MaxSize(link Link, maxSize int) error { return ErrNotImplemented } func LinkSetGROIPv4MaxSize(link Link, maxSize int) error { return ErrNotImplemented } func LinkAdd(link Link) error { return ErrNotImplemented } func LinkDel(link Link) error { return ErrNotImplemented } func SetHairpin(link Link, mode bool) error { return ErrNotImplemented } func SetGuard(link Link, mode bool) error { return ErrNotImplemented } func SetFastLeave(link Link, mode bool) error { return ErrNotImplemented } func SetLearning(link Link, mode bool) error { return ErrNotImplemented } func SetRootBlock(link Link, mode bool) error { return ErrNotImplemented } func SetFlood(link Link, mode bool) error { return ErrNotImplemented } func LinkList() ([]Link, error) { return nil, ErrNotImplemented } func AddrAdd(link Link, addr *Addr) error { return ErrNotImplemented } func AddrReplace(link Link, addr *Addr) error { return ErrNotImplemented } func AddrDel(link Link, addr *Addr) error { return ErrNotImplemented } func AddrList(link Link, family int) ([]Addr, error) { return nil, ErrNotImplemented } func RouteAdd(route *Route) error { return ErrNotImplemented } func RouteAppend(route *Route) error { return ErrNotImplemented } func RouteChange(route *Route) error { return ErrNotImplemented } func RouteDel(route *Route) error { return ErrNotImplemented } func RouteGet(destination net.IP) ([]Route, error) { return nil, ErrNotImplemented } func RouteList(link Link, family int) ([]Route, error) { return nil, ErrNotImplemented } func RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { return nil, ErrNotImplemented } func RouteReplace(route *Route) error { return ErrNotImplemented } func XfrmPolicyAdd(policy *XfrmPolicy) error { return ErrNotImplemented } func XfrmPolicyDel(policy *XfrmPolicy) error { return ErrNotImplemented } func XfrmPolicyList(family int) ([]XfrmPolicy, error) { return nil, ErrNotImplemented } func XfrmPolicyGet(policy *XfrmPolicy) (*XfrmPolicy, error) { return nil, ErrNotImplemented } func XfrmStateAdd(policy *XfrmState) error { return ErrNotImplemented } func XfrmStateDel(policy *XfrmState) error { return ErrNotImplemented } func XfrmStateList(family int) ([]XfrmState, error) { return nil, ErrNotImplemented } func NeighAdd(neigh *Neigh) error { return ErrNotImplemented } func NeighSet(neigh *Neigh) error { return ErrNotImplemented } func NeighAppend(neigh *Neigh) error { return ErrNotImplemented } func NeighDel(neigh *Neigh) error { return ErrNotImplemented } func NeighList(linkIndex, family int) ([]Neigh, error) { return nil, ErrNotImplemented } func NeighDeserialize(m []byte) (*Neigh, error) { return nil, ErrNotImplemented } func SocketGet(local, remote net.Addr) (*Socket, error) { return nil, ErrNotImplemented } func SocketDestroy(local, remote net.Addr) (*Socket, error) { return nil, ErrNotImplemented } netlink-1.3.0/netns_linux.go000066400000000000000000000105441466216277000160730ustar00rootroot00000000000000package netlink // Network namespace ID functions // // The kernel has a weird concept called the network namespace ID. // This is different from the file reference in proc (and any bind-mounted // namespaces, etc.) // // Instead, namespaces can be assigned a numeric ID at any time. Once set, // the ID is fixed. The ID can either be set manually by the user, or // automatically, triggered by certain kernel actions. The most common kernel // action that triggers namespace ID creation is moving one end of a veth pair // in to that namespace. import ( "fmt" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // These can be replaced by the values from sys/unix when it is next released. const ( _ = iota NETNSA_NSID NETNSA_PID NETNSA_FD ) // GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id). // Returns -1 if the namespace does not have an ID set. func (h *Handle) GetNetNsIdByPid(pid int) (int, error) { return h.getNetNsId(NETNSA_PID, uint32(pid)) } // GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id). // Returns -1 if the namespace does not have an ID set. func GetNetNsIdByPid(pid int) (int, error) { return pkgHandle.GetNetNsIdByPid(pid) } // SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id). // The ID can only be set for namespaces without an ID already set. func (h *Handle) SetNetNsIdByPid(pid, nsid int) error { return h.setNetNsId(NETNSA_PID, uint32(pid), uint32(nsid)) } // SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id). // The ID can only be set for namespaces without an ID already set. func SetNetNsIdByPid(pid, nsid int) error { return pkgHandle.SetNetNsIdByPid(pid, nsid) } // GetNetNsIdByFd looks up the network namespace ID for a given fd. // fd must be an open file descriptor to a namespace file. // Returns -1 if the namespace does not have an ID set. func (h *Handle) GetNetNsIdByFd(fd int) (int, error) { return h.getNetNsId(NETNSA_FD, uint32(fd)) } // GetNetNsIdByFd looks up the network namespace ID for a given fd. // fd must be an open file descriptor to a namespace file. // Returns -1 if the namespace does not have an ID set. func GetNetNsIdByFd(fd int) (int, error) { return pkgHandle.GetNetNsIdByFd(fd) } // SetNetNSIdByFd sets the ID of the network namespace for a given fd. // fd must be an open file descriptor to a namespace file. // The ID can only be set for namespaces without an ID already set. func (h *Handle) SetNetNsIdByFd(fd, nsid int) error { return h.setNetNsId(NETNSA_FD, uint32(fd), uint32(nsid)) } // SetNetNSIdByFd sets the ID of the network namespace for a given fd. // fd must be an open file descriptor to a namespace file. // The ID can only be set for namespaces without an ID already set. func SetNetNsIdByFd(fd, nsid int) error { return pkgHandle.SetNetNsIdByFd(fd, nsid) } // getNetNsId requests the netnsid for a given type-val pair // type should be either NETNSA_PID or NETNSA_FD func (h *Handle) getNetNsId(attrType int, val uint32) (int, error) { req := h.newNetlinkRequest(unix.RTM_GETNSID, unix.NLM_F_REQUEST) rtgen := nl.NewRtGenMsg() req.AddData(rtgen) b := make([]byte, 4) native.PutUint32(b, val) attr := nl.NewRtAttr(attrType, b) req.AddData(attr) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID) if err != nil { return 0, err } for _, m := range msgs { msg := nl.DeserializeRtGenMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return 0, err } for _, attr := range attrs { switch attr.Attr.Type { case NETNSA_NSID: return int(int32(native.Uint32(attr.Value))), nil } } } return 0, fmt.Errorf("unexpected empty result") } // setNetNsId sets the netnsid for a given type-val pair // type should be either NETNSA_PID or NETNSA_FD // The ID can only be set for namespaces without an ID already set func (h *Handle) setNetNsId(attrType int, val uint32, newnsid uint32) error { req := h.newNetlinkRequest(unix.RTM_NEWNSID, unix.NLM_F_REQUEST|unix.NLM_F_ACK) rtgen := nl.NewRtGenMsg() req.AddData(rtgen) b := make([]byte, 4) native.PutUint32(b, val) attr := nl.NewRtAttr(attrType, b) req.AddData(attr) b1 := make([]byte, 4) native.PutUint32(b1, newnsid) attr1 := nl.NewRtAttr(NETNSA_NSID, b1) req.AddData(attr1) _, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID) return err } netlink-1.3.0/netns_test.go000066400000000000000000000040541466216277000157120ustar00rootroot00000000000000// +build linux package netlink import ( "os" "runtime" "syscall" "testing" "github.com/vishvananda/netns" ) // TestNetNsIdByFd tests setting and getting the network namespace ID // by file descriptor. It opens a namespace fd, sets it to a random id, // then retrieves the ID. // This does not do any namespace switching. func TestNetNsIdByFd(t *testing.T) { skipUnlessRoot(t) // create a network namespace ns, err := netns.New() CheckErrorFail(t, err) // set its ID // In an attempt to avoid namespace id collisions, set this to something // insanely high. When the kernel assigns IDs, it does so starting from 0 // So, just use our pid shifted up 16 bits wantID := os.Getpid() << 16 h, err := NewHandle() CheckErrorFail(t, err) err = h.SetNetNsIdByFd(int(ns), wantID) CheckErrorFail(t, err) // Get the ID back, make sure it matches haveID, _ := h.GetNetNsIdByFd(int(ns)) if haveID != wantID { t.Errorf("GetNetNsIdByFd returned %d, want %d", haveID, wantID) } ns.Close() } // TestNetNsIdByPid tests manipulating namespace IDs by pid (really, task / thread id) // Does the same as TestNetNsIdByFd, but we need to change namespaces so we // actually have a pid in that namespace func TestNetNsIdByPid(t *testing.T) { skipUnlessRoot(t) runtime.LockOSThread() // we need a constant OS thread origNs, _ := netns.Get() // create and enter a new netns ns, err := netns.New() CheckErrorFail(t, err) err = netns.Set(ns) CheckErrorFail(t, err) // make sure we go back to the original namespace when done defer func() { err := netns.Set(origNs) if err != nil { panic("failed to restore network ns, bailing") } runtime.UnlockOSThread() }() // As above, we'll pick a crazy large netnsid to avoid collisions wantID := syscall.Gettid() << 16 h, err := NewHandle() CheckErrorFail(t, err) err = h.SetNetNsIdByPid(syscall.Gettid(), wantID) CheckErrorFail(t, err) //Get the ID and see if it worked haveID, _ := h.GetNetNsIdByPid(syscall.Gettid()) if haveID != wantID { t.Errorf("GetNetNsIdByPid returned %d, want %d", haveID, wantID) } } netlink-1.3.0/netns_unspecified.go000066400000000000000000000005121466216277000172240ustar00rootroot00000000000000// +build !linux package netlink func GetNetNsIdByPid(pid int) (int, error) { return 0, ErrNotImplemented } func SetNetNsIdByPid(pid, nsid int) error { return ErrNotImplemented } func GetNetNsIdByFd(fd int) (int, error) { return 0, ErrNotImplemented } func SetNetNsIdByFd(fd, nsid int) error { return ErrNotImplemented } netlink-1.3.0/nl/000077500000000000000000000000001466216277000136035ustar00rootroot00000000000000netlink-1.3.0/nl/addr_linux.go000066400000000000000000000027531466216277000162720ustar00rootroot00000000000000package nl import ( "unsafe" "golang.org/x/sys/unix" ) type IfAddrmsg struct { unix.IfAddrmsg } func NewIfAddrmsg(family int) *IfAddrmsg { return &IfAddrmsg{ IfAddrmsg: unix.IfAddrmsg{ Family: uint8(family), }, } } // struct ifaddrmsg { // __u8 ifa_family; // __u8 ifa_prefixlen; /* The prefix length */ // __u8 ifa_flags; /* Flags */ // __u8 ifa_scope; /* Address scope */ // __u32 ifa_index; /* Link index */ // }; // type IfAddrmsg struct { // Family uint8 // Prefixlen uint8 // Flags uint8 // Scope uint8 // Index uint32 // } // SizeofIfAddrmsg = 0x8 func DeserializeIfAddrmsg(b []byte) *IfAddrmsg { return (*IfAddrmsg)(unsafe.Pointer(&b[0:unix.SizeofIfAddrmsg][0])) } func (msg *IfAddrmsg) Serialize() []byte { return (*(*[unix.SizeofIfAddrmsg]byte)(unsafe.Pointer(msg)))[:] } func (msg *IfAddrmsg) Len() int { return unix.SizeofIfAddrmsg } // struct ifa_cacheinfo { // __u32 ifa_prefered; // __u32 ifa_valid; // __u32 cstamp; /* created timestamp, hundredths of seconds */ // __u32 tstamp; /* updated timestamp, hundredths of seconds */ // }; type IfaCacheInfo struct { unix.IfaCacheinfo } func (msg *IfaCacheInfo) Len() int { return unix.SizeofIfaCacheinfo } func DeserializeIfaCacheInfo(b []byte) *IfaCacheInfo { return (*IfaCacheInfo)(unsafe.Pointer(&b[0:unix.SizeofIfaCacheinfo][0])) } func (msg *IfaCacheInfo) Serialize() []byte { return (*(*[unix.SizeofIfaCacheinfo]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/addr_linux_test.go000066400000000000000000000032141466216277000173220ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" "golang.org/x/sys/unix" ) func (msg *IfAddrmsg) write(b []byte) { native := NativeEndian() b[0] = msg.Family b[1] = msg.Prefixlen b[2] = msg.Flags b[3] = msg.Scope native.PutUint32(b[4:8], msg.Index) } func (msg *IfAddrmsg) serializeSafe() []byte { len := unix.SizeofIfAddrmsg b := make([]byte, len) msg.write(b) return b } func deserializeIfAddrmsgSafe(b []byte) *IfAddrmsg { var msg = IfAddrmsg{} binary.Read(bytes.NewReader(b[0:unix.SizeofIfAddrmsg]), NativeEndian(), &msg) return &msg } func TestIfAddrmsgDeserializeSerialize(t *testing.T) { var orig = make([]byte, unix.SizeofIfAddrmsg) rand.Read(orig) safemsg := deserializeIfAddrmsgSafe(orig) msg := DeserializeIfAddrmsg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *IfaCacheInfo) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Prefered)) native.PutUint32(b[4:8], uint32(msg.Valid)) native.PutUint32(b[8:12], uint32(msg.Cstamp)) native.PutUint32(b[12:16], uint32(msg.Tstamp)) } func (msg *IfaCacheInfo) serializeSafe() []byte { length := unix.SizeofIfaCacheinfo b := make([]byte, length) msg.write(b) return b } func deserializeIfaCacheInfoSafe(b []byte) *IfaCacheInfo { var msg = IfaCacheInfo{} binary.Read(bytes.NewReader(b[0:unix.SizeofIfaCacheinfo]), NativeEndian(), &msg) return &msg } func TestIfaCacheInfoDeserializeSerialize(t *testing.T) { var orig = make([]byte, unix.SizeofIfaCacheinfo) rand.Read(orig) safemsg := deserializeIfaCacheInfoSafe(orig) msg := DeserializeIfaCacheInfo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/bridge_linux.go000066400000000000000000000026101466216277000166040ustar00rootroot00000000000000package nl import ( "fmt" "unsafe" ) const ( SizeofBridgeVlanInfo = 0x04 ) /* Bridge Flags */ const ( BRIDGE_FLAGS_MASTER = iota + 1 /* Bridge command to/from master */ BRIDGE_FLAGS_SELF /* Bridge command to/from lowerdev */ ) /* Bridge management nested attributes * [IFLA_AF_SPEC] = { * [IFLA_BRIDGE_FLAGS] * [IFLA_BRIDGE_MODE] * [IFLA_BRIDGE_VLAN_INFO] * } */ const ( IFLA_BRIDGE_FLAGS = iota IFLA_BRIDGE_MODE IFLA_BRIDGE_VLAN_INFO ) const ( BRIDGE_VLAN_INFO_MASTER = 1 << iota BRIDGE_VLAN_INFO_PVID BRIDGE_VLAN_INFO_UNTAGGED BRIDGE_VLAN_INFO_RANGE_BEGIN BRIDGE_VLAN_INFO_RANGE_END ) // struct bridge_vlan_info { // __u16 flags; // __u16 vid; // }; type BridgeVlanInfo struct { Flags uint16 Vid uint16 } func (b *BridgeVlanInfo) Serialize() []byte { return (*(*[SizeofBridgeVlanInfo]byte)(unsafe.Pointer(b)))[:] } func DeserializeBridgeVlanInfo(b []byte) *BridgeVlanInfo { return (*BridgeVlanInfo)(unsafe.Pointer(&b[0:SizeofBridgeVlanInfo][0])) } func (b *BridgeVlanInfo) PortVID() bool { return b.Flags&BRIDGE_VLAN_INFO_PVID > 0 } func (b *BridgeVlanInfo) EngressUntag() bool { return b.Flags&BRIDGE_VLAN_INFO_UNTAGGED > 0 } func (b *BridgeVlanInfo) String() string { return fmt.Sprintf("%+v", *b) } /* New extended info filters for IFLA_EXT_MASK */ const ( RTEXT_FILTER_VF = 1 << iota RTEXT_FILTER_BRVLAN RTEXT_FILTER_BRVLAN_COMPRESSED ) netlink-1.3.0/nl/bridge_linux_test.go000066400000000000000000000014531466216277000176470ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *BridgeVlanInfo) write(b []byte) { native := NativeEndian() native.PutUint16(b[0:2], msg.Flags) native.PutUint16(b[2:4], msg.Vid) } func (msg *BridgeVlanInfo) serializeSafe() []byte { length := SizeofBridgeVlanInfo b := make([]byte, length) msg.write(b) return b } func deserializeBridgeVlanInfoSafe(b []byte) *BridgeVlanInfo { var msg = BridgeVlanInfo{} binary.Read(bytes.NewReader(b[0:SizeofBridgeVlanInfo]), NativeEndian(), &msg) return &msg } func TestBridgeVlanInfoDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofBridgeVlanInfo) rand.Read(orig) safemsg := deserializeBridgeVlanInfoSafe(orig) msg := DeserializeBridgeVlanInfo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/conntrack_linux.go000066400000000000000000000136221466216277000173370ustar00rootroot00000000000000package nl import "unsafe" // Track the message sizes for the correct serialization/deserialization const ( SizeofNfgenmsg = 4 SizeofNfattr = 4 SizeofNfConntrack = 376 SizeofNfctTupleHead = 52 ) var L4ProtoMap = map[uint8]string{ 6: "tcp", 17: "udp", } // From https://git.netfilter.org/libnetfilter_conntrack/tree/include/libnetfilter_conntrack/libnetfilter_conntrack_tcp.h // enum tcp_state { // TCP_CONNTRACK_NONE, // TCP_CONNTRACK_SYN_SENT, // TCP_CONNTRACK_SYN_RECV, // TCP_CONNTRACK_ESTABLISHED, // TCP_CONNTRACK_FIN_WAIT, // TCP_CONNTRACK_CLOSE_WAIT, // TCP_CONNTRACK_LAST_ACK, // TCP_CONNTRACK_TIME_WAIT, // TCP_CONNTRACK_CLOSE, // TCP_CONNTRACK_LISTEN, /* obsolete */ // #define TCP_CONNTRACK_SYN_SENT2 TCP_CONNTRACK_LISTEN // TCP_CONNTRACK_MAX, // TCP_CONNTRACK_IGNORE // }; const ( TCP_CONNTRACK_NONE = 0 TCP_CONNTRACK_SYN_SENT = 1 TCP_CONNTRACK_SYN_RECV = 2 TCP_CONNTRACK_ESTABLISHED = 3 TCP_CONNTRACK_FIN_WAIT = 4 TCP_CONNTRACK_CLOSE_WAIT = 5 TCP_CONNTRACK_LAST_ACK = 6 TCP_CONNTRACK_TIME_WAIT = 7 TCP_CONNTRACK_CLOSE = 8 TCP_CONNTRACK_LISTEN = 9 TCP_CONNTRACK_SYN_SENT2 = 9 TCP_CONNTRACK_MAX = 10 TCP_CONNTRACK_IGNORE = 11 ) // All the following constants are coming from: // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink_conntrack.h // enum cntl_msg_types { // IPCTNL_MSG_CT_NEW, // IPCTNL_MSG_CT_GET, // IPCTNL_MSG_CT_DELETE, // IPCTNL_MSG_CT_GET_CTRZERO, // IPCTNL_MSG_CT_GET_STATS_CPU, // IPCTNL_MSG_CT_GET_STATS, // IPCTNL_MSG_CT_GET_DYING, // IPCTNL_MSG_CT_GET_UNCONFIRMED, // // IPCTNL_MSG_MAX // }; const ( IPCTNL_MSG_CT_NEW = 0 IPCTNL_MSG_CT_GET = 1 IPCTNL_MSG_CT_DELETE = 2 ) // #define NFNETLINK_V0 0 const ( NFNETLINK_V0 = 0 ) const ( NLA_F_NESTED uint16 = (1 << 15) // #define NLA_F_NESTED (1 << 15) NLA_F_NET_BYTEORDER uint16 = (1 << 14) // #define NLA_F_NESTED (1 << 14) NLA_TYPE_MASK = ^(NLA_F_NESTED | NLA_F_NET_BYTEORDER) NLA_ALIGNTO uint16 = 4 // #define NLA_ALIGNTO 4 ) // enum ctattr_type { // CTA_UNSPEC, // CTA_TUPLE_ORIG, // CTA_TUPLE_REPLY, // CTA_STATUS, // CTA_PROTOINFO, // CTA_HELP, // CTA_NAT_SRC, // #define CTA_NAT CTA_NAT_SRC /* backwards compatibility */ // CTA_TIMEOUT, // CTA_MARK, // CTA_COUNTERS_ORIG, // CTA_COUNTERS_REPLY, // CTA_USE, // CTA_ID, // CTA_NAT_DST, // CTA_TUPLE_MASTER, // CTA_SEQ_ADJ_ORIG, // CTA_NAT_SEQ_ADJ_ORIG = CTA_SEQ_ADJ_ORIG, // CTA_SEQ_ADJ_REPLY, // CTA_NAT_SEQ_ADJ_REPLY = CTA_SEQ_ADJ_REPLY, // CTA_SECMARK, /* obsolete */ // CTA_ZONE, // CTA_SECCTX, // CTA_TIMESTAMP, // CTA_MARK_MASK, // CTA_LABELS, // CTA_LABELS_MASK, // __CTA_MAX // }; const ( CTA_TUPLE_ORIG = 1 CTA_TUPLE_REPLY = 2 CTA_STATUS = 3 CTA_PROTOINFO = 4 CTA_TIMEOUT = 7 CTA_MARK = 8 CTA_COUNTERS_ORIG = 9 CTA_COUNTERS_REPLY = 10 CTA_USE = 11 CTA_ID = 12 CTA_ZONE = 18 CTA_TIMESTAMP = 20 CTA_LABELS = 22 CTA_LABELS_MASK = 23 ) // enum ctattr_tuple { // CTA_TUPLE_UNSPEC, // CTA_TUPLE_IP, // CTA_TUPLE_PROTO, // CTA_TUPLE_ZONE, // __CTA_TUPLE_MAX // }; // #define CTA_TUPLE_MAX (__CTA_TUPLE_MAX - 1) const ( CTA_TUPLE_IP = 1 CTA_TUPLE_PROTO = 2 ) // enum ctattr_ip { // CTA_IP_UNSPEC, // CTA_IP_V4_SRC, // CTA_IP_V4_DST, // CTA_IP_V6_SRC, // CTA_IP_V6_DST, // __CTA_IP_MAX // }; // #define CTA_IP_MAX (__CTA_IP_MAX - 1) const ( CTA_IP_V4_SRC = 1 CTA_IP_V4_DST = 2 CTA_IP_V6_SRC = 3 CTA_IP_V6_DST = 4 ) // enum ctattr_l4proto { // CTA_PROTO_UNSPEC, // CTA_PROTO_NUM, // CTA_PROTO_SRC_PORT, // CTA_PROTO_DST_PORT, // CTA_PROTO_ICMP_ID, // CTA_PROTO_ICMP_TYPE, // CTA_PROTO_ICMP_CODE, // CTA_PROTO_ICMPV6_ID, // CTA_PROTO_ICMPV6_TYPE, // CTA_PROTO_ICMPV6_CODE, // __CTA_PROTO_MAX // }; // #define CTA_PROTO_MAX (__CTA_PROTO_MAX - 1) const ( CTA_PROTO_NUM = 1 CTA_PROTO_SRC_PORT = 2 CTA_PROTO_DST_PORT = 3 ) // enum ctattr_protoinfo { // CTA_PROTOINFO_UNSPEC, // CTA_PROTOINFO_TCP, // CTA_PROTOINFO_DCCP, // CTA_PROTOINFO_SCTP, // __CTA_PROTOINFO_MAX // }; // #define CTA_PROTOINFO_MAX (__CTA_PROTOINFO_MAX - 1) const ( CTA_PROTOINFO_UNSPEC = 0 CTA_PROTOINFO_TCP = 1 CTA_PROTOINFO_DCCP = 2 CTA_PROTOINFO_SCTP = 3 ) // enum ctattr_protoinfo_tcp { // CTA_PROTOINFO_TCP_UNSPEC, // CTA_PROTOINFO_TCP_STATE, // CTA_PROTOINFO_TCP_WSCALE_ORIGINAL, // CTA_PROTOINFO_TCP_WSCALE_REPLY, // CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, // CTA_PROTOINFO_TCP_FLAGS_REPLY, // __CTA_PROTOINFO_TCP_MAX // }; // #define CTA_PROTOINFO_TCP_MAX (__CTA_PROTOINFO_TCP_MAX - 1) const ( CTA_PROTOINFO_TCP_STATE = 1 CTA_PROTOINFO_TCP_WSCALE_ORIGINAL = 2 CTA_PROTOINFO_TCP_WSCALE_REPLY = 3 CTA_PROTOINFO_TCP_FLAGS_ORIGINAL = 4 CTA_PROTOINFO_TCP_FLAGS_REPLY = 5 ) // enum ctattr_counters { // CTA_COUNTERS_UNSPEC, // CTA_COUNTERS_PACKETS, /* 64bit counters */ // CTA_COUNTERS_BYTES, /* 64bit counters */ // CTA_COUNTERS32_PACKETS, /* old 32bit counters, unused */ // CTA_COUNTERS32_BYTES, /* old 32bit counters, unused */ // CTA_COUNTERS_PAD, // __CTA_COUNTERS_M // }; // #define CTA_COUNTERS_MAX (__CTA_COUNTERS_MAX - 1) const ( CTA_COUNTERS_PACKETS = 1 CTA_COUNTERS_BYTES = 2 ) // enum CTA TIMESTAMP TLVs // CTA_TIMESTAMP_START /* 64bit value */ // CTA_TIMESTAMP_STOP /* 64bit value */ const ( CTA_TIMESTAMP_START = 1 CTA_TIMESTAMP_STOP = 2 ) // /* General form of address family dependent message. // */ // struct nfgenmsg { // __u8 nfgen_family; /* AF_xxx */ // __u8 version; /* nfnetlink version */ // __be16 res_id; /* resource id */ // }; type Nfgenmsg struct { NfgenFamily uint8 Version uint8 ResId uint16 // big endian } func (msg *Nfgenmsg) Len() int { return SizeofNfgenmsg } func DeserializeNfgenmsg(b []byte) *Nfgenmsg { return (*Nfgenmsg)(unsafe.Pointer(&b[0:SizeofNfgenmsg][0])) } func (msg *Nfgenmsg) Serialize() []byte { return (*(*[SizeofNfgenmsg]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/devlink_linux.go000066400000000000000000000102231466216277000170030ustar00rootroot00000000000000package nl // All the following constants are coming from: // https://github.com/torvalds/linux/blob/master/include/uapi/linux/devlink.h const ( GENL_DEVLINK_VERSION = 1 GENL_DEVLINK_NAME = "devlink" ) const ( DEVLINK_CMD_GET = 1 DEVLINK_CMD_PORT_GET = 5 DEVLINK_CMD_PORT_SET = 6 DEVLINK_CMD_PORT_NEW = 7 DEVLINK_CMD_PORT_DEL = 8 DEVLINK_CMD_ESWITCH_GET = 29 DEVLINK_CMD_ESWITCH_SET = 30 DEVLINK_CMD_RESOURCE_DUMP = 36 DEVLINK_CMD_PARAM_GET = 38 DEVLINK_CMD_PARAM_SET = 39 DEVLINK_CMD_INFO_GET = 51 ) const ( DEVLINK_ATTR_BUS_NAME = 1 DEVLINK_ATTR_DEV_NAME = 2 DEVLINK_ATTR_PORT_INDEX = 3 DEVLINK_ATTR_PORT_TYPE = 4 DEVLINK_ATTR_PORT_NETDEV_IFINDEX = 6 DEVLINK_ATTR_PORT_NETDEV_NAME = 7 DEVLINK_ATTR_PORT_IBDEV_NAME = 8 DEVLINK_ATTR_ESWITCH_MODE = 25 DEVLINK_ATTR_ESWITCH_INLINE_MODE = 26 DEVLINK_ATTR_ESWITCH_ENCAP_MODE = 62 DEVLINK_ATTR_RESOURCE_LIST = 63 /* nested */ DEVLINK_ATTR_RESOURCE = 64 /* nested */ DEVLINK_ATTR_RESOURCE_NAME = 65 /* string */ DEVLINK_ATTR_RESOURCE_ID = 66 /* u64 */ DEVLINK_ATTR_RESOURCE_SIZE = 67 /* u64 */ DEVLINK_ATTR_RESOURCE_SIZE_NEW = 68 /* u64 */ DEVLINK_ATTR_RESOURCE_SIZE_VALID = 69 /* u8 */ DEVLINK_ATTR_RESOURCE_SIZE_MIN = 70 /* u64 */ DEVLINK_ATTR_RESOURCE_SIZE_MAX = 71 /* u64 */ DEVLINK_ATTR_RESOURCE_SIZE_GRAN = 72 /* u64 */ DEVLINK_ATTR_RESOURCE_UNIT = 73 /* u8 */ DEVLINK_ATTR_RESOURCE_OCC = 74 /* u64 */ DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID = 75 /* u64 */ DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS = 76 /* u64 */ DEVLINK_ATTR_PORT_FLAVOUR = 77 DEVLINK_ATTR_INFO_DRIVER_NAME = 98 DEVLINK_ATTR_INFO_SERIAL_NUMBER = 99 DEVLINK_ATTR_INFO_VERSION_FIXED = 100 DEVLINK_ATTR_INFO_VERSION_RUNNING = 101 DEVLINK_ATTR_INFO_VERSION_STORED = 102 DEVLINK_ATTR_INFO_VERSION_NAME = 103 DEVLINK_ATTR_INFO_VERSION_VALUE = 104 DEVLINK_ATTR_PORT_PCI_PF_NUMBER = 127 DEVLINK_ATTR_PORT_FUNCTION = 145 DEVLINK_ATTR_PORT_CONTROLLER_NUMBER = 150 DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 164 ) const ( DEVLINK_ESWITCH_MODE_LEGACY = 0 DEVLINK_ESWITCH_MODE_SWITCHDEV = 1 ) const ( DEVLINK_ESWITCH_INLINE_MODE_NONE = 0 DEVLINK_ESWITCH_INLINE_MODE_LINK = 1 DEVLINK_ESWITCH_INLINE_MODE_NETWORK = 2 DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT = 3 ) const ( DEVLINK_ESWITCH_ENCAP_MODE_NONE = 0 DEVLINK_ESWITCH_ENCAP_MODE_BASIC = 1 ) const ( DEVLINK_PORT_FLAVOUR_PHYSICAL = 0 DEVLINK_PORT_FLAVOUR_CPU = 1 DEVLINK_PORT_FLAVOUR_DSA = 2 DEVLINK_PORT_FLAVOUR_PCI_PF = 3 DEVLINK_PORT_FLAVOUR_PCI_VF = 4 DEVLINK_PORT_FLAVOUR_VIRTUAL = 5 DEVLINK_PORT_FLAVOUR_UNUSED = 6 DEVLINK_PORT_FLAVOUR_PCI_SF = 7 ) const ( DEVLINK_PORT_TYPE_NOTSET = 0 DEVLINK_PORT_TYPE_AUTO = 1 DEVLINK_PORT_TYPE_ETH = 2 DEVLINK_PORT_TYPE_IB = 3 ) const ( DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR = 1 DEVLINK_PORT_FN_ATTR_STATE = 2 DEVLINK_PORT_FN_ATTR_OPSTATE = 3 ) const ( DEVLINK_PORT_FN_STATE_INACTIVE = 0 DEVLINK_PORT_FN_STATE_ACTIVE = 1 ) const ( DEVLINK_PORT_FN_OPSTATE_DETACHED = 0 DEVLINK_PORT_FN_OPSTATE_ATTACHED = 1 ) const ( DEVLINK_RESOURCE_UNIT_ENTRY uint8 = 0 ) const ( DEVLINK_ATTR_PARAM = iota + 80 /* nested */ DEVLINK_ATTR_PARAM_NAME /* string */ DEVLINK_ATTR_PARAM_GENERIC /* flag */ DEVLINK_ATTR_PARAM_TYPE /* u8 */ DEVLINK_ATTR_PARAM_VALUES_LIST /* nested */ DEVLINK_ATTR_PARAM_VALUE /* nested */ DEVLINK_ATTR_PARAM_VALUE_DATA /* dynamic */ DEVLINK_ATTR_PARAM_VALUE_CMODE /* u8 */ ) const ( DEVLINK_PARAM_TYPE_U8 = 1 DEVLINK_PARAM_TYPE_U16 = 2 DEVLINK_PARAM_TYPE_U32 = 3 DEVLINK_PARAM_TYPE_STRING = 5 DEVLINK_PARAM_TYPE_BOOL = 6 ) const ( DEVLINK_PARAM_CMODE_RUNTIME = iota DEVLINK_PARAM_CMODE_DRIVERINIT DEVLINK_PARAM_CMODE_PERMANENT ) netlink-1.3.0/nl/genetlink_linux.go000066400000000000000000000026221466216277000173330ustar00rootroot00000000000000package nl import ( "unsafe" ) const SizeofGenlmsg = 4 const ( GENL_ID_CTRL = 0x10 GENL_CTRL_VERSION = 2 GENL_CTRL_NAME = "nlctrl" ) const ( GENL_CTRL_CMD_GETFAMILY = 3 ) const ( GENL_CTRL_ATTR_UNSPEC = iota GENL_CTRL_ATTR_FAMILY_ID GENL_CTRL_ATTR_FAMILY_NAME GENL_CTRL_ATTR_VERSION GENL_CTRL_ATTR_HDRSIZE GENL_CTRL_ATTR_MAXATTR GENL_CTRL_ATTR_OPS GENL_CTRL_ATTR_MCAST_GROUPS ) const ( GENL_CTRL_ATTR_OP_UNSPEC = iota GENL_CTRL_ATTR_OP_ID GENL_CTRL_ATTR_OP_FLAGS ) const ( GENL_ADMIN_PERM = 1 << iota GENL_CMD_CAP_DO GENL_CMD_CAP_DUMP GENL_CMD_CAP_HASPOL ) const ( GENL_CTRL_ATTR_MCAST_GRP_UNSPEC = iota GENL_CTRL_ATTR_MCAST_GRP_NAME GENL_CTRL_ATTR_MCAST_GRP_ID ) const ( GENL_GTP_VERSION = 0 GENL_GTP_NAME = "gtp" ) const ( GENL_GTP_CMD_NEWPDP = iota GENL_GTP_CMD_DELPDP GENL_GTP_CMD_GETPDP ) const ( GENL_GTP_ATTR_UNSPEC = iota GENL_GTP_ATTR_LINK GENL_GTP_ATTR_VERSION GENL_GTP_ATTR_TID GENL_GTP_ATTR_PEER_ADDRESS GENL_GTP_ATTR_MS_ADDRESS GENL_GTP_ATTR_FLOW GENL_GTP_ATTR_NET_NS_FD GENL_GTP_ATTR_I_TEI GENL_GTP_ATTR_O_TEI GENL_GTP_ATTR_PAD ) type Genlmsg struct { Command uint8 Version uint8 } func (msg *Genlmsg) Len() int { return SizeofGenlmsg } func DeserializeGenlmsg(b []byte) *Genlmsg { return (*Genlmsg)(unsafe.Pointer(&b[0:SizeofGenlmsg][0])) } func (msg *Genlmsg) Serialize() []byte { return (*(*[SizeofGenlmsg]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/ip6tnl_linux.go000066400000000000000000000007551466216277000165740ustar00rootroot00000000000000package nl // id's of route attribute from https://elixir.bootlin.com/linux/v5.17.3/source/include/uapi/linux/lwtunnel.h#L38 // the value's size are specified in https://elixir.bootlin.com/linux/v5.17.3/source/net/ipv4/ip_tunnel_core.c#L928 const ( LWTUNNEL_IP6_UNSPEC = iota LWTUNNEL_IP6_ID LWTUNNEL_IP6_DST LWTUNNEL_IP6_SRC LWTUNNEL_IP6_HOPLIMIT LWTUNNEL_IP6_TC LWTUNNEL_IP6_FLAGS LWTUNNEL_IP6_PAD // not implemented LWTUNNEL_IP6_OPTS // not implemented __LWTUNNEL_IP6_MAX ) netlink-1.3.0/nl/ipset_linux.go000066400000000000000000000135321466216277000165010ustar00rootroot00000000000000package nl import ( "strconv" "golang.org/x/sys/unix" ) const ( /* The protocol version */ IPSET_PROTOCOL = 6 /* The max length of strings including NUL: set and type identifiers */ IPSET_MAXNAMELEN = 32 /* The maximum permissible comment length we will accept over netlink */ IPSET_MAX_COMMENT_SIZE = 255 ) const ( _ = iota IPSET_CMD_PROTOCOL /* 1: Return protocol version */ IPSET_CMD_CREATE /* 2: Create a new (empty) set */ IPSET_CMD_DESTROY /* 3: Destroy a (empty) set */ IPSET_CMD_FLUSH /* 4: Remove all elements from a set */ IPSET_CMD_RENAME /* 5: Rename a set */ IPSET_CMD_SWAP /* 6: Swap two sets */ IPSET_CMD_LIST /* 7: List sets */ IPSET_CMD_SAVE /* 8: Save sets */ IPSET_CMD_ADD /* 9: Add an element to a set */ IPSET_CMD_DEL /* 10: Delete an element from a set */ IPSET_CMD_TEST /* 11: Test an element in a set */ IPSET_CMD_HEADER /* 12: Get set header data only */ IPSET_CMD_TYPE /* 13: Get set type */ ) /* Attributes at command level */ const ( _ = iota IPSET_ATTR_PROTOCOL /* 1: Protocol version */ IPSET_ATTR_SETNAME /* 2: Name of the set */ IPSET_ATTR_TYPENAME /* 3: Typename */ IPSET_ATTR_REVISION /* 4: Settype revision */ IPSET_ATTR_FAMILY /* 5: Settype family */ IPSET_ATTR_FLAGS /* 6: Flags at command level */ IPSET_ATTR_DATA /* 7: Nested attributes */ IPSET_ATTR_ADT /* 8: Multiple data containers */ IPSET_ATTR_LINENO /* 9: Restore lineno */ IPSET_ATTR_PROTOCOL_MIN /* 10: Minimal supported version number */ IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME /* Setname at rename/swap */ IPSET_ATTR_REVISION_MIN = IPSET_ATTR_PROTOCOL_MIN /* type rev min */ ) /* CADT specific attributes */ const ( IPSET_ATTR_IP = 1 IPSET_ATTR_IP_FROM = 1 IPSET_ATTR_IP_TO = 2 IPSET_ATTR_CIDR = 3 IPSET_ATTR_PORT = 4 IPSET_ATTR_PORT_FROM = 4 IPSET_ATTR_PORT_TO = 5 IPSET_ATTR_TIMEOUT = 6 IPSET_ATTR_PROTO = 7 IPSET_ATTR_CADT_FLAGS = 8 IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO /* 9 */ IPSET_ATTR_MARK = 10 IPSET_ATTR_MARKMASK = 11 /* Reserve empty slots */ IPSET_ATTR_CADT_MAX = 16 /* Create-only specific attributes */ IPSET_ATTR_GC = 3 + iota IPSET_ATTR_HASHSIZE IPSET_ATTR_MAXELEM IPSET_ATTR_NETMASK IPSET_ATTR_PROBES IPSET_ATTR_RESIZE IPSET_ATTR_SIZE /* Kernel-only */ IPSET_ATTR_ELEMENTS IPSET_ATTR_REFERENCES IPSET_ATTR_MEMSIZE SET_ATTR_CREATE_MAX ) const ( IPSET_ATTR_IPADDR_IPV4 = 1 IPSET_ATTR_IPADDR_IPV6 = 2 ) /* ADT specific attributes */ const ( IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + iota + 1 IPSET_ATTR_NAME IPSET_ATTR_NAMEREF IPSET_ATTR_IP2 IPSET_ATTR_CIDR2 IPSET_ATTR_IP2_TO IPSET_ATTR_IFACE IPSET_ATTR_BYTES IPSET_ATTR_PACKETS IPSET_ATTR_COMMENT IPSET_ATTR_SKBMARK IPSET_ATTR_SKBPRIO IPSET_ATTR_SKBQUEUE ) /* Flags at CADT attribute level, upper half of cmdattrs */ const ( IPSET_FLAG_BIT_BEFORE = 0 IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE) IPSET_FLAG_BIT_PHYSDEV = 1 IPSET_FLAG_PHYSDEV = (1 << IPSET_FLAG_BIT_PHYSDEV) IPSET_FLAG_BIT_NOMATCH = 2 IPSET_FLAG_NOMATCH = (1 << IPSET_FLAG_BIT_NOMATCH) IPSET_FLAG_BIT_WITH_COUNTERS = 3 IPSET_FLAG_WITH_COUNTERS = (1 << IPSET_FLAG_BIT_WITH_COUNTERS) IPSET_FLAG_BIT_WITH_COMMENT = 4 IPSET_FLAG_WITH_COMMENT = (1 << IPSET_FLAG_BIT_WITH_COMMENT) IPSET_FLAG_BIT_WITH_FORCEADD = 5 IPSET_FLAG_WITH_FORCEADD = (1 << IPSET_FLAG_BIT_WITH_FORCEADD) IPSET_FLAG_BIT_WITH_SKBINFO = 6 IPSET_FLAG_WITH_SKBINFO = (1 << IPSET_FLAG_BIT_WITH_SKBINFO) IPSET_FLAG_CADT_MAX = 15 ) const ( IPSET_ERR_PRIVATE = 4096 + iota IPSET_ERR_PROTOCOL IPSET_ERR_FIND_TYPE IPSET_ERR_MAX_SETS IPSET_ERR_BUSY IPSET_ERR_EXIST_SETNAME2 IPSET_ERR_TYPE_MISMATCH IPSET_ERR_EXIST IPSET_ERR_INVALID_CIDR IPSET_ERR_INVALID_NETMASK IPSET_ERR_INVALID_FAMILY IPSET_ERR_TIMEOUT IPSET_ERR_REFERENCED IPSET_ERR_IPADDR_IPV4 IPSET_ERR_IPADDR_IPV6 IPSET_ERR_COUNTER IPSET_ERR_COMMENT IPSET_ERR_INVALID_MARKMASK IPSET_ERR_SKBINFO /* Type specific error codes */ IPSET_ERR_TYPE_SPECIFIC = 4352 ) type IPSetError uintptr func (e IPSetError) Error() string { switch int(e) { case IPSET_ERR_PRIVATE: return "private" case IPSET_ERR_PROTOCOL: return "invalid protocol" case IPSET_ERR_FIND_TYPE: return "invalid type" case IPSET_ERR_MAX_SETS: return "max sets reached" case IPSET_ERR_BUSY: return "busy" case IPSET_ERR_EXIST_SETNAME2: return "exist_setname2" case IPSET_ERR_TYPE_MISMATCH: return "type mismatch" case IPSET_ERR_EXIST: return "exist" case IPSET_ERR_INVALID_CIDR: return "invalid cidr" case IPSET_ERR_INVALID_NETMASK: return "invalid netmask" case IPSET_ERR_INVALID_FAMILY: return "invalid family" case IPSET_ERR_TIMEOUT: return "timeout" case IPSET_ERR_REFERENCED: return "referenced" case IPSET_ERR_IPADDR_IPV4: return "invalid ipv4 address" case IPSET_ERR_IPADDR_IPV6: return "invalid ipv6 address" case IPSET_ERR_COUNTER: return "invalid counter" case IPSET_ERR_COMMENT: return "invalid comment" case IPSET_ERR_INVALID_MARKMASK: return "invalid markmask" case IPSET_ERR_SKBINFO: return "skbinfo" default: return "errno " + strconv.Itoa(int(e)) } } func GetIpsetFlags(cmd int) int { switch cmd { case IPSET_CMD_CREATE: return unix.NLM_F_REQUEST | unix.NLM_F_ACK | unix.NLM_F_CREATE case IPSET_CMD_DESTROY, IPSET_CMD_FLUSH, IPSET_CMD_RENAME, IPSET_CMD_SWAP, IPSET_CMD_TEST: return unix.NLM_F_REQUEST | unix.NLM_F_ACK case IPSET_CMD_LIST, IPSET_CMD_SAVE: return unix.NLM_F_REQUEST | unix.NLM_F_ACK | unix.NLM_F_ROOT | unix.NLM_F_MATCH | unix.NLM_F_DUMP case IPSET_CMD_ADD, IPSET_CMD_DEL: return unix.NLM_F_REQUEST | unix.NLM_F_ACK case IPSET_CMD_HEADER, IPSET_CMD_TYPE, IPSET_CMD_PROTOCOL: return unix.NLM_F_REQUEST default: return 0 } } netlink-1.3.0/nl/link_linux.go000066400000000000000000000407631466216277000163200ustar00rootroot00000000000000package nl import ( "bytes" "encoding/binary" "fmt" "unsafe" ) const ( DEFAULT_CHANGE = 0xFFFFFFFF ) const ( IFLA_INFO_UNSPEC = iota IFLA_INFO_KIND IFLA_INFO_DATA IFLA_INFO_XSTATS IFLA_INFO_SLAVE_KIND IFLA_INFO_SLAVE_DATA IFLA_INFO_MAX = IFLA_INFO_SLAVE_DATA ) const ( IFLA_VLAN_UNSPEC = iota IFLA_VLAN_ID IFLA_VLAN_FLAGS IFLA_VLAN_EGRESS_QOS IFLA_VLAN_INGRESS_QOS IFLA_VLAN_PROTOCOL IFLA_VLAN_MAX = IFLA_VLAN_PROTOCOL ) const ( IFLA_NETKIT_UNSPEC = iota IFLA_NETKIT_PEER_INFO IFLA_NETKIT_PRIMARY IFLA_NETKIT_POLICY IFLA_NETKIT_PEER_POLICY IFLA_NETKIT_MODE IFLA_NETKIT_MAX = IFLA_NETKIT_MODE ) const ( VETH_INFO_UNSPEC = iota VETH_INFO_PEER VETH_INFO_MAX = VETH_INFO_PEER ) const ( IFLA_VXLAN_UNSPEC = iota IFLA_VXLAN_ID IFLA_VXLAN_GROUP IFLA_VXLAN_LINK IFLA_VXLAN_LOCAL IFLA_VXLAN_TTL IFLA_VXLAN_TOS IFLA_VXLAN_LEARNING IFLA_VXLAN_AGEING IFLA_VXLAN_LIMIT IFLA_VXLAN_PORT_RANGE IFLA_VXLAN_PROXY IFLA_VXLAN_RSC IFLA_VXLAN_L2MISS IFLA_VXLAN_L3MISS IFLA_VXLAN_PORT IFLA_VXLAN_GROUP6 IFLA_VXLAN_LOCAL6 IFLA_VXLAN_UDP_CSUM IFLA_VXLAN_UDP_ZERO_CSUM6_TX IFLA_VXLAN_UDP_ZERO_CSUM6_RX IFLA_VXLAN_REMCSUM_TX IFLA_VXLAN_REMCSUM_RX IFLA_VXLAN_GBP IFLA_VXLAN_REMCSUM_NOPARTIAL IFLA_VXLAN_FLOWBASED IFLA_VXLAN_MAX = IFLA_VXLAN_FLOWBASED ) const ( BRIDGE_MODE_UNSPEC = iota BRIDGE_MODE_HAIRPIN ) const ( IFLA_BRPORT_UNSPEC = iota IFLA_BRPORT_STATE IFLA_BRPORT_PRIORITY IFLA_BRPORT_COST IFLA_BRPORT_MODE IFLA_BRPORT_GUARD IFLA_BRPORT_PROTECT IFLA_BRPORT_FAST_LEAVE IFLA_BRPORT_LEARNING IFLA_BRPORT_UNICAST_FLOOD IFLA_BRPORT_PROXYARP IFLA_BRPORT_LEARNING_SYNC IFLA_BRPORT_PROXYARP_WIFI IFLA_BRPORT_ROOT_ID IFLA_BRPORT_BRIDGE_ID IFLA_BRPORT_DESIGNATED_PORT IFLA_BRPORT_DESIGNATED_COST IFLA_BRPORT_ID IFLA_BRPORT_NO IFLA_BRPORT_TOPOLOGY_CHANGE_ACK IFLA_BRPORT_CONFIG_PENDING IFLA_BRPORT_MESSAGE_AGE_TIMER IFLA_BRPORT_FORWARD_DELAY_TIMER IFLA_BRPORT_HOLD_TIMER IFLA_BRPORT_FLUSH IFLA_BRPORT_MULTICAST_ROUTER IFLA_BRPORT_PAD IFLA_BRPORT_MCAST_FLOOD IFLA_BRPORT_MCAST_TO_UCAST IFLA_BRPORT_VLAN_TUNNEL IFLA_BRPORT_BCAST_FLOOD IFLA_BRPORT_GROUP_FWD_MASK IFLA_BRPORT_NEIGH_SUPPRESS IFLA_BRPORT_ISOLATED IFLA_BRPORT_BACKUP_PORT IFLA_BRPORT_MRP_RING_OPEN IFLA_BRPORT_MRP_IN_OPEN IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT IFLA_BRPORT_MCAST_EHT_HOSTS_CNT IFLA_BRPORT_LOCKED IFLA_BRPORT_MAB IFLA_BRPORT_MCAST_N_GROUPS IFLA_BRPORT_MCAST_MAX_GROUPS IFLA_BRPORT_MAX = IFLA_BRPORT_MCAST_MAX_GROUPS ) const ( IFLA_IPVLAN_UNSPEC = iota IFLA_IPVLAN_MODE IFLA_IPVLAN_FLAG IFLA_IPVLAN_MAX = IFLA_IPVLAN_FLAG ) const ( IFLA_MACVLAN_UNSPEC = iota IFLA_MACVLAN_MODE IFLA_MACVLAN_FLAGS IFLA_MACVLAN_MACADDR_MODE IFLA_MACVLAN_MACADDR IFLA_MACVLAN_MACADDR_DATA IFLA_MACVLAN_MACADDR_COUNT IFLA_MACVLAN_BC_QUEUE_LEN IFLA_MACVLAN_BC_QUEUE_LEN_USED IFLA_MACVLAN_MAX = IFLA_MACVLAN_BC_QUEUE_LEN_USED ) const ( MACVLAN_MODE_PRIVATE = 1 MACVLAN_MODE_VEPA = 2 MACVLAN_MODE_BRIDGE = 4 MACVLAN_MODE_PASSTHRU = 8 MACVLAN_MODE_SOURCE = 16 ) const ( MACVLAN_MACADDR_ADD = iota MACVLAN_MACADDR_DEL MACVLAN_MACADDR_FLUSH MACVLAN_MACADDR_SET ) const ( IFLA_BOND_UNSPEC = iota IFLA_BOND_MODE IFLA_BOND_ACTIVE_SLAVE IFLA_BOND_MIIMON IFLA_BOND_UPDELAY IFLA_BOND_DOWNDELAY IFLA_BOND_USE_CARRIER IFLA_BOND_ARP_INTERVAL IFLA_BOND_ARP_IP_TARGET IFLA_BOND_ARP_VALIDATE IFLA_BOND_ARP_ALL_TARGETS IFLA_BOND_PRIMARY IFLA_BOND_PRIMARY_RESELECT IFLA_BOND_FAIL_OVER_MAC IFLA_BOND_XMIT_HASH_POLICY IFLA_BOND_RESEND_IGMP IFLA_BOND_NUM_PEER_NOTIF IFLA_BOND_ALL_SLAVES_ACTIVE IFLA_BOND_MIN_LINKS IFLA_BOND_LP_INTERVAL IFLA_BOND_PACKETS_PER_SLAVE IFLA_BOND_AD_LACP_RATE IFLA_BOND_AD_SELECT IFLA_BOND_AD_INFO IFLA_BOND_AD_ACTOR_SYS_PRIO IFLA_BOND_AD_USER_PORT_KEY IFLA_BOND_AD_ACTOR_SYSTEM IFLA_BOND_TLB_DYNAMIC_LB ) const ( IFLA_BOND_AD_INFO_UNSPEC = iota IFLA_BOND_AD_INFO_AGGREGATOR IFLA_BOND_AD_INFO_NUM_PORTS IFLA_BOND_AD_INFO_ACTOR_KEY IFLA_BOND_AD_INFO_PARTNER_KEY IFLA_BOND_AD_INFO_PARTNER_MAC ) const ( IFLA_BOND_SLAVE_UNSPEC = iota IFLA_BOND_SLAVE_STATE IFLA_BOND_SLAVE_MII_STATUS IFLA_BOND_SLAVE_LINK_FAILURE_COUNT IFLA_BOND_SLAVE_PERM_HWADDR IFLA_BOND_SLAVE_QUEUE_ID IFLA_BOND_SLAVE_AD_AGGREGATOR_ID IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE ) const ( IFLA_GENEVE_UNSPEC = iota IFLA_GENEVE_ID // vni IFLA_GENEVE_REMOTE IFLA_GENEVE_TTL IFLA_GENEVE_TOS IFLA_GENEVE_PORT // destination port IFLA_GENEVE_COLLECT_METADATA IFLA_GENEVE_REMOTE6 IFLA_GENEVE_UDP_CSUM IFLA_GENEVE_UDP_ZERO_CSUM6_TX IFLA_GENEVE_UDP_ZERO_CSUM6_RX IFLA_GENEVE_LABEL IFLA_GENEVE_TTL_INHERIT IFLA_GENEVE_DF IFLA_GENEVE_INNER_PROTO_INHERIT IFLA_GENEVE_MAX = IFLA_GENEVE_INNER_PROTO_INHERIT ) const ( IFLA_GRE_UNSPEC = iota IFLA_GRE_LINK IFLA_GRE_IFLAGS IFLA_GRE_OFLAGS IFLA_GRE_IKEY IFLA_GRE_OKEY IFLA_GRE_LOCAL IFLA_GRE_REMOTE IFLA_GRE_TTL IFLA_GRE_TOS IFLA_GRE_PMTUDISC IFLA_GRE_ENCAP_LIMIT IFLA_GRE_FLOWINFO IFLA_GRE_FLAGS IFLA_GRE_ENCAP_TYPE IFLA_GRE_ENCAP_FLAGS IFLA_GRE_ENCAP_SPORT IFLA_GRE_ENCAP_DPORT IFLA_GRE_COLLECT_METADATA IFLA_GRE_MAX = IFLA_GRE_COLLECT_METADATA ) const ( GRE_CSUM = 0x8000 GRE_ROUTING = 0x4000 GRE_KEY = 0x2000 GRE_SEQ = 0x1000 GRE_STRICT = 0x0800 GRE_REC = 0x0700 GRE_FLAGS = 0x00F8 GRE_VERSION = 0x0007 ) const ( IFLA_VF_INFO_UNSPEC = iota IFLA_VF_INFO IFLA_VF_INFO_MAX = IFLA_VF_INFO ) const ( IFLA_VF_UNSPEC = iota IFLA_VF_MAC /* Hardware queue specific attributes */ IFLA_VF_VLAN IFLA_VF_TX_RATE /* Max TX Bandwidth Allocation */ IFLA_VF_SPOOFCHK /* Spoof Checking on/off switch */ IFLA_VF_LINK_STATE /* link state enable/disable/auto switch */ IFLA_VF_RATE /* Min and Max TX Bandwidth Allocation */ IFLA_VF_RSS_QUERY_EN /* RSS Redirection Table and Hash Key query * on/off switch */ IFLA_VF_STATS /* network device statistics */ IFLA_VF_TRUST /* Trust state of VF */ IFLA_VF_IB_NODE_GUID /* VF Infiniband node GUID */ IFLA_VF_IB_PORT_GUID /* VF Infiniband port GUID */ IFLA_VF_VLAN_LIST /* nested list of vlans, option for QinQ */ IFLA_VF_MAX = IFLA_VF_IB_PORT_GUID ) const ( IFLA_VF_VLAN_INFO_UNSPEC = iota IFLA_VF_VLAN_INFO /* VLAN ID, QoS and VLAN protocol */ __IFLA_VF_VLAN_INFO_MAX ) const ( IFLA_VF_LINK_STATE_AUTO = iota /* link state of the uplink */ IFLA_VF_LINK_STATE_ENABLE /* link always up */ IFLA_VF_LINK_STATE_DISABLE /* link always down */ IFLA_VF_LINK_STATE_MAX = IFLA_VF_LINK_STATE_DISABLE ) const ( IFLA_VF_STATS_RX_PACKETS = iota IFLA_VF_STATS_TX_PACKETS IFLA_VF_STATS_RX_BYTES IFLA_VF_STATS_TX_BYTES IFLA_VF_STATS_BROADCAST IFLA_VF_STATS_MULTICAST IFLA_VF_STATS_RX_DROPPED IFLA_VF_STATS_TX_DROPPED IFLA_VF_STATS_MAX = IFLA_VF_STATS_TX_DROPPED ) const ( SizeofVfMac = 0x24 SizeofVfVlan = 0x0c SizeofVfVlanInfo = 0x10 SizeofVfTxRate = 0x08 SizeofVfRate = 0x0c SizeofVfSpoofchk = 0x08 SizeofVfLinkState = 0x08 SizeofVfRssQueryEn = 0x08 SizeofVfTrust = 0x08 SizeofVfGUID = 0x10 ) // struct ifla_vf_mac { // __u32 vf; // __u8 mac[32]; /* MAX_ADDR_LEN */ // }; type VfMac struct { Vf uint32 Mac [32]byte } func (msg *VfMac) Len() int { return SizeofVfMac } func DeserializeVfMac(b []byte) *VfMac { return (*VfMac)(unsafe.Pointer(&b[0:SizeofVfMac][0])) } func (msg *VfMac) Serialize() []byte { return (*(*[SizeofVfMac]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_vlan { // __u32 vf; // __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ // __u32 qos; // }; type VfVlan struct { Vf uint32 Vlan uint32 Qos uint32 } func (msg *VfVlan) Len() int { return SizeofVfVlan } func DeserializeVfVlan(b []byte) *VfVlan { return (*VfVlan)(unsafe.Pointer(&b[0:SizeofVfVlan][0])) } func (msg *VfVlan) Serialize() []byte { return (*(*[SizeofVfVlan]byte)(unsafe.Pointer(msg)))[:] } func DeserializeVfVlanList(b []byte) ([]*VfVlanInfo, error) { var vfVlanInfoList []*VfVlanInfo attrs, err := ParseRouteAttr(b) if err != nil { return nil, err } for _, element := range attrs { if element.Attr.Type == IFLA_VF_VLAN_INFO { vfVlanInfoList = append(vfVlanInfoList, DeserializeVfVlanInfo(element.Value)) } } if len(vfVlanInfoList) == 0 { return nil, fmt.Errorf("VF vlan list is defined but no vf vlan info elements were found") } return vfVlanInfoList, nil } // struct ifla_vf_vlan_info { // __u32 vf; // __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ // __u32 qos; // __be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */ // }; type VfVlanInfo struct { VfVlan VlanProto uint16 } func DeserializeVfVlanInfo(b []byte) *VfVlanInfo { return &VfVlanInfo{ *(*VfVlan)(unsafe.Pointer(&b[0:SizeofVfVlan][0])), binary.BigEndian.Uint16(b[SizeofVfVlan:SizeofVfVlanInfo]), } } func (msg *VfVlanInfo) Serialize() []byte { return (*(*[SizeofVfVlanInfo]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_tx_rate { // __u32 vf; // __u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */ // }; type VfTxRate struct { Vf uint32 Rate uint32 } func (msg *VfTxRate) Len() int { return SizeofVfTxRate } func DeserializeVfTxRate(b []byte) *VfTxRate { return (*VfTxRate)(unsafe.Pointer(&b[0:SizeofVfTxRate][0])) } func (msg *VfTxRate) Serialize() []byte { return (*(*[SizeofVfTxRate]byte)(unsafe.Pointer(msg)))[:] } //struct ifla_vf_stats { // __u64 rx_packets; // __u64 tx_packets; // __u64 rx_bytes; // __u64 tx_bytes; // __u64 broadcast; // __u64 multicast; //}; type VfStats struct { RxPackets uint64 TxPackets uint64 RxBytes uint64 TxBytes uint64 Multicast uint64 Broadcast uint64 RxDropped uint64 TxDropped uint64 } func DeserializeVfStats(b []byte) VfStats { var vfstat VfStats stats, err := ParseRouteAttr(b) if err != nil { return vfstat } var valueVar uint64 for _, stat := range stats { if err := binary.Read(bytes.NewBuffer(stat.Value), NativeEndian(), &valueVar); err != nil { break } switch stat.Attr.Type { case IFLA_VF_STATS_RX_PACKETS: vfstat.RxPackets = valueVar case IFLA_VF_STATS_TX_PACKETS: vfstat.TxPackets = valueVar case IFLA_VF_STATS_RX_BYTES: vfstat.RxBytes = valueVar case IFLA_VF_STATS_TX_BYTES: vfstat.TxBytes = valueVar case IFLA_VF_STATS_MULTICAST: vfstat.Multicast = valueVar case IFLA_VF_STATS_BROADCAST: vfstat.Broadcast = valueVar case IFLA_VF_STATS_RX_DROPPED: vfstat.RxDropped = valueVar case IFLA_VF_STATS_TX_DROPPED: vfstat.TxDropped = valueVar } } return vfstat } // struct ifla_vf_rate { // __u32 vf; // __u32 min_tx_rate; /* Min Bandwidth in Mbps */ // __u32 max_tx_rate; /* Max Bandwidth in Mbps */ // }; type VfRate struct { Vf uint32 MinTxRate uint32 MaxTxRate uint32 } func (msg *VfRate) Len() int { return SizeofVfRate } func DeserializeVfRate(b []byte) *VfRate { return (*VfRate)(unsafe.Pointer(&b[0:SizeofVfRate][0])) } func (msg *VfRate) Serialize() []byte { return (*(*[SizeofVfRate]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_spoofchk { // __u32 vf; // __u32 setting; // }; type VfSpoofchk struct { Vf uint32 Setting uint32 } func (msg *VfSpoofchk) Len() int { return SizeofVfSpoofchk } func DeserializeVfSpoofchk(b []byte) *VfSpoofchk { return (*VfSpoofchk)(unsafe.Pointer(&b[0:SizeofVfSpoofchk][0])) } func (msg *VfSpoofchk) Serialize() []byte { return (*(*[SizeofVfSpoofchk]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_link_state { // __u32 vf; // __u32 link_state; // }; type VfLinkState struct { Vf uint32 LinkState uint32 } func (msg *VfLinkState) Len() int { return SizeofVfLinkState } func DeserializeVfLinkState(b []byte) *VfLinkState { return (*VfLinkState)(unsafe.Pointer(&b[0:SizeofVfLinkState][0])) } func (msg *VfLinkState) Serialize() []byte { return (*(*[SizeofVfLinkState]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_rss_query_en { // __u32 vf; // __u32 setting; // }; type VfRssQueryEn struct { Vf uint32 Setting uint32 } func (msg *VfRssQueryEn) Len() int { return SizeofVfRssQueryEn } func DeserializeVfRssQueryEn(b []byte) *VfRssQueryEn { return (*VfRssQueryEn)(unsafe.Pointer(&b[0:SizeofVfRssQueryEn][0])) } func (msg *VfRssQueryEn) Serialize() []byte { return (*(*[SizeofVfRssQueryEn]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_trust { // __u32 vf; // __u32 setting; // }; type VfTrust struct { Vf uint32 Setting uint32 } func (msg *VfTrust) Len() int { return SizeofVfTrust } func DeserializeVfTrust(b []byte) *VfTrust { return (*VfTrust)(unsafe.Pointer(&b[0:SizeofVfTrust][0])) } func (msg *VfTrust) Serialize() []byte { return (*(*[SizeofVfTrust]byte)(unsafe.Pointer(msg)))[:] } // struct ifla_vf_guid { // __u32 vf; // __u32 rsvd; // __u64 guid; // }; type VfGUID struct { Vf uint32 Rsvd uint32 GUID uint64 } func (msg *VfGUID) Len() int { return SizeofVfGUID } func DeserializeVfGUID(b []byte) *VfGUID { return (*VfGUID)(unsafe.Pointer(&b[0:SizeofVfGUID][0])) } func (msg *VfGUID) Serialize() []byte { return (*(*[SizeofVfGUID]byte)(unsafe.Pointer(msg)))[:] } const ( XDP_FLAGS_UPDATE_IF_NOEXIST = 1 << iota XDP_FLAGS_SKB_MODE XDP_FLAGS_DRV_MODE XDP_FLAGS_MASK = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_SKB_MODE | XDP_FLAGS_DRV_MODE ) const ( IFLA_XDP_UNSPEC = iota IFLA_XDP_FD /* fd of xdp program to attach, or -1 to remove */ IFLA_XDP_ATTACHED /* read-only bool indicating if prog is attached */ IFLA_XDP_FLAGS /* xdp prog related flags */ IFLA_XDP_PROG_ID /* xdp prog id */ IFLA_XDP_MAX = IFLA_XDP_PROG_ID ) // XDP program attach mode (used as dump value for IFLA_XDP_ATTACHED) const ( XDP_ATTACHED_NONE = iota XDP_ATTACHED_DRV XDP_ATTACHED_SKB XDP_ATTACHED_HW ) const ( IFLA_IPTUN_UNSPEC = iota IFLA_IPTUN_LINK IFLA_IPTUN_LOCAL IFLA_IPTUN_REMOTE IFLA_IPTUN_TTL IFLA_IPTUN_TOS IFLA_IPTUN_ENCAP_LIMIT IFLA_IPTUN_FLOWINFO IFLA_IPTUN_FLAGS IFLA_IPTUN_PROTO IFLA_IPTUN_PMTUDISC IFLA_IPTUN_6RD_PREFIX IFLA_IPTUN_6RD_RELAY_PREFIX IFLA_IPTUN_6RD_PREFIXLEN IFLA_IPTUN_6RD_RELAY_PREFIXLEN IFLA_IPTUN_ENCAP_TYPE IFLA_IPTUN_ENCAP_FLAGS IFLA_IPTUN_ENCAP_SPORT IFLA_IPTUN_ENCAP_DPORT IFLA_IPTUN_COLLECT_METADATA IFLA_IPTUN_MAX = IFLA_IPTUN_COLLECT_METADATA ) const ( IFLA_VTI_UNSPEC = iota IFLA_VTI_LINK IFLA_VTI_IKEY IFLA_VTI_OKEY IFLA_VTI_LOCAL IFLA_VTI_REMOTE IFLA_VTI_MAX = IFLA_VTI_REMOTE ) const ( IFLA_VRF_UNSPEC = iota IFLA_VRF_TABLE ) const ( IFLA_BR_UNSPEC = iota IFLA_BR_FORWARD_DELAY IFLA_BR_HELLO_TIME IFLA_BR_MAX_AGE IFLA_BR_AGEING_TIME IFLA_BR_STP_STATE IFLA_BR_PRIORITY IFLA_BR_VLAN_FILTERING IFLA_BR_VLAN_PROTOCOL IFLA_BR_GROUP_FWD_MASK IFLA_BR_ROOT_ID IFLA_BR_BRIDGE_ID IFLA_BR_ROOT_PORT IFLA_BR_ROOT_PATH_COST IFLA_BR_TOPOLOGY_CHANGE IFLA_BR_TOPOLOGY_CHANGE_DETECTED IFLA_BR_HELLO_TIMER IFLA_BR_TCN_TIMER IFLA_BR_TOPOLOGY_CHANGE_TIMER IFLA_BR_GC_TIMER IFLA_BR_GROUP_ADDR IFLA_BR_FDB_FLUSH IFLA_BR_MCAST_ROUTER IFLA_BR_MCAST_SNOOPING IFLA_BR_MCAST_QUERY_USE_IFADDR IFLA_BR_MCAST_QUERIER IFLA_BR_MCAST_HASH_ELASTICITY IFLA_BR_MCAST_HASH_MAX IFLA_BR_MCAST_LAST_MEMBER_CNT IFLA_BR_MCAST_STARTUP_QUERY_CNT IFLA_BR_MCAST_LAST_MEMBER_INTVL IFLA_BR_MCAST_MEMBERSHIP_INTVL IFLA_BR_MCAST_QUERIER_INTVL IFLA_BR_MCAST_QUERY_INTVL IFLA_BR_MCAST_QUERY_RESPONSE_INTVL IFLA_BR_MCAST_STARTUP_QUERY_INTVL IFLA_BR_NF_CALL_IPTABLES IFLA_BR_NF_CALL_IP6TABLES IFLA_BR_NF_CALL_ARPTABLES IFLA_BR_VLAN_DEFAULT_PVID IFLA_BR_PAD IFLA_BR_VLAN_STATS_ENABLED IFLA_BR_MCAST_STATS_ENABLED IFLA_BR_MCAST_IGMP_VERSION IFLA_BR_MCAST_MLD_VERSION IFLA_BR_MAX = IFLA_BR_MCAST_MLD_VERSION ) const ( IFLA_GTP_UNSPEC = iota IFLA_GTP_FD0 IFLA_GTP_FD1 IFLA_GTP_PDP_HASHSIZE IFLA_GTP_ROLE ) const ( GTP_ROLE_GGSN = iota GTP_ROLE_SGSN ) const ( IFLA_XFRM_UNSPEC = iota IFLA_XFRM_LINK IFLA_XFRM_IF_ID IFLA_XFRM_MAX = iota - 1 ) const ( IFLA_TUN_UNSPEC = iota IFLA_TUN_OWNER IFLA_TUN_GROUP IFLA_TUN_TYPE IFLA_TUN_PI IFLA_TUN_VNET_HDR IFLA_TUN_PERSIST IFLA_TUN_MULTI_QUEUE IFLA_TUN_NUM_QUEUES IFLA_TUN_NUM_DISABLED_QUEUES IFLA_TUN_MAX = IFLA_TUN_NUM_DISABLED_QUEUES ) const ( IFLA_IPOIB_UNSPEC = iota IFLA_IPOIB_PKEY IFLA_IPOIB_MODE IFLA_IPOIB_UMCAST IFLA_IPOIB_MAX = IFLA_IPOIB_UMCAST ) const ( IFLA_CAN_UNSPEC = iota IFLA_CAN_BITTIMING IFLA_CAN_BITTIMING_CONST IFLA_CAN_CLOCK IFLA_CAN_STATE IFLA_CAN_CTRLMODE IFLA_CAN_RESTART_MS IFLA_CAN_RESTART IFLA_CAN_BERR_COUNTER IFLA_CAN_DATA_BITTIMING IFLA_CAN_DATA_BITTIMING_CONST IFLA_CAN_TERMINATION IFLA_CAN_TERMINATION_CONST IFLA_CAN_BITRATE_CONST IFLA_CAN_DATA_BITRATE_CONST IFLA_CAN_BITRATE_MAX IFLA_CAN_MAX = IFLA_CAN_BITRATE_MAX ) const ( IFLA_BAREUDP_UNSPEC = iota IFLA_BAREUDP_PORT IFLA_BAREUDP_ETHERTYPE IFLA_BAREUDP_SRCPORT_MIN IFLA_BAREUDP_MULTIPROTO_MODE IFLA_BAREUDP_MAX = IFLA_BAREUDP_MULTIPROTO_MODE ) netlink-1.3.0/nl/link_linux_test.go000066400000000000000000000115461466216277000173540ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *VfMac) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) copy(b[4:36], msg.Mac[:]) } func (msg *VfMac) serializeSafe() []byte { length := SizeofVfMac b := make([]byte, length) msg.write(b) return b } func deserializeVfMacSafe(b []byte) *VfMac { var msg = VfMac{} binary.Read(bytes.NewReader(b[0:SizeofVfMac]), NativeEndian(), &msg) return &msg } func TestVfMacDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfMac) rand.Read(orig) safemsg := deserializeVfMacSafe(orig) msg := DeserializeVfMac(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfVlan) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.Vlan)) native.PutUint32(b[8:12], uint32(msg.Qos)) } func (msg *VfVlan) serializeSafe() []byte { length := SizeofVfVlan b := make([]byte, length) msg.write(b) return b } func deserializeVfVlanSafe(b []byte) *VfVlan { var msg = VfVlan{} binary.Read(bytes.NewReader(b[0:SizeofVfVlan]), NativeEndian(), &msg) return &msg } func TestVfVlanDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfVlan) rand.Read(orig) safemsg := deserializeVfVlanSafe(orig) msg := DeserializeVfVlan(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfTxRate) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.Rate)) } func (msg *VfTxRate) serializeSafe() []byte { length := SizeofVfTxRate b := make([]byte, length) msg.write(b) return b } func deserializeVfTxRateSafe(b []byte) *VfTxRate { var msg = VfTxRate{} binary.Read(bytes.NewReader(b[0:SizeofVfTxRate]), NativeEndian(), &msg) return &msg } func TestVfTxRateDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfTxRate) rand.Read(orig) safemsg := deserializeVfTxRateSafe(orig) msg := DeserializeVfTxRate(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfRate) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.MinTxRate)) native.PutUint32(b[8:12], uint32(msg.MaxTxRate)) } func (msg *VfRate) serializeSafe() []byte { length := SizeofVfRate b := make([]byte, length) msg.write(b) return b } func deserializeVfRateSafe(b []byte) *VfRate { var msg = VfRate{} binary.Read(bytes.NewReader(b[0:SizeofVfRate]), NativeEndian(), &msg) return &msg } func TestVfRateDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfRate) rand.Read(orig) safemsg := deserializeVfRateSafe(orig) msg := DeserializeVfRate(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfSpoofchk) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.Setting)) } func (msg *VfSpoofchk) serializeSafe() []byte { length := SizeofVfSpoofchk b := make([]byte, length) msg.write(b) return b } func deserializeVfSpoofchkSafe(b []byte) *VfSpoofchk { var msg = VfSpoofchk{} binary.Read(bytes.NewReader(b[0:SizeofVfSpoofchk]), NativeEndian(), &msg) return &msg } func TestVfSpoofchkDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfSpoofchk) rand.Read(orig) safemsg := deserializeVfSpoofchkSafe(orig) msg := DeserializeVfSpoofchk(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfLinkState) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.LinkState)) } func (msg *VfLinkState) serializeSafe() []byte { length := SizeofVfLinkState b := make([]byte, length) msg.write(b) return b } func deserializeVfLinkStateSafe(b []byte) *VfLinkState { var msg = VfLinkState{} binary.Read(bytes.NewReader(b[0:SizeofVfLinkState]), NativeEndian(), &msg) return &msg } func TestVfLinkStateDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfLinkState) rand.Read(orig) safemsg := deserializeVfLinkStateSafe(orig) msg := DeserializeVfLinkState(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *VfRssQueryEn) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], uint32(msg.Vf)) native.PutUint32(b[4:8], uint32(msg.Setting)) } func (msg *VfRssQueryEn) serializeSafe() []byte { length := SizeofVfRssQueryEn b := make([]byte, length) msg.write(b) return b } func deserializeVfRssQueryEnSafe(b []byte) *VfRssQueryEn { var msg = VfRssQueryEn{} binary.Read(bytes.NewReader(b[0:SizeofVfRssQueryEn]), NativeEndian(), &msg) return &msg } func TestVfRssQueryEnDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofVfRssQueryEn) rand.Read(orig) safemsg := deserializeVfRssQueryEnSafe(orig) msg := DeserializeVfRssQueryEn(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/lwt_linux.go000066400000000000000000000005421466216277000161600ustar00rootroot00000000000000package nl const ( LWT_BPF_PROG_UNSPEC = iota LWT_BPF_PROG_FD LWT_BPF_PROG_NAME __LWT_BPF_PROG_MAX ) const ( LWT_BPF_PROG_MAX = __LWT_BPF_PROG_MAX - 1 ) const ( LWT_BPF_UNSPEC = iota LWT_BPF_IN LWT_BPF_OUT LWT_BPF_XMIT LWT_BPF_XMIT_HEADROOM __LWT_BPF_MAX ) const ( LWT_BPF_MAX = __LWT_BPF_MAX - 1 ) const ( LWT_BPF_MAX_HEADROOM = 256 ) netlink-1.3.0/nl/mpls_linux.go000066400000000000000000000012601466216277000163230ustar00rootroot00000000000000package nl import "encoding/binary" const ( MPLS_LS_LABEL_SHIFT = 12 MPLS_LS_S_SHIFT = 8 ) func EncodeMPLSStack(labels ...int) []byte { b := make([]byte, 4*len(labels)) for idx, label := range labels { l := label << MPLS_LS_LABEL_SHIFT if idx == len(labels)-1 { l |= 1 << MPLS_LS_S_SHIFT } binary.BigEndian.PutUint32(b[idx*4:], uint32(l)) } return b } func DecodeMPLSStack(buf []byte) []int { if len(buf)%4 != 0 { return nil } stack := make([]int, 0, len(buf)/4) for len(buf) > 0 { l := binary.BigEndian.Uint32(buf[:4]) buf = buf[4:] stack = append(stack, int(l)>>MPLS_LS_LABEL_SHIFT) if (l>>MPLS_LS_S_SHIFT)&1 > 0 { break } } return stack } netlink-1.3.0/nl/nl_linux.go000066400000000000000000000576261466216277000160020ustar00rootroot00000000000000// Package nl has low level primitives for making Netlink calls. package nl import ( "bytes" "encoding/binary" "fmt" "net" "os" "runtime" "sync" "sync/atomic" "syscall" "unsafe" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) const ( // Family type definitions FAMILY_ALL = unix.AF_UNSPEC FAMILY_V4 = unix.AF_INET FAMILY_V6 = unix.AF_INET6 FAMILY_MPLS = unix.AF_MPLS // Arbitrary set value (greater than default 4k) to allow receiving // from kernel more verbose messages e.g. for statistics, // tc rules or filters, or other more memory requiring data. RECEIVE_BUFFER_SIZE = 65536 // Kernel netlink pid PidKernel uint32 = 0 SizeofCnMsgOp = 0x18 ) // SupportedNlFamilies contains the list of netlink families this netlink package supports var SupportedNlFamilies = []int{unix.NETLINK_ROUTE, unix.NETLINK_XFRM, unix.NETLINK_NETFILTER} var nextSeqNr uint32 // Default netlink socket timeout, 60s var SocketTimeoutTv = unix.Timeval{Sec: 60, Usec: 0} // ErrorMessageReporting is the default error message reporting configuration for the new netlink sockets var EnableErrorMessageReporting bool = false // GetIPFamily returns the family type of a net.IP. func GetIPFamily(ip net.IP) int { if len(ip) <= net.IPv4len { return FAMILY_V4 } if ip.To4() != nil { return FAMILY_V4 } return FAMILY_V6 } var nativeEndian binary.ByteOrder // NativeEndian gets native endianness for the system func NativeEndian() binary.ByteOrder { if nativeEndian == nil { var x uint32 = 0x01020304 if *(*byte)(unsafe.Pointer(&x)) == 0x01 { nativeEndian = binary.BigEndian } else { nativeEndian = binary.LittleEndian } } return nativeEndian } // Byte swap a 16 bit value if we aren't big endian func Swap16(i uint16) uint16 { if NativeEndian() == binary.BigEndian { return i } return (i&0xff00)>>8 | (i&0xff)<<8 } // Byte swap a 32 bit value if aren't big endian func Swap32(i uint32) uint32 { if NativeEndian() == binary.BigEndian { return i } return (i&0xff000000)>>24 | (i&0xff0000)>>8 | (i&0xff00)<<8 | (i&0xff)<<24 } const ( NLMSGERR_ATTR_UNUSED = 0 NLMSGERR_ATTR_MSG = 1 NLMSGERR_ATTR_OFFS = 2 NLMSGERR_ATTR_COOKIE = 3 NLMSGERR_ATTR_POLICY = 4 ) type NetlinkRequestData interface { Len() int Serialize() []byte } const ( PROC_CN_MCAST_LISTEN = 1 PROC_CN_MCAST_IGNORE ) type CbID struct { Idx uint32 Val uint32 } type CnMsg struct { ID CbID Seq uint32 Ack uint32 Length uint16 Flags uint16 } type CnMsgOp struct { CnMsg // here we differ from the C header Op uint32 } func NewCnMsg(idx, val, op uint32) *CnMsgOp { var cm CnMsgOp cm.ID.Idx = idx cm.ID.Val = val cm.Ack = 0 cm.Seq = 1 cm.Length = uint16(binary.Size(op)) cm.Op = op return &cm } func (msg *CnMsgOp) Serialize() []byte { return (*(*[SizeofCnMsgOp]byte)(unsafe.Pointer(msg)))[:] } func DeserializeCnMsgOp(b []byte) *CnMsgOp { return (*CnMsgOp)(unsafe.Pointer(&b[0:SizeofCnMsgOp][0])) } func (msg *CnMsgOp) Len() int { return SizeofCnMsgOp } // IfInfomsg is related to links, but it is used for list requests as well type IfInfomsg struct { unix.IfInfomsg } // Create an IfInfomsg with family specified func NewIfInfomsg(family int) *IfInfomsg { return &IfInfomsg{ IfInfomsg: unix.IfInfomsg{ Family: uint8(family), }, } } func DeserializeIfInfomsg(b []byte) *IfInfomsg { return (*IfInfomsg)(unsafe.Pointer(&b[0:unix.SizeofIfInfomsg][0])) } func (msg *IfInfomsg) Serialize() []byte { return (*(*[unix.SizeofIfInfomsg]byte)(unsafe.Pointer(msg)))[:] } func (msg *IfInfomsg) Len() int { return unix.SizeofIfInfomsg } func (msg *IfInfomsg) EncapType() string { switch msg.Type { case 0: return "generic" case unix.ARPHRD_ETHER: return "ether" case unix.ARPHRD_EETHER: return "eether" case unix.ARPHRD_AX25: return "ax25" case unix.ARPHRD_PRONET: return "pronet" case unix.ARPHRD_CHAOS: return "chaos" case unix.ARPHRD_IEEE802: return "ieee802" case unix.ARPHRD_ARCNET: return "arcnet" case unix.ARPHRD_APPLETLK: return "atalk" case unix.ARPHRD_DLCI: return "dlci" case unix.ARPHRD_ATM: return "atm" case unix.ARPHRD_METRICOM: return "metricom" case unix.ARPHRD_IEEE1394: return "ieee1394" case unix.ARPHRD_INFINIBAND: return "infiniband" case unix.ARPHRD_SLIP: return "slip" case unix.ARPHRD_CSLIP: return "cslip" case unix.ARPHRD_SLIP6: return "slip6" case unix.ARPHRD_CSLIP6: return "cslip6" case unix.ARPHRD_RSRVD: return "rsrvd" case unix.ARPHRD_ADAPT: return "adapt" case unix.ARPHRD_ROSE: return "rose" case unix.ARPHRD_X25: return "x25" case unix.ARPHRD_HWX25: return "hwx25" case unix.ARPHRD_PPP: return "ppp" case unix.ARPHRD_HDLC: return "hdlc" case unix.ARPHRD_LAPB: return "lapb" case unix.ARPHRD_DDCMP: return "ddcmp" case unix.ARPHRD_RAWHDLC: return "rawhdlc" case unix.ARPHRD_TUNNEL: return "ipip" case unix.ARPHRD_TUNNEL6: return "tunnel6" case unix.ARPHRD_FRAD: return "frad" case unix.ARPHRD_SKIP: return "skip" case unix.ARPHRD_LOOPBACK: return "loopback" case unix.ARPHRD_LOCALTLK: return "ltalk" case unix.ARPHRD_FDDI: return "fddi" case unix.ARPHRD_BIF: return "bif" case unix.ARPHRD_SIT: return "sit" case unix.ARPHRD_IPDDP: return "ip/ddp" case unix.ARPHRD_IPGRE: return "gre" case unix.ARPHRD_PIMREG: return "pimreg" case unix.ARPHRD_HIPPI: return "hippi" case unix.ARPHRD_ASH: return "ash" case unix.ARPHRD_ECONET: return "econet" case unix.ARPHRD_IRDA: return "irda" case unix.ARPHRD_FCPP: return "fcpp" case unix.ARPHRD_FCAL: return "fcal" case unix.ARPHRD_FCPL: return "fcpl" case unix.ARPHRD_FCFABRIC: return "fcfb0" case unix.ARPHRD_FCFABRIC + 1: return "fcfb1" case unix.ARPHRD_FCFABRIC + 2: return "fcfb2" case unix.ARPHRD_FCFABRIC + 3: return "fcfb3" case unix.ARPHRD_FCFABRIC + 4: return "fcfb4" case unix.ARPHRD_FCFABRIC + 5: return "fcfb5" case unix.ARPHRD_FCFABRIC + 6: return "fcfb6" case unix.ARPHRD_FCFABRIC + 7: return "fcfb7" case unix.ARPHRD_FCFABRIC + 8: return "fcfb8" case unix.ARPHRD_FCFABRIC + 9: return "fcfb9" case unix.ARPHRD_FCFABRIC + 10: return "fcfb10" case unix.ARPHRD_FCFABRIC + 11: return "fcfb11" case unix.ARPHRD_FCFABRIC + 12: return "fcfb12" case unix.ARPHRD_IEEE802_TR: return "tr" case unix.ARPHRD_IEEE80211: return "ieee802.11" case unix.ARPHRD_IEEE80211_PRISM: return "ieee802.11/prism" case unix.ARPHRD_IEEE80211_RADIOTAP: return "ieee802.11/radiotap" case unix.ARPHRD_IEEE802154: return "ieee802.15.4" case 65534: return "none" case 65535: return "void" } return fmt.Sprintf("unknown%d", msg.Type) } // Round the length of a netlink message up to align it properly. // Taken from syscall/netlink_linux.go by The Go Authors under BSD-style license. func nlmAlignOf(msglen int) int { return (msglen + syscall.NLMSG_ALIGNTO - 1) & ^(syscall.NLMSG_ALIGNTO - 1) } func rtaAlignOf(attrlen int) int { return (attrlen + unix.RTA_ALIGNTO - 1) & ^(unix.RTA_ALIGNTO - 1) } func NewIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg { msg := NewIfInfomsg(family) parent.children = append(parent.children, msg) return msg } type Uint32Bitfield struct { Value uint32 Selector uint32 } func (a *Uint32Bitfield) Serialize() []byte { return (*(*[SizeofUint32Bitfield]byte)(unsafe.Pointer(a)))[:] } func DeserializeUint32Bitfield(data []byte) *Uint32Bitfield { return (*Uint32Bitfield)(unsafe.Pointer(&data[0:SizeofUint32Bitfield][0])) } type Uint32Attribute struct { Type uint16 Value uint32 } func (a *Uint32Attribute) Serialize() []byte { native := NativeEndian() buf := make([]byte, rtaAlignOf(8)) native.PutUint16(buf[0:2], 8) native.PutUint16(buf[2:4], a.Type) if a.Type&NLA_F_NET_BYTEORDER != 0 { binary.BigEndian.PutUint32(buf[4:], a.Value) } else { native.PutUint32(buf[4:], a.Value) } return buf } func (a *Uint32Attribute) Len() int { return 8 } // Extend RtAttr to handle data and children type RtAttr struct { unix.RtAttr Data []byte children []NetlinkRequestData } // Create a new Extended RtAttr object func NewRtAttr(attrType int, data []byte) *RtAttr { return &RtAttr{ RtAttr: unix.RtAttr{ Type: uint16(attrType), }, children: []NetlinkRequestData{}, Data: data, } } // NewRtAttrChild adds an RtAttr as a child to the parent and returns the new attribute // // Deprecated: Use AddRtAttr() on the parent object func NewRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { return parent.AddRtAttr(attrType, data) } // AddRtAttr adds an RtAttr as a child and returns the new attribute func (a *RtAttr) AddRtAttr(attrType int, data []byte) *RtAttr { attr := NewRtAttr(attrType, data) a.children = append(a.children, attr) return attr } // AddChild adds an existing NetlinkRequestData as a child. func (a *RtAttr) AddChild(attr NetlinkRequestData) { a.children = append(a.children, attr) } func (a *RtAttr) Len() int { if len(a.children) == 0 { return (unix.SizeofRtAttr + len(a.Data)) } l := 0 for _, child := range a.children { l += rtaAlignOf(child.Len()) } l += unix.SizeofRtAttr return rtaAlignOf(l + len(a.Data)) } // Serialize the RtAttr into a byte array // This can't just unsafe.cast because it must iterate through children. func (a *RtAttr) Serialize() []byte { native := NativeEndian() length := a.Len() buf := make([]byte, rtaAlignOf(length)) next := 4 if a.Data != nil { copy(buf[next:], a.Data) next += rtaAlignOf(len(a.Data)) } if len(a.children) > 0 { for _, child := range a.children { childBuf := child.Serialize() copy(buf[next:], childBuf) next += rtaAlignOf(len(childBuf)) } } if l := uint16(length); l != 0 { native.PutUint16(buf[0:2], l) } native.PutUint16(buf[2:4], a.Type) return buf } type NetlinkRequest struct { unix.NlMsghdr Data []NetlinkRequestData RawData []byte Sockets map[int]*SocketHandle } // Serialize the Netlink Request into a byte array func (req *NetlinkRequest) Serialize() []byte { length := unix.SizeofNlMsghdr dataBytes := make([][]byte, len(req.Data)) for i, data := range req.Data { dataBytes[i] = data.Serialize() length = length + len(dataBytes[i]) } length += len(req.RawData) req.Len = uint32(length) b := make([]byte, length) hdr := (*(*[unix.SizeofNlMsghdr]byte)(unsafe.Pointer(req)))[:] next := unix.SizeofNlMsghdr copy(b[0:next], hdr) for _, data := range dataBytes { for _, dataByte := range data { b[next] = dataByte next = next + 1 } } // Add the raw data if any if len(req.RawData) > 0 { copy(b[next:length], req.RawData) } return b } func (req *NetlinkRequest) AddData(data NetlinkRequestData) { req.Data = append(req.Data, data) } // AddRawData adds raw bytes to the end of the NetlinkRequest object during serialization func (req *NetlinkRequest) AddRawData(data []byte) { req.RawData = append(req.RawData, data...) } // Execute the request against the given sockType. // Returns a list of netlink messages in serialized format, optionally filtered // by resType. func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) { var res [][]byte err := req.ExecuteIter(sockType, resType, func(msg []byte) bool { res = append(res, msg) return true }) if err != nil { return nil, err } return res, nil } // ExecuteIter executes the request against the given sockType. // Calls the provided callback func once for each netlink message. // If the callback returns false, it is not called again, but // the remaining messages are consumed/discarded. // // Thread safety: ExecuteIter holds a lock on the socket until // it finishes iteration so the callback must not call back into // the netlink API. func (req *NetlinkRequest) ExecuteIter(sockType int, resType uint16, f func(msg []byte) bool) error { var ( s *NetlinkSocket err error ) if req.Sockets != nil { if sh, ok := req.Sockets[sockType]; ok { s = sh.Socket req.Seq = atomic.AddUint32(&sh.Seq, 1) } } sharedSocket := s != nil if s == nil { s, err = getNetlinkSocket(sockType) if err != nil { return err } if err := s.SetSendTimeout(&SocketTimeoutTv); err != nil { return err } if err := s.SetReceiveTimeout(&SocketTimeoutTv); err != nil { return err } if EnableErrorMessageReporting { if err := s.SetExtAck(true); err != nil { return err } } defer s.Close() } else { s.Lock() defer s.Unlock() } if err := s.Send(req); err != nil { return err } pid, err := s.GetPid() if err != nil { return err } done: for { msgs, from, err := s.Receive() if err != nil { return err } if from.Pid != PidKernel { return fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, PidKernel) } for _, m := range msgs { if m.Header.Seq != req.Seq { if sharedSocket { continue } return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, req.Seq) } if m.Header.Pid != pid { continue } if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 { return syscall.Errno(unix.EINTR) } if m.Header.Type == unix.NLMSG_DONE || m.Header.Type == unix.NLMSG_ERROR { // NLMSG_DONE might have no payload, if so assume no error. if m.Header.Type == unix.NLMSG_DONE && len(m.Data) == 0 { break done } native := NativeEndian() errno := int32(native.Uint32(m.Data[0:4])) if errno == 0 { break done } var err error err = syscall.Errno(-errno) unreadData := m.Data[4:] if m.Header.Flags&unix.NLM_F_ACK_TLVS != 0 && len(unreadData) > syscall.SizeofNlMsghdr { // Skip the echoed request message. echoReqH := (*syscall.NlMsghdr)(unsafe.Pointer(&unreadData[0])) unreadData = unreadData[nlmAlignOf(int(echoReqH.Len)):] // Annotate `err` using nlmsgerr attributes. for len(unreadData) >= syscall.SizeofRtAttr { attr := (*syscall.RtAttr)(unsafe.Pointer(&unreadData[0])) attrData := unreadData[syscall.SizeofRtAttr:attr.Len] switch attr.Type { case NLMSGERR_ATTR_MSG: err = fmt.Errorf("%w: %s", err, unix.ByteSliceToString(attrData)) default: // TODO: handle other NLMSGERR_ATTR types } unreadData = unreadData[rtaAlignOf(int(attr.Len)):] } } return err } if resType != 0 && m.Header.Type != resType { continue } if cont := f(m.Data); !cont { // Drain the rest of the messages from the kernel but don't // pass them to the iterator func. f = dummyMsgIterFunc } if m.Header.Flags&unix.NLM_F_MULTI == 0 { break done } } } return nil } func dummyMsgIterFunc(msg []byte) bool { return true } // Create a new netlink request from proto and flags // Note the Len value will be inaccurate once data is added until // the message is serialized func NewNetlinkRequest(proto, flags int) *NetlinkRequest { return &NetlinkRequest{ NlMsghdr: unix.NlMsghdr{ Len: uint32(unix.SizeofNlMsghdr), Type: uint16(proto), Flags: unix.NLM_F_REQUEST | uint16(flags), Seq: atomic.AddUint32(&nextSeqNr, 1), }, } } type NetlinkSocket struct { fd int32 file *os.File lsa unix.SockaddrNetlink sync.Mutex } func getNetlinkSocket(protocol int) (*NetlinkSocket, error) { fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, protocol) if err != nil { return nil, err } err = unix.SetNonblock(fd, true) if err != nil { return nil, err } s := &NetlinkSocket{ fd: int32(fd), file: os.NewFile(uintptr(fd), "netlink"), } s.lsa.Family = unix.AF_NETLINK if err := unix.Bind(fd, &s.lsa); err != nil { unix.Close(fd) return nil, err } return s, nil } // GetNetlinkSocketAt opens a netlink socket in the network namespace newNs // and positions the thread back into the network namespace specified by curNs, // when done. If curNs is close, the function derives the current namespace and // moves back into it when done. If newNs is close, the socket will be opened // in the current network namespace. func GetNetlinkSocketAt(newNs, curNs netns.NsHandle, protocol int) (*NetlinkSocket, error) { c, err := executeInNetns(newNs, curNs) if err != nil { return nil, err } defer c() return getNetlinkSocket(protocol) } // executeInNetns sets execution of the code following this call to the // network namespace newNs, then moves the thread back to curNs if open, // otherwise to the current netns at the time the function was invoked // In case of success, the caller is expected to execute the returned function // at the end of the code that needs to be executed in the network namespace. // Example: // // func jobAt(...) error { // d, err := executeInNetns(...) // if err != nil { return err} // defer d() // < code which needs to be executed in specific netns> // } // // TODO: his function probably belongs to netns pkg. func executeInNetns(newNs, curNs netns.NsHandle) (func(), error) { var ( err error moveBack func(netns.NsHandle) error closeNs func() error unlockThd func() ) restore := func() { // order matters if moveBack != nil { moveBack(curNs) } if closeNs != nil { closeNs() } if unlockThd != nil { unlockThd() } } if newNs.IsOpen() { runtime.LockOSThread() unlockThd = runtime.UnlockOSThread if !curNs.IsOpen() { if curNs, err = netns.Get(); err != nil { restore() return nil, fmt.Errorf("could not get current namespace while creating netlink socket: %v", err) } closeNs = curNs.Close } if err := netns.Set(newNs); err != nil { restore() return nil, fmt.Errorf("failed to set into network namespace %d while creating netlink socket: %v", newNs, err) } moveBack = netns.Set } return restore, nil } // Create a netlink socket with a given protocol (e.g. NETLINK_ROUTE) // and subscribe it to multicast groups passed in variable argument list. // Returns the netlink socket on which Receive() method can be called // to retrieve the messages from the kernel. func Subscribe(protocol int, groups ...uint) (*NetlinkSocket, error) { fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, protocol) if err != nil { return nil, err } err = unix.SetNonblock(fd, true) if err != nil { return nil, err } s := &NetlinkSocket{ fd: int32(fd), file: os.NewFile(uintptr(fd), "netlink"), } s.lsa.Family = unix.AF_NETLINK for _, g := range groups { s.lsa.Groups |= (1 << (g - 1)) } if err := unix.Bind(fd, &s.lsa); err != nil { unix.Close(fd) return nil, err } return s, nil } // SubscribeAt works like Subscribe plus let's the caller choose the network // namespace in which the socket would be opened (newNs). Then control goes back // to curNs if open, otherwise to the netns at the time this function was called. func SubscribeAt(newNs, curNs netns.NsHandle, protocol int, groups ...uint) (*NetlinkSocket, error) { c, err := executeInNetns(newNs, curNs) if err != nil { return nil, err } defer c() return Subscribe(protocol, groups...) } func (s *NetlinkSocket) Close() { s.file.Close() } func (s *NetlinkSocket) GetFd() int { return int(s.fd) } func (s *NetlinkSocket) Send(request *NetlinkRequest) error { return unix.Sendto(int(s.fd), request.Serialize(), 0, &s.lsa) } func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, *unix.SockaddrNetlink, error) { rawConn, err := s.file.SyscallConn() if err != nil { return nil, nil, err } var ( fromAddr *unix.SockaddrNetlink rb [RECEIVE_BUFFER_SIZE]byte nr int from unix.Sockaddr innerErr error ) err = rawConn.Read(func(fd uintptr) (done bool) { nr, from, innerErr = unix.Recvfrom(int(fd), rb[:], 0) return innerErr != unix.EWOULDBLOCK }) if innerErr != nil { err = innerErr } if err != nil { return nil, nil, err } fromAddr, ok := from.(*unix.SockaddrNetlink) if !ok { return nil, nil, fmt.Errorf("Error converting to netlink sockaddr") } if nr < unix.NLMSG_HDRLEN { return nil, nil, fmt.Errorf("Got short response from netlink") } msgLen := nlmAlignOf(nr) rb2 := make([]byte, msgLen) copy(rb2, rb[:msgLen]) nl, err := syscall.ParseNetlinkMessage(rb2) if err != nil { return nil, nil, err } return nl, fromAddr, nil } // SetSendTimeout allows to set a send timeout on the socket func (s *NetlinkSocket) SetSendTimeout(timeout *unix.Timeval) error { // Set a send timeout of SOCKET_SEND_TIMEOUT, this will allow the Send to periodically unblock and avoid that a routine // remains stuck on a send on a closed fd return unix.SetsockoptTimeval(int(s.fd), unix.SOL_SOCKET, unix.SO_SNDTIMEO, timeout) } // SetReceiveTimeout allows to set a receive timeout on the socket func (s *NetlinkSocket) SetReceiveTimeout(timeout *unix.Timeval) error { // Set a read timeout of SOCKET_READ_TIMEOUT, this will allow the Read to periodically unblock and avoid that a routine // remains stuck on a recvmsg on a closed fd return unix.SetsockoptTimeval(int(s.fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, timeout) } // SetReceiveBufferSize allows to set a receive buffer size on the socket func (s *NetlinkSocket) SetReceiveBufferSize(size int, force bool) error { opt := unix.SO_RCVBUF if force { opt = unix.SO_RCVBUFFORCE } return unix.SetsockoptInt(int(s.fd), unix.SOL_SOCKET, opt, size) } // SetExtAck requests error messages to be reported on the socket func (s *NetlinkSocket) SetExtAck(enable bool) error { var enableN int if enable { enableN = 1 } return unix.SetsockoptInt(int(s.fd), unix.SOL_NETLINK, unix.NETLINK_EXT_ACK, enableN) } func (s *NetlinkSocket) GetPid() (uint32, error) { lsa, err := unix.Getsockname(int(s.fd)) if err != nil { return 0, err } switch v := lsa.(type) { case *unix.SockaddrNetlink: return v.Pid, nil } return 0, fmt.Errorf("Wrong socket type") } func ZeroTerminated(s string) []byte { bytes := make([]byte, len(s)+1) for i := 0; i < len(s); i++ { bytes[i] = s[i] } bytes[len(s)] = 0 return bytes } func NonZeroTerminated(s string) []byte { bytes := make([]byte, len(s)) for i := 0; i < len(s); i++ { bytes[i] = s[i] } return bytes } func BytesToString(b []byte) string { n := bytes.Index(b, []byte{0}) return string(b[:n]) } func Uint8Attr(v uint8) []byte { return []byte{byte(v)} } func Uint16Attr(v uint16) []byte { native := NativeEndian() bytes := make([]byte, 2) native.PutUint16(bytes, v) return bytes } func BEUint16Attr(v uint16) []byte { bytes := make([]byte, 2) binary.BigEndian.PutUint16(bytes, v) return bytes } func Uint32Attr(v uint32) []byte { native := NativeEndian() bytes := make([]byte, 4) native.PutUint32(bytes, v) return bytes } func BEUint32Attr(v uint32) []byte { bytes := make([]byte, 4) binary.BigEndian.PutUint32(bytes, v) return bytes } func Uint64Attr(v uint64) []byte { native := NativeEndian() bytes := make([]byte, 8) native.PutUint64(bytes, v) return bytes } func BEUint64Attr(v uint64) []byte { bytes := make([]byte, 8) binary.BigEndian.PutUint64(bytes, v) return bytes } func ParseRouteAttr(b []byte) ([]syscall.NetlinkRouteAttr, error) { var attrs []syscall.NetlinkRouteAttr for len(b) >= unix.SizeofRtAttr { a, vbuf, alen, err := netlinkRouteAttrAndValue(b) if err != nil { return nil, err } ra := syscall.NetlinkRouteAttr{Attr: syscall.RtAttr(*a), Value: vbuf[:int(a.Len)-unix.SizeofRtAttr]} attrs = append(attrs, ra) b = b[alen:] } return attrs, nil } // ParseRouteAttrAsMap parses provided buffer that contains raw RtAttrs and returns a map of parsed // atttributes indexed by attribute type or error if occured. func ParseRouteAttrAsMap(b []byte) (map[uint16]syscall.NetlinkRouteAttr, error) { attrMap := make(map[uint16]syscall.NetlinkRouteAttr) attrs, err := ParseRouteAttr(b) if err != nil { return nil, err } for _, attr := range attrs { attrMap[attr.Attr.Type] = attr } return attrMap, nil } func netlinkRouteAttrAndValue(b []byte) (*unix.RtAttr, []byte, int, error) { a := (*unix.RtAttr)(unsafe.Pointer(&b[0])) if int(a.Len) < unix.SizeofRtAttr || int(a.Len) > len(b) { return nil, nil, 0, unix.EINVAL } return a, b[unix.SizeofRtAttr:], rtaAlignOf(int(a.Len)), nil } // SocketHandle contains the netlink socket and the associated // sequence counter for a specific netlink family type SocketHandle struct { Seq uint32 Socket *NetlinkSocket } // Close closes the netlink socket func (sh *SocketHandle) Close() { if sh.Socket != nil { sh.Socket.Close() } } netlink-1.3.0/nl/nl_linux_test.go000066400000000000000000000072421466216277000170260ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "reflect" "testing" "time" "golang.org/x/sys/unix" ) type testSerializer interface { serializeSafe() []byte Serialize() []byte } func testDeserializeSerialize(t *testing.T, orig []byte, safemsg testSerializer, msg testSerializer) { if !reflect.DeepEqual(safemsg, msg) { t.Fatal("Deserialization failed.\n", safemsg, "\n", msg) } safe := msg.serializeSafe() if !bytes.Equal(safe, orig) { t.Fatal("Safe serialization failed.\n", safe, "\n", orig) } b := msg.Serialize() if !bytes.Equal(b, safe) { t.Fatal("Serialization failed.\n", b, "\n", safe) } } func (msg *IfInfomsg) write(b []byte) { native := NativeEndian() b[0] = msg.Family // pad byte is skipped because it is not exported on linux/s390x native.PutUint16(b[2:4], msg.Type) native.PutUint32(b[4:8], uint32(msg.Index)) native.PutUint32(b[8:12], msg.Flags) native.PutUint32(b[12:16], msg.Change) } func (msg *IfInfomsg) serializeSafe() []byte { length := unix.SizeofIfInfomsg b := make([]byte, length) msg.write(b) return b } func deserializeIfInfomsgSafe(b []byte) *IfInfomsg { var msg = IfInfomsg{} binary.Read(bytes.NewReader(b[0:unix.SizeofIfInfomsg]), NativeEndian(), &msg) return &msg } func TestIfInfomsgDeserializeSerialize(t *testing.T) { var orig = make([]byte, unix.SizeofIfInfomsg) rand.Read(orig) // zero out the pad byte orig[1] = 0 safemsg := deserializeIfInfomsgSafe(orig) msg := DeserializeIfInfomsg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func TestIfSocketCloses(t *testing.T) { nlSock, err := Subscribe(unix.NETLINK_ROUTE, unix.RTNLGRP_NEIGH) if err != nil { t.Fatalf("Error on creating the socket: %v", err) } endCh := make(chan error) go func(sk *NetlinkSocket, endCh chan error) { endCh <- nil for { _, _, err := sk.Receive() if err == unix.EAGAIN { endCh <- err return } } }(nlSock, endCh) // first receive nil if msg := <-endCh; msg != nil { t.Fatalf("Expected nil instead got: %v", msg) } // this to guarantee that the receive is invoked before the close time.Sleep(4 * time.Second) // Close the socket nlSock.Close() // Expect to have an error msg := <-endCh if msg == nil { t.Fatalf("Expected error instead received nil") } } func (msg *CnMsgOp) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], msg.ID.Idx) native.PutUint32(b[4:8], msg.ID.Val) native.PutUint32(b[8:12], msg.Seq) native.PutUint32(b[12:16], msg.Ack) native.PutUint16(b[16:18], msg.Length) native.PutUint16(b[18:20], msg.Flags) native.PutUint32(b[20:24], msg.Op) } func (msg *CnMsgOp) serializeSafe() []byte { length := msg.Len() b := make([]byte, length) msg.write(b) return b } func deserializeCnMsgOpSafe(b []byte) *CnMsgOp { var msg = CnMsgOp{} binary.Read(bytes.NewReader(b[0:SizeofCnMsgOp]), NativeEndian(), &msg) return &msg } func TestCnMsgOpDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofCnMsgOp) rand.Read(orig) safemsg := deserializeCnMsgOpSafe(orig) msg := DeserializeCnMsgOp(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func TestParseRouteAttrAsMap(t *testing.T) { attr1 := NewRtAttr(0x1, ZeroTerminated("foo")) attr2 := NewRtAttr(0x2, ZeroTerminated("bar")) raw := make([]byte, 0) raw = append(raw, attr1.Serialize()...) raw = append(raw, attr2.Serialize()...) attrs, err := ParseRouteAttrAsMap(raw) if err != nil { t.Errorf("failed to parse route attributes %s", err) } attr, ok := attrs[0x1] if !ok || BytesToString(attr.Value) != "foo" { t.Error("missing/incorrect \"foo\" attribute") } attr, ok = attrs[0x2] if !ok || BytesToString(attr.Value) != "bar" { t.Error("missing/incorrect \"bar\" attribute") } } netlink-1.3.0/nl/nl_unspecified.go000066400000000000000000000002201466216277000171130ustar00rootroot00000000000000// +build !linux package nl import "encoding/binary" var SupportedNlFamilies = []int{} func NativeEndian() binary.ByteOrder { return nil } netlink-1.3.0/nl/parse_attr_linux.go000066400000000000000000000032751466216277000175240ustar00rootroot00000000000000package nl import ( "encoding/binary" "fmt" "log" ) type Attribute struct { Type uint16 Value []byte } func ParseAttributes(data []byte) <-chan Attribute { native := NativeEndian() result := make(chan Attribute) go func() { i := 0 for i+4 < len(data) { length := int(native.Uint16(data[i : i+2])) attrType := native.Uint16(data[i+2 : i+4]) if length < 4 { log.Printf("attribute 0x%02x has invalid length of %d bytes", attrType, length) break } if len(data) < i+length { log.Printf("attribute 0x%02x of length %d is truncated, only %d bytes remaining", attrType, length, len(data)-i) break } result <- Attribute{ Type: attrType, Value: data[i+4 : i+length], } i += rtaAlignOf(length) } close(result) }() return result } func PrintAttributes(data []byte) { printAttributes(data, 0) } func printAttributes(data []byte, level int) { for attr := range ParseAttributes(data) { for i := 0; i < level; i++ { print("> ") } nested := attr.Type&NLA_F_NESTED != 0 fmt.Printf("type=%d nested=%v len=%v %v\n", attr.Type&NLA_TYPE_MASK, nested, len(attr.Value), attr.Value) if nested { printAttributes(attr.Value, level+1) } } } // Uint32 returns the uint32 value respecting the NET_BYTEORDER flag func (attr *Attribute) Uint32() uint32 { if attr.Type&NLA_F_NET_BYTEORDER != 0 { return binary.BigEndian.Uint32(attr.Value) } else { return NativeEndian().Uint32(attr.Value) } } // Uint64 returns the uint64 value respecting the NET_BYTEORDER flag func (attr *Attribute) Uint64() uint64 { if attr.Type&NLA_F_NET_BYTEORDER != 0 { return binary.BigEndian.Uint64(attr.Value) } else { return NativeEndian().Uint64(attr.Value) } } netlink-1.3.0/nl/rdma_link_linux.go000066400000000000000000000016641466216277000173200ustar00rootroot00000000000000package nl const ( RDMA_NL_GET_CLIENT_SHIFT = 10 ) const ( RDMA_NL_NLDEV = 5 ) const ( RDMA_NLDEV_CMD_GET = 1 RDMA_NLDEV_CMD_SET = 2 RDMA_NLDEV_CMD_NEWLINK = 3 RDMA_NLDEV_CMD_DELLINK = 4 RDMA_NLDEV_CMD_SYS_GET = 6 RDMA_NLDEV_CMD_SYS_SET = 7 ) const ( RDMA_NLDEV_ATTR_DEV_INDEX = 1 RDMA_NLDEV_ATTR_DEV_NAME = 2 RDMA_NLDEV_ATTR_PORT_INDEX = 3 RDMA_NLDEV_ATTR_CAP_FLAGS = 4 RDMA_NLDEV_ATTR_FW_VERSION = 5 RDMA_NLDEV_ATTR_NODE_GUID = 6 RDMA_NLDEV_ATTR_SYS_IMAGE_GUID = 7 RDMA_NLDEV_ATTR_SUBNET_PREFIX = 8 RDMA_NLDEV_ATTR_LID = 9 RDMA_NLDEV_ATTR_SM_LID = 10 RDMA_NLDEV_ATTR_LMC = 11 RDMA_NLDEV_ATTR_PORT_STATE = 12 RDMA_NLDEV_ATTR_PORT_PHYS_STATE = 13 RDMA_NLDEV_ATTR_DEV_NODE_TYPE = 14 RDMA_NLDEV_ATTR_NDEV_NAME = 51 RDMA_NLDEV_ATTR_LINK_TYPE = 65 RDMA_NLDEV_SYS_ATTR_NETNS_MODE = 66 RDMA_NLDEV_NET_NS_FD = 68 ) netlink-1.3.0/nl/route_linux.go000066400000000000000000000037711466216277000165170ustar00rootroot00000000000000package nl import ( "unsafe" "golang.org/x/sys/unix" ) type RtMsg struct { unix.RtMsg } func NewRtMsg() *RtMsg { return &RtMsg{ RtMsg: unix.RtMsg{ Table: unix.RT_TABLE_MAIN, Scope: unix.RT_SCOPE_UNIVERSE, Protocol: unix.RTPROT_BOOT, Type: unix.RTN_UNICAST, }, } } func NewRtDelMsg() *RtMsg { return &RtMsg{ RtMsg: unix.RtMsg{ Table: unix.RT_TABLE_MAIN, Scope: unix.RT_SCOPE_NOWHERE, }, } } func (msg *RtMsg) Len() int { return unix.SizeofRtMsg } func DeserializeRtMsg(b []byte) *RtMsg { return (*RtMsg)(unsafe.Pointer(&b[0:unix.SizeofRtMsg][0])) } func (msg *RtMsg) Serialize() []byte { return (*(*[unix.SizeofRtMsg]byte)(unsafe.Pointer(msg)))[:] } type RtNexthop struct { unix.RtNexthop Children []NetlinkRequestData } func DeserializeRtNexthop(b []byte) *RtNexthop { return &RtNexthop{ RtNexthop: *((*unix.RtNexthop)(unsafe.Pointer(&b[0:unix.SizeofRtNexthop][0]))), } } func (msg *RtNexthop) Len() int { if len(msg.Children) == 0 { return unix.SizeofRtNexthop } l := 0 for _, child := range msg.Children { l += rtaAlignOf(child.Len()) } l += unix.SizeofRtNexthop return rtaAlignOf(l) } func (msg *RtNexthop) Serialize() []byte { length := msg.Len() msg.RtNexthop.Len = uint16(length) buf := make([]byte, length) copy(buf, (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(msg)))[:]) next := rtaAlignOf(unix.SizeofRtNexthop) if len(msg.Children) > 0 { for _, child := range msg.Children { childBuf := child.Serialize() copy(buf[next:], childBuf) next += rtaAlignOf(len(childBuf)) } } return buf } type RtGenMsg struct { unix.RtGenmsg } func NewRtGenMsg() *RtGenMsg { return &RtGenMsg{ RtGenmsg: unix.RtGenmsg{ Family: unix.AF_UNSPEC, }, } } func (msg *RtGenMsg) Len() int { return rtaAlignOf(unix.SizeofRtGenmsg) } func DeserializeRtGenMsg(b []byte) *RtGenMsg { return &RtGenMsg{RtGenmsg: unix.RtGenmsg{Family: b[0]}} } func (msg *RtGenMsg) Serialize() []byte { out := make([]byte, msg.Len()) out[0] = msg.Family return out } netlink-1.3.0/nl/route_linux_test.go000066400000000000000000000027731466216277000175570ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" "golang.org/x/sys/unix" ) func (msg *RtMsg) write(b []byte) { native := NativeEndian() b[0] = msg.Family b[1] = msg.Dst_len b[2] = msg.Src_len b[3] = msg.Tos b[4] = msg.Table b[5] = msg.Protocol b[6] = msg.Scope b[7] = msg.Type native.PutUint32(b[8:12], msg.Flags) } func (msg *RtMsg) serializeSafe() []byte { len := unix.SizeofRtMsg b := make([]byte, len) msg.write(b) return b } func deserializeRtMsgSafe(b []byte) *RtMsg { var msg = RtMsg{} binary.Read(bytes.NewReader(b[0:unix.SizeofRtMsg]), NativeEndian(), &msg) return &msg } func TestRtMsgDeserializeSerialize(t *testing.T) { var orig = make([]byte, unix.SizeofRtMsg) rand.Read(orig) safemsg := deserializeRtMsgSafe(orig) msg := DeserializeRtMsg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func TestDeserializeRtNexthop(t *testing.T) { buf := make([]byte, unix.SizeofRtNexthop+64) native := NativeEndian() native.PutUint16(buf[0:2], unix.SizeofRtNexthop) buf[2] = 17 buf[3] = 1 native.PutUint32(buf[4:8], 1234) msg := DeserializeRtNexthop(buf) safemsg := &RtNexthop{ unix.RtNexthop{ Len: unix.SizeofRtNexthop, Flags: 17, Hops: 1, Ifindex: 1234, }, nil, } if msg.Len() != safemsg.Len() || msg.Flags != safemsg.Flags || msg.Hops != safemsg.Hops || msg.Ifindex != safemsg.Ifindex { t.Fatal("Deserialization failed.\nIn:", buf, "\nOut:", msg, "\n", msg.Serialize(), "\nExpected:", safemsg, "\n", safemsg.Serialize()) } } netlink-1.3.0/nl/seg6_linux.go000066400000000000000000000100211466216277000162070ustar00rootroot00000000000000package nl import ( "errors" "fmt" "net" ) type IPv6SrHdr struct { nextHdr uint8 hdrLen uint8 routingType uint8 segmentsLeft uint8 firstSegment uint8 flags uint8 reserved uint16 Segments []net.IP } func (s1 *IPv6SrHdr) Equal(s2 IPv6SrHdr) bool { if len(s1.Segments) != len(s2.Segments) { return false } for i := range s1.Segments { if !s1.Segments[i].Equal(s2.Segments[i]) { return false } } return s1.nextHdr == s2.nextHdr && s1.hdrLen == s2.hdrLen && s1.routingType == s2.routingType && s1.segmentsLeft == s2.segmentsLeft && s1.firstSegment == s2.firstSegment && s1.flags == s2.flags // reserved doesn't need to be identical. } // seg6 encap mode const ( SEG6_IPTUN_MODE_INLINE = iota SEG6_IPTUN_MODE_ENCAP ) // number of nested RTATTR // from include/uapi/linux/seg6_iptunnel.h const ( SEG6_IPTUNNEL_UNSPEC = iota SEG6_IPTUNNEL_SRH __SEG6_IPTUNNEL_MAX ) const ( SEG6_IPTUNNEL_MAX = __SEG6_IPTUNNEL_MAX - 1 ) func EncodeSEG6Encap(mode int, segments []net.IP) ([]byte, error) { nsegs := len(segments) // nsegs: number of segments if nsegs == 0 { return nil, errors.New("EncodeSEG6Encap: No Segment in srh") } b := make([]byte, 12, 12+len(segments)*16) native := NativeEndian() native.PutUint32(b, uint32(mode)) b[4] = 0 // srh.nextHdr (0 when calling netlink) b[5] = uint8(16 * nsegs >> 3) // srh.hdrLen (in 8-octets unit) b[6] = IPV6_SRCRT_TYPE_4 // srh.routingType (assigned by IANA) b[7] = uint8(nsegs - 1) // srh.segmentsLeft b[8] = uint8(nsegs - 1) // srh.firstSegment b[9] = 0 // srh.flags (SR6_FLAG1_HMAC for srh_hmac) // srh.reserved: Defined as "Tag" in draft-ietf-6man-segment-routing-header-07 native.PutUint16(b[10:], 0) // srh.reserved for _, netIP := range segments { b = append(b, netIP...) // srh.Segments } return b, nil } func DecodeSEG6Encap(buf []byte) (int, []net.IP, error) { native := NativeEndian() mode := int(native.Uint32(buf)) srh := IPv6SrHdr{ nextHdr: buf[4], hdrLen: buf[5], routingType: buf[6], segmentsLeft: buf[7], firstSegment: buf[8], flags: buf[9], reserved: native.Uint16(buf[10:12]), } buf = buf[12:] if len(buf)%16 != 0 { err := fmt.Errorf("DecodeSEG6Encap: error parsing Segment List (buf len: %d)", len(buf)) return mode, nil, err } for len(buf) > 0 { srh.Segments = append(srh.Segments, net.IP(buf[:16])) buf = buf[16:] } return mode, srh.Segments, nil } func DecodeSEG6Srh(buf []byte) ([]net.IP, error) { native := NativeEndian() srh := IPv6SrHdr{ nextHdr: buf[0], hdrLen: buf[1], routingType: buf[2], segmentsLeft: buf[3], firstSegment: buf[4], flags: buf[5], reserved: native.Uint16(buf[6:8]), } buf = buf[8:] if len(buf)%16 != 0 { err := fmt.Errorf("DecodeSEG6Srh: error parsing Segment List (buf len: %d)", len(buf)) return nil, err } for len(buf) > 0 { srh.Segments = append(srh.Segments, net.IP(buf[:16])) buf = buf[16:] } return srh.Segments, nil } func EncodeSEG6Srh(segments []net.IP) ([]byte, error) { nsegs := len(segments) // nsegs: number of segments if nsegs == 0 { return nil, errors.New("EncodeSEG6Srh: No Segments") } b := make([]byte, 8, 8+len(segments)*16) native := NativeEndian() b[0] = 0 // srh.nextHdr (0 when calling netlink) b[1] = uint8(16 * nsegs >> 3) // srh.hdrLen (in 8-octets unit) b[2] = IPV6_SRCRT_TYPE_4 // srh.routingType (assigned by IANA) b[3] = uint8(nsegs - 1) // srh.segmentsLeft b[4] = uint8(nsegs - 1) // srh.firstSegment b[5] = 0 // srh.flags (SR6_FLAG1_HMAC for srh_hmac) // srh.reserved: Defined as "Tag" in draft-ietf-6man-segment-routing-header-07 native.PutUint16(b[6:], 0) // srh.reserved for _, netIP := range segments { b = append(b, netIP...) // srh.Segments } return b, nil } // Helper functions func SEG6EncapModeString(mode int) string { switch mode { case SEG6_IPTUN_MODE_INLINE: return "inline" case SEG6_IPTUN_MODE_ENCAP: return "encap" } return "unknown" } netlink-1.3.0/nl/seg6local_linux.go000066400000000000000000000037351466216277000172400ustar00rootroot00000000000000package nl import () // seg6local parameters const ( SEG6_LOCAL_UNSPEC = iota SEG6_LOCAL_ACTION SEG6_LOCAL_SRH SEG6_LOCAL_TABLE SEG6_LOCAL_NH4 SEG6_LOCAL_NH6 SEG6_LOCAL_IIF SEG6_LOCAL_OIF SEG6_LOCAL_BPF __SEG6_LOCAL_MAX ) const ( SEG6_LOCAL_MAX = __SEG6_LOCAL_MAX ) // seg6local actions const ( SEG6_LOCAL_ACTION_END = iota + 1 // 1 SEG6_LOCAL_ACTION_END_X // 2 SEG6_LOCAL_ACTION_END_T // 3 SEG6_LOCAL_ACTION_END_DX2 // 4 SEG6_LOCAL_ACTION_END_DX6 // 5 SEG6_LOCAL_ACTION_END_DX4 // 6 SEG6_LOCAL_ACTION_END_DT6 // 7 SEG6_LOCAL_ACTION_END_DT4 // 8 SEG6_LOCAL_ACTION_END_B6 // 9 SEG6_LOCAL_ACTION_END_B6_ENCAPS // 10 SEG6_LOCAL_ACTION_END_BM // 11 SEG6_LOCAL_ACTION_END_S // 12 SEG6_LOCAL_ACTION_END_AS // 13 SEG6_LOCAL_ACTION_END_AM // 14 SEG6_LOCAL_ACTION_END_BPF // 15 __SEG6_LOCAL_ACTION_MAX ) const ( SEG6_LOCAL_ACTION_MAX = __SEG6_LOCAL_ACTION_MAX - 1 ) // Helper functions func SEG6LocalActionString(action int) string { switch action { case SEG6_LOCAL_ACTION_END: return "End" case SEG6_LOCAL_ACTION_END_X: return "End.X" case SEG6_LOCAL_ACTION_END_T: return "End.T" case SEG6_LOCAL_ACTION_END_DX2: return "End.DX2" case SEG6_LOCAL_ACTION_END_DX6: return "End.DX6" case SEG6_LOCAL_ACTION_END_DX4: return "End.DX4" case SEG6_LOCAL_ACTION_END_DT6: return "End.DT6" case SEG6_LOCAL_ACTION_END_DT4: return "End.DT4" case SEG6_LOCAL_ACTION_END_B6: return "End.B6" case SEG6_LOCAL_ACTION_END_B6_ENCAPS: return "End.B6.Encaps" case SEG6_LOCAL_ACTION_END_BM: return "End.BM" case SEG6_LOCAL_ACTION_END_S: return "End.S" case SEG6_LOCAL_ACTION_END_AS: return "End.AS" case SEG6_LOCAL_ACTION_END_AM: return "End.AM" case SEG6_LOCAL_ACTION_END_BPF: return "End.BPF" } return "unknown" } netlink-1.3.0/nl/syscall.go000066400000000000000000000036031466216277000156060ustar00rootroot00000000000000package nl // syscall package lack of rule attributes type. // Thus there are defined below const ( FRA_UNSPEC = iota FRA_DST /* destination address */ FRA_SRC /* source address */ FRA_IIFNAME /* interface name */ FRA_GOTO /* target to jump to (FR_ACT_GOTO) */ FRA_UNUSED2 FRA_PRIORITY /* priority/preference */ FRA_UNUSED3 FRA_UNUSED4 FRA_UNUSED5 FRA_FWMARK /* mark */ FRA_FLOW /* flow/class id */ FRA_TUN_ID FRA_SUPPRESS_IFGROUP FRA_SUPPRESS_PREFIXLEN FRA_TABLE /* Extended table id */ FRA_FWMASK /* mask for netfilter mark */ FRA_OIFNAME FRA_PAD FRA_L3MDEV /* iif or oif is l3mdev goto its table */ FRA_UID_RANGE /* UID range */ FRA_PROTOCOL /* Originator of the rule */ FRA_IP_PROTO /* ip proto */ FRA_SPORT_RANGE /* sport */ FRA_DPORT_RANGE /* dport */ ) // ip rule netlink request types const ( FR_ACT_UNSPEC = iota FR_ACT_TO_TBL /* Pass to fixed table */ FR_ACT_GOTO /* Jump to another rule */ FR_ACT_NOP /* No operation */ FR_ACT_RES3 FR_ACT_RES4 FR_ACT_BLACKHOLE /* Drop without notification */ FR_ACT_UNREACHABLE /* Drop with ENETUNREACH */ FR_ACT_PROHIBIT /* Drop with EACCES */ ) // socket diags related const ( SOCK_DIAG_BY_FAMILY = 20 /* linux.sock_diag.h */ SOCK_DESTROY = 21 TCPDIAG_NOCOOKIE = 0xFFFFFFFF /* TCPDIAG_NOCOOKIE in net/ipv4/tcp_diag.h*/ ) // RTA_ENCAP subtype const ( MPLS_IPTUNNEL_UNSPEC = iota MPLS_IPTUNNEL_DST ) // light weight tunnel encap types const ( LWTUNNEL_ENCAP_NONE = iota LWTUNNEL_ENCAP_MPLS LWTUNNEL_ENCAP_IP LWTUNNEL_ENCAP_ILA LWTUNNEL_ENCAP_IP6 LWTUNNEL_ENCAP_SEG6 LWTUNNEL_ENCAP_BPF LWTUNNEL_ENCAP_SEG6_LOCAL ) // routing header types const ( IPV6_SRCRT_STRICT = 0x01 // Deprecated; will be removed IPV6_SRCRT_TYPE_0 = 0 // Deprecated; will be removed IPV6_SRCRT_TYPE_2 = 2 // IPv6 type 2 Routing Header IPV6_SRCRT_TYPE_4 = 4 // Segment Routing with IPv6 ) netlink-1.3.0/nl/tc_linux.go000066400000000000000000001031611466216277000157610ustar00rootroot00000000000000package nl import ( "bytes" "encoding/binary" "fmt" "net" "unsafe" "golang.org/x/sys/unix" ) // LinkLayer const ( LINKLAYER_UNSPEC = iota LINKLAYER_ETHERNET LINKLAYER_ATM ) // ATM const ( ATM_CELL_PAYLOAD = 48 ATM_CELL_SIZE = 53 ) const TC_LINKLAYER_MASK = 0x0F // Police const ( TCA_POLICE_UNSPEC = iota TCA_POLICE_TBF TCA_POLICE_RATE TCA_POLICE_PEAKRATE TCA_POLICE_AVRATE TCA_POLICE_RESULT TCA_POLICE_MAX = TCA_POLICE_RESULT ) // Message types const ( TCA_UNSPEC = iota TCA_KIND TCA_OPTIONS TCA_STATS TCA_XSTATS TCA_RATE TCA_FCNT TCA_STATS2 TCA_STAB TCA_PAD TCA_DUMP_INVISIBLE TCA_CHAIN TCA_HW_OFFLOAD TCA_INGRESS_BLOCK TCA_EGRESS_BLOCK TCA_DUMP_FLAGS TCA_MAX = TCA_DUMP_FLAGS ) const ( TCA_ACT_TAB = 1 TCAA_MAX = 1 ) const ( TCA_ACT_UNSPEC = iota TCA_ACT_KIND TCA_ACT_OPTIONS TCA_ACT_INDEX TCA_ACT_STATS TCA_ACT_PAD TCA_ACT_COOKIE TCA_ACT_FLAGS TCA_ACT_HW_STATS TCA_ACT_USED_HW_STATS TCA_ACT_IN_HW_COUNT TCA_ACT_MAX ) const ( TCA_PRIO_UNSPEC = iota TCA_PRIO_MQ TCA_PRIO_MAX = TCA_PRIO_MQ ) const ( TCA_STATS_UNSPEC = iota TCA_STATS_BASIC TCA_STATS_RATE_EST TCA_STATS_QUEUE TCA_STATS_APP TCA_STATS_RATE_EST64 TCA_STATS_PAD TCA_STATS_BASIC_HW TCA_STATS_PKT64 TCA_STATS_MAX = TCA_STATS_PKT64 ) const ( SizeofTcMsg = 0x14 SizeofTcActionMsg = 0x04 SizeofTcPrioMap = 0x14 SizeofTcRateSpec = 0x0c SizeofTcNetemQopt = 0x18 SizeofTcNetemCorr = 0x0c SizeofTcNetemReorder = 0x08 SizeofTcNetemCorrupt = 0x08 SizeOfTcNetemRate = 0x10 SizeofTcTbfQopt = 2*SizeofTcRateSpec + 0x0c SizeofTcHtbCopt = 2*SizeofTcRateSpec + 0x14 SizeofTcHtbGlob = 0x14 SizeofTcU32Key = 0x10 SizeofTcU32Sel = 0x10 // without keys SizeofTcGen = 0x16 SizeofTcConnmark = SizeofTcGen + 0x04 SizeofTcCsum = SizeofTcGen + 0x04 SizeofTcMirred = SizeofTcGen + 0x08 SizeofTcTunnelKey = SizeofTcGen + 0x04 SizeofTcSkbEdit = SizeofTcGen SizeofTcPolice = 2*SizeofTcRateSpec + 0x20 SizeofTcSfqQopt = 0x0b SizeofTcSfqRedStats = 0x18 SizeofTcSfqQoptV1 = SizeofTcSfqQopt + SizeofTcSfqRedStats + 0x1c SizeofUint32Bitfield = 0x8 ) // struct tcmsg { // unsigned char tcm_family; // unsigned char tcm__pad1; // unsigned short tcm__pad2; // int tcm_ifindex; // __u32 tcm_handle; // __u32 tcm_parent; // __u32 tcm_info; // }; type TcMsg struct { Family uint8 Pad [3]byte Ifindex int32 Handle uint32 Parent uint32 Info uint32 } func (msg *TcMsg) Len() int { return SizeofTcMsg } func DeserializeTcMsg(b []byte) *TcMsg { return (*TcMsg)(unsafe.Pointer(&b[0:SizeofTcMsg][0])) } func (x *TcMsg) Serialize() []byte { return (*(*[SizeofTcMsg]byte)(unsafe.Pointer(x)))[:] } type Tcf struct { Install uint64 LastUse uint64 Expires uint64 FirstUse uint64 } func DeserializeTcf(b []byte) *Tcf { const size = int(unsafe.Sizeof(Tcf{})) return (*Tcf)(unsafe.Pointer(&b[0:size][0])) } // struct tcamsg { // unsigned char tca_family; // unsigned char tca__pad1; // unsigned short tca__pad2; // }; type TcActionMsg struct { Family uint8 Pad [3]byte } func (msg *TcActionMsg) Len() int { return SizeofTcActionMsg } func DeserializeTcActionMsg(b []byte) *TcActionMsg { return (*TcActionMsg)(unsafe.Pointer(&b[0:SizeofTcActionMsg][0])) } func (x *TcActionMsg) Serialize() []byte { return (*(*[SizeofTcActionMsg]byte)(unsafe.Pointer(x)))[:] } const ( TC_PRIO_MAX = 15 ) // struct tc_prio_qopt { // int bands; /* Number of bands */ // __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */ // }; type TcPrioMap struct { Bands int32 Priomap [TC_PRIO_MAX + 1]uint8 } func (msg *TcPrioMap) Len() int { return SizeofTcPrioMap } func DeserializeTcPrioMap(b []byte) *TcPrioMap { return (*TcPrioMap)(unsafe.Pointer(&b[0:SizeofTcPrioMap][0])) } func (x *TcPrioMap) Serialize() []byte { return (*(*[SizeofTcPrioMap]byte)(unsafe.Pointer(x)))[:] } const ( TCA_TBF_UNSPEC = iota TCA_TBF_PARMS TCA_TBF_RTAB TCA_TBF_PTAB TCA_TBF_RATE64 TCA_TBF_PRATE64 TCA_TBF_BURST TCA_TBF_PBURST TCA_TBF_MAX = TCA_TBF_PBURST ) // struct tc_ratespec { // unsigned char cell_log; // __u8 linklayer; /* lower 4 bits */ // unsigned short overhead; // short cell_align; // unsigned short mpu; // __u32 rate; // }; type TcRateSpec struct { CellLog uint8 Linklayer uint8 Overhead uint16 CellAlign int16 Mpu uint16 Rate uint32 } func (msg *TcRateSpec) Len() int { return SizeofTcRateSpec } func DeserializeTcRateSpec(b []byte) *TcRateSpec { return (*TcRateSpec)(unsafe.Pointer(&b[0:SizeofTcRateSpec][0])) } func (x *TcRateSpec) Serialize() []byte { return (*(*[SizeofTcRateSpec]byte)(unsafe.Pointer(x)))[:] } /** * NETEM */ const ( TCA_NETEM_UNSPEC = iota TCA_NETEM_CORR TCA_NETEM_DELAY_DIST TCA_NETEM_REORDER TCA_NETEM_CORRUPT TCA_NETEM_LOSS TCA_NETEM_RATE TCA_NETEM_ECN TCA_NETEM_RATE64 TCA_NETEM_MAX = TCA_NETEM_RATE64 ) // struct tc_netem_qopt { // __u32 latency; /* added delay (us) */ // __u32 limit; /* fifo limit (packets) */ // __u32 loss; /* random packet loss (0=none ~0=100%) */ // __u32 gap; /* re-ordering gap (0 for none) */ // __u32 duplicate; /* random packet dup (0=none ~0=100%) */ // __u32 jitter; /* random jitter in latency (us) */ // }; type TcNetemQopt struct { Latency uint32 Limit uint32 Loss uint32 Gap uint32 Duplicate uint32 Jitter uint32 } func (msg *TcNetemQopt) Len() int { return SizeofTcNetemQopt } func DeserializeTcNetemQopt(b []byte) *TcNetemQopt { return (*TcNetemQopt)(unsafe.Pointer(&b[0:SizeofTcNetemQopt][0])) } func (x *TcNetemQopt) Serialize() []byte { return (*(*[SizeofTcNetemQopt]byte)(unsafe.Pointer(x)))[:] } // struct tc_netem_corr { // __u32 delay_corr; /* delay correlation */ // __u32 loss_corr; /* packet loss correlation */ // __u32 dup_corr; /* duplicate correlation */ // }; type TcNetemCorr struct { DelayCorr uint32 LossCorr uint32 DupCorr uint32 } func (msg *TcNetemCorr) Len() int { return SizeofTcNetemCorr } func DeserializeTcNetemCorr(b []byte) *TcNetemCorr { return (*TcNetemCorr)(unsafe.Pointer(&b[0:SizeofTcNetemCorr][0])) } func (x *TcNetemCorr) Serialize() []byte { return (*(*[SizeofTcNetemCorr]byte)(unsafe.Pointer(x)))[:] } // struct tc_netem_reorder { // __u32 probability; // __u32 correlation; // }; type TcNetemReorder struct { Probability uint32 Correlation uint32 } func (msg *TcNetemReorder) Len() int { return SizeofTcNetemReorder } func DeserializeTcNetemReorder(b []byte) *TcNetemReorder { return (*TcNetemReorder)(unsafe.Pointer(&b[0:SizeofTcNetemReorder][0])) } func (x *TcNetemReorder) Serialize() []byte { return (*(*[SizeofTcNetemReorder]byte)(unsafe.Pointer(x)))[:] } // struct tc_netem_corrupt { // __u32 probability; // __u32 correlation; // }; type TcNetemCorrupt struct { Probability uint32 Correlation uint32 } func (msg *TcNetemCorrupt) Len() int { return SizeofTcNetemCorrupt } func DeserializeTcNetemCorrupt(b []byte) *TcNetemCorrupt { return (*TcNetemCorrupt)(unsafe.Pointer(&b[0:SizeofTcNetemCorrupt][0])) } func (x *TcNetemCorrupt) Serialize() []byte { return (*(*[SizeofTcNetemCorrupt]byte)(unsafe.Pointer(x)))[:] } // TcNetemRate is a struct that represents the rate of a netem qdisc type TcNetemRate struct { Rate uint32 PacketOverhead int32 CellSize uint32 CellOverhead int32 } func (msg *TcNetemRate) Len() int { return SizeofTcRateSpec } func DeserializeTcNetemRate(b []byte) *TcNetemRate { return (*TcNetemRate)(unsafe.Pointer(&b[0:SizeofTcRateSpec][0])) } func (msg *TcNetemRate) Serialize() []byte { return (*(*[SizeOfTcNetemRate]byte)(unsafe.Pointer(msg)))[:] } // struct tc_tbf_qopt { // struct tc_ratespec rate; // struct tc_ratespec peakrate; // __u32 limit; // __u32 buffer; // __u32 mtu; // }; type TcTbfQopt struct { Rate TcRateSpec Peakrate TcRateSpec Limit uint32 Buffer uint32 Mtu uint32 } func (msg *TcTbfQopt) Len() int { return SizeofTcTbfQopt } func DeserializeTcTbfQopt(b []byte) *TcTbfQopt { return (*TcTbfQopt)(unsafe.Pointer(&b[0:SizeofTcTbfQopt][0])) } func (x *TcTbfQopt) Serialize() []byte { return (*(*[SizeofTcTbfQopt]byte)(unsafe.Pointer(x)))[:] } const ( TCA_HTB_UNSPEC = iota TCA_HTB_PARMS TCA_HTB_INIT TCA_HTB_CTAB TCA_HTB_RTAB TCA_HTB_DIRECT_QLEN TCA_HTB_RATE64 TCA_HTB_CEIL64 TCA_HTB_MAX = TCA_HTB_CEIL64 ) //struct tc_htb_opt { // struct tc_ratespec rate; // struct tc_ratespec ceil; // __u32 buffer; // __u32 cbuffer; // __u32 quantum; // __u32 level; /* out only */ // __u32 prio; //}; type TcHtbCopt struct { Rate TcRateSpec Ceil TcRateSpec Buffer uint32 Cbuffer uint32 Quantum uint32 Level uint32 Prio uint32 } func (msg *TcHtbCopt) Len() int { return SizeofTcHtbCopt } func DeserializeTcHtbCopt(b []byte) *TcHtbCopt { return (*TcHtbCopt)(unsafe.Pointer(&b[0:SizeofTcHtbCopt][0])) } func (x *TcHtbCopt) Serialize() []byte { return (*(*[SizeofTcHtbCopt]byte)(unsafe.Pointer(x)))[:] } type TcHtbGlob struct { Version uint32 Rate2Quantum uint32 Defcls uint32 Debug uint32 DirectPkts uint32 } func (msg *TcHtbGlob) Len() int { return SizeofTcHtbGlob } func DeserializeTcHtbGlob(b []byte) *TcHtbGlob { return (*TcHtbGlob)(unsafe.Pointer(&b[0:SizeofTcHtbGlob][0])) } func (x *TcHtbGlob) Serialize() []byte { return (*(*[SizeofTcHtbGlob]byte)(unsafe.Pointer(x)))[:] } // HFSC type Curve struct { m1 uint32 d uint32 m2 uint32 } type HfscCopt struct { Rsc Curve Fsc Curve Usc Curve } func (c *Curve) Attrs() (uint32, uint32, uint32) { return c.m1, c.d, c.m2 } func (c *Curve) Set(m1 uint32, d uint32, m2 uint32) { c.m1 = m1 c.d = d c.m2 = m2 } func DeserializeHfscCurve(b []byte) *Curve { return &Curve{ m1: binary.LittleEndian.Uint32(b[0:4]), d: binary.LittleEndian.Uint32(b[4:8]), m2: binary.LittleEndian.Uint32(b[8:12]), } } func SerializeHfscCurve(c *Curve) (b []byte) { t := make([]byte, binary.MaxVarintLen32) binary.LittleEndian.PutUint32(t, c.m1) b = append(b, t[:4]...) binary.LittleEndian.PutUint32(t, c.d) b = append(b, t[:4]...) binary.LittleEndian.PutUint32(t, c.m2) b = append(b, t[:4]...) return b } type TcHfscOpt struct { Defcls uint16 } func (x *TcHfscOpt) Serialize() []byte { return (*(*[2]byte)(unsafe.Pointer(x)))[:] } const ( TCA_U32_UNSPEC = iota TCA_U32_CLASSID TCA_U32_HASH TCA_U32_LINK TCA_U32_DIVISOR TCA_U32_SEL TCA_U32_POLICE TCA_U32_ACT TCA_U32_INDEV TCA_U32_PCNT TCA_U32_MARK TCA_U32_MAX = TCA_U32_MARK ) // struct tc_u32_key { // __be32 mask; // __be32 val; // int off; // int offmask; // }; type TcU32Key struct { Mask uint32 // big endian Val uint32 // big endian Off int32 OffMask int32 } func (msg *TcU32Key) Len() int { return SizeofTcU32Key } func DeserializeTcU32Key(b []byte) *TcU32Key { return (*TcU32Key)(unsafe.Pointer(&b[0:SizeofTcU32Key][0])) } func (x *TcU32Key) Serialize() []byte { return (*(*[SizeofTcU32Key]byte)(unsafe.Pointer(x)))[:] } // struct tc_u32_sel { // unsigned char flags; // unsigned char offshift; // unsigned char nkeys; // // __be16 offmask; // __u16 off; // short offoff; // // short hoff; // __be32 hmask; // struct tc_u32_key keys[0]; // }; const ( TC_U32_TERMINAL = 1 << iota TC_U32_OFFSET = 1 << iota TC_U32_VAROFFSET = 1 << iota TC_U32_EAT = 1 << iota ) type TcU32Sel struct { Flags uint8 Offshift uint8 Nkeys uint8 Pad uint8 Offmask uint16 // big endian Off uint16 Offoff int16 Hoff int16 Hmask uint32 // big endian Keys []TcU32Key } func (msg *TcU32Sel) Len() int { return SizeofTcU32Sel + int(msg.Nkeys)*SizeofTcU32Key } func DeserializeTcU32Sel(b []byte) *TcU32Sel { x := &TcU32Sel{} copy((*(*[SizeofTcU32Sel]byte)(unsafe.Pointer(x)))[:], b) next := SizeofTcU32Sel var i uint8 for i = 0; i < x.Nkeys; i++ { x.Keys = append(x.Keys, *DeserializeTcU32Key(b[next:])) next += SizeofTcU32Key } return x } func (x *TcU32Sel) Serialize() []byte { // This can't just unsafe.cast because it must iterate through keys. buf := make([]byte, x.Len()) copy(buf, (*(*[SizeofTcU32Sel]byte)(unsafe.Pointer(x)))[:]) next := SizeofTcU32Sel for _, key := range x.Keys { keyBuf := key.Serialize() copy(buf[next:], keyBuf) next += SizeofTcU32Key } return buf } type TcGen struct { Index uint32 Capab uint32 Action int32 Refcnt int32 Bindcnt int32 } func (msg *TcGen) Len() int { return SizeofTcGen } func DeserializeTcGen(b []byte) *TcGen { return (*TcGen)(unsafe.Pointer(&b[0:SizeofTcGen][0])) } func (x *TcGen) Serialize() []byte { return (*(*[SizeofTcGen]byte)(unsafe.Pointer(x)))[:] } // #define tc_gen \ // __u32 index; \ // __u32 capab; \ // int action; \ // int refcnt; \ // int bindcnt const ( TCA_ACT_GACT = 5 ) const ( TCA_GACT_UNSPEC = iota TCA_GACT_TM TCA_GACT_PARMS TCA_GACT_PROB TCA_GACT_MAX = TCA_GACT_PROB ) type TcGact TcGen const ( TCA_ACT_BPF = 13 ) const ( TCA_ACT_BPF_UNSPEC = iota TCA_ACT_BPF_TM TCA_ACT_BPF_PARMS TCA_ACT_BPF_OPS_LEN TCA_ACT_BPF_OPS TCA_ACT_BPF_FD TCA_ACT_BPF_NAME TCA_ACT_BPF_MAX = TCA_ACT_BPF_NAME ) const ( TCA_BPF_FLAG_ACT_DIRECT uint32 = 1 << iota ) const ( TCA_BPF_UNSPEC = iota TCA_BPF_ACT TCA_BPF_POLICE TCA_BPF_CLASSID TCA_BPF_OPS_LEN TCA_BPF_OPS TCA_BPF_FD TCA_BPF_NAME TCA_BPF_FLAGS TCA_BPF_FLAGS_GEN TCA_BPF_TAG TCA_BPF_ID TCA_BPF_MAX = TCA_BPF_ID ) type TcBpf TcGen const ( TCA_ACT_CONNMARK = 14 ) const ( TCA_CONNMARK_UNSPEC = iota TCA_CONNMARK_PARMS TCA_CONNMARK_TM TCA_CONNMARK_MAX = TCA_CONNMARK_TM ) // struct tc_connmark { // tc_gen; // __u16 zone; // }; type TcConnmark struct { TcGen Zone uint16 } func (msg *TcConnmark) Len() int { return SizeofTcConnmark } func DeserializeTcConnmark(b []byte) *TcConnmark { return (*TcConnmark)(unsafe.Pointer(&b[0:SizeofTcConnmark][0])) } func (x *TcConnmark) Serialize() []byte { return (*(*[SizeofTcConnmark]byte)(unsafe.Pointer(x)))[:] } const ( TCA_CSUM_UNSPEC = iota TCA_CSUM_PARMS TCA_CSUM_TM TCA_CSUM_PAD TCA_CSUM_MAX = TCA_CSUM_PAD ) // struct tc_csum { // tc_gen; // __u32 update_flags; // } type TcCsum struct { TcGen UpdateFlags uint32 } func (msg *TcCsum) Len() int { return SizeofTcCsum } func DeserializeTcCsum(b []byte) *TcCsum { return (*TcCsum)(unsafe.Pointer(&b[0:SizeofTcCsum][0])) } func (x *TcCsum) Serialize() []byte { return (*(*[SizeofTcCsum]byte)(unsafe.Pointer(x)))[:] } const ( TCA_ACT_MIRRED = 8 ) const ( TCA_MIRRED_UNSPEC = iota TCA_MIRRED_TM TCA_MIRRED_PARMS TCA_MIRRED_MAX = TCA_MIRRED_PARMS ) // struct tc_mirred { // tc_gen; // int eaction; /* one of IN/EGRESS_MIRROR/REDIR */ // __u32 ifindex; /* ifindex of egress port */ // }; type TcMirred struct { TcGen Eaction int32 Ifindex uint32 } func (msg *TcMirred) Len() int { return SizeofTcMirred } func DeserializeTcMirred(b []byte) *TcMirred { return (*TcMirred)(unsafe.Pointer(&b[0:SizeofTcMirred][0])) } func (x *TcMirred) Serialize() []byte { return (*(*[SizeofTcMirred]byte)(unsafe.Pointer(x)))[:] } const ( TCA_TUNNEL_KEY_UNSPEC = iota TCA_TUNNEL_KEY_TM TCA_TUNNEL_KEY_PARMS TCA_TUNNEL_KEY_ENC_IPV4_SRC TCA_TUNNEL_KEY_ENC_IPV4_DST TCA_TUNNEL_KEY_ENC_IPV6_SRC TCA_TUNNEL_KEY_ENC_IPV6_DST TCA_TUNNEL_KEY_ENC_KEY_ID TCA_TUNNEL_KEY_PAD TCA_TUNNEL_KEY_ENC_DST_PORT TCA_TUNNEL_KEY_NO_CSUM TCA_TUNNEL_KEY_ENC_OPTS TCA_TUNNEL_KEY_ENC_TOS TCA_TUNNEL_KEY_ENC_TTL TCA_TUNNEL_KEY_MAX ) type TcTunnelKey struct { TcGen Action int32 } func (x *TcTunnelKey) Len() int { return SizeofTcTunnelKey } func DeserializeTunnelKey(b []byte) *TcTunnelKey { return (*TcTunnelKey)(unsafe.Pointer(&b[0:SizeofTcTunnelKey][0])) } func (x *TcTunnelKey) Serialize() []byte { return (*(*[SizeofTcTunnelKey]byte)(unsafe.Pointer(x)))[:] } const ( TCA_SKBEDIT_UNSPEC = iota TCA_SKBEDIT_TM TCA_SKBEDIT_PARMS TCA_SKBEDIT_PRIORITY TCA_SKBEDIT_QUEUE_MAPPING TCA_SKBEDIT_MARK TCA_SKBEDIT_PAD TCA_SKBEDIT_PTYPE TCA_SKBEDIT_MASK TCA_SKBEDIT_MAX ) type TcSkbEdit struct { TcGen } func (x *TcSkbEdit) Len() int { return SizeofTcSkbEdit } func DeserializeSkbEdit(b []byte) *TcSkbEdit { return (*TcSkbEdit)(unsafe.Pointer(&b[0:SizeofTcSkbEdit][0])) } func (x *TcSkbEdit) Serialize() []byte { return (*(*[SizeofTcSkbEdit]byte)(unsafe.Pointer(x)))[:] } // struct tc_police { // __u32 index; // int action; // __u32 limit; // __u32 burst; // __u32 mtu; // struct tc_ratespec rate; // struct tc_ratespec peakrate; // int refcnt; // int bindcnt; // __u32 capab; // }; type TcPolice struct { Index uint32 Action int32 Limit uint32 Burst uint32 Mtu uint32 Rate TcRateSpec PeakRate TcRateSpec Refcnt int32 Bindcnt int32 Capab uint32 } func (msg *TcPolice) Len() int { return SizeofTcPolice } func DeserializeTcPolice(b []byte) *TcPolice { return (*TcPolice)(unsafe.Pointer(&b[0:SizeofTcPolice][0])) } func (x *TcPolice) Serialize() []byte { return (*(*[SizeofTcPolice]byte)(unsafe.Pointer(x)))[:] } const ( TCA_FW_UNSPEC = iota TCA_FW_CLASSID TCA_FW_POLICE TCA_FW_INDEV TCA_FW_ACT TCA_FW_MASK TCA_FW_MAX = TCA_FW_MASK ) const ( TCA_MATCHALL_UNSPEC = iota TCA_MATCHALL_CLASSID TCA_MATCHALL_ACT TCA_MATCHALL_FLAGS ) const ( TCA_FQ_UNSPEC = iota TCA_FQ_PLIMIT // limit of total number of packets in queue TCA_FQ_FLOW_PLIMIT // limit of packets per flow TCA_FQ_QUANTUM // RR quantum TCA_FQ_INITIAL_QUANTUM // RR quantum for new flow TCA_FQ_RATE_ENABLE // enable/disable rate limiting TCA_FQ_FLOW_DEFAULT_RATE // obsolete do not use TCA_FQ_FLOW_MAX_RATE // per flow max rate TCA_FQ_BUCKETS_LOG // log2(number of buckets) TCA_FQ_FLOW_REFILL_DELAY // flow credit refill delay in usec TCA_FQ_ORPHAN_MASK // mask applied to orphaned skb hashes TCA_FQ_LOW_RATE_THRESHOLD // per packet delay under this rate TCA_FQ_CE_THRESHOLD // DCTCP-like CE-marking threshold TCA_FQ_TIMER_SLACK // timer slack TCA_FQ_HORIZON // time horizon in us TCA_FQ_HORIZON_DROP // drop packets beyond horizon, or cap their EDT ) const ( TCA_FQ_CODEL_UNSPEC = iota TCA_FQ_CODEL_TARGET TCA_FQ_CODEL_LIMIT TCA_FQ_CODEL_INTERVAL TCA_FQ_CODEL_ECN TCA_FQ_CODEL_FLOWS TCA_FQ_CODEL_QUANTUM TCA_FQ_CODEL_CE_THRESHOLD TCA_FQ_CODEL_DROP_BATCH_SIZE TCA_FQ_CODEL_MEMORY_LIMIT ) const ( TCA_HFSC_UNSPEC = iota TCA_HFSC_RSC TCA_HFSC_FSC TCA_HFSC_USC ) const ( TCA_FLOWER_UNSPEC = iota TCA_FLOWER_CLASSID TCA_FLOWER_INDEV TCA_FLOWER_ACT TCA_FLOWER_KEY_ETH_DST /* ETH_ALEN */ TCA_FLOWER_KEY_ETH_DST_MASK /* ETH_ALEN */ TCA_FLOWER_KEY_ETH_SRC /* ETH_ALEN */ TCA_FLOWER_KEY_ETH_SRC_MASK /* ETH_ALEN */ TCA_FLOWER_KEY_ETH_TYPE /* be16 */ TCA_FLOWER_KEY_IP_PROTO /* u8 */ TCA_FLOWER_KEY_IPV4_SRC /* be32 */ TCA_FLOWER_KEY_IPV4_SRC_MASK /* be32 */ TCA_FLOWER_KEY_IPV4_DST /* be32 */ TCA_FLOWER_KEY_IPV4_DST_MASK /* be32 */ TCA_FLOWER_KEY_IPV6_SRC /* struct in6_addr */ TCA_FLOWER_KEY_IPV6_SRC_MASK /* struct in6_addr */ TCA_FLOWER_KEY_IPV6_DST /* struct in6_addr */ TCA_FLOWER_KEY_IPV6_DST_MASK /* struct in6_addr */ TCA_FLOWER_KEY_TCP_SRC /* be16 */ TCA_FLOWER_KEY_TCP_DST /* be16 */ TCA_FLOWER_KEY_UDP_SRC /* be16 */ TCA_FLOWER_KEY_UDP_DST /* be16 */ TCA_FLOWER_FLAGS TCA_FLOWER_KEY_VLAN_ID /* be16 */ TCA_FLOWER_KEY_VLAN_PRIO /* u8 */ TCA_FLOWER_KEY_VLAN_ETH_TYPE /* be16 */ TCA_FLOWER_KEY_ENC_KEY_ID /* be32 */ TCA_FLOWER_KEY_ENC_IPV4_SRC /* be32 */ TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK /* be32 */ TCA_FLOWER_KEY_ENC_IPV4_DST /* be32 */ TCA_FLOWER_KEY_ENC_IPV4_DST_MASK /* be32 */ TCA_FLOWER_KEY_ENC_IPV6_SRC /* struct in6_addr */ TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK /* struct in6_addr */ TCA_FLOWER_KEY_ENC_IPV6_DST /* struct in6_addr */ TCA_FLOWER_KEY_ENC_IPV6_DST_MASK /* struct in6_addr */ TCA_FLOWER_KEY_TCP_SRC_MASK /* be16 */ TCA_FLOWER_KEY_TCP_DST_MASK /* be16 */ TCA_FLOWER_KEY_UDP_SRC_MASK /* be16 */ TCA_FLOWER_KEY_UDP_DST_MASK /* be16 */ TCA_FLOWER_KEY_SCTP_SRC_MASK /* be16 */ TCA_FLOWER_KEY_SCTP_DST_MASK /* be16 */ TCA_FLOWER_KEY_SCTP_SRC /* be16 */ TCA_FLOWER_KEY_SCTP_DST /* be16 */ TCA_FLOWER_KEY_ENC_UDP_SRC_PORT /* be16 */ TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK /* be16 */ TCA_FLOWER_KEY_ENC_UDP_DST_PORT /* be16 */ TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK /* be16 */ TCA_FLOWER_KEY_FLAGS /* be32 */ TCA_FLOWER_KEY_FLAGS_MASK /* be32 */ TCA_FLOWER_KEY_ICMPV4_CODE /* u8 */ TCA_FLOWER_KEY_ICMPV4_CODE_MASK /* u8 */ TCA_FLOWER_KEY_ICMPV4_TYPE /* u8 */ TCA_FLOWER_KEY_ICMPV4_TYPE_MASK /* u8 */ TCA_FLOWER_KEY_ICMPV6_CODE /* u8 */ TCA_FLOWER_KEY_ICMPV6_CODE_MASK /* u8 */ TCA_FLOWER_KEY_ICMPV6_TYPE /* u8 */ TCA_FLOWER_KEY_ICMPV6_TYPE_MASK /* u8 */ TCA_FLOWER_KEY_ARP_SIP /* be32 */ TCA_FLOWER_KEY_ARP_SIP_MASK /* be32 */ TCA_FLOWER_KEY_ARP_TIP /* be32 */ TCA_FLOWER_KEY_ARP_TIP_MASK /* be32 */ TCA_FLOWER_KEY_ARP_OP /* u8 */ TCA_FLOWER_KEY_ARP_OP_MASK /* u8 */ TCA_FLOWER_KEY_ARP_SHA /* ETH_ALEN */ TCA_FLOWER_KEY_ARP_SHA_MASK /* ETH_ALEN */ TCA_FLOWER_KEY_ARP_THA /* ETH_ALEN */ TCA_FLOWER_KEY_ARP_THA_MASK /* ETH_ALEN */ TCA_FLOWER_KEY_MPLS_TTL /* u8 - 8 bits */ TCA_FLOWER_KEY_MPLS_BOS /* u8 - 1 bit */ TCA_FLOWER_KEY_MPLS_TC /* u8 - 3 bits */ TCA_FLOWER_KEY_MPLS_LABEL /* be32 - 20 bits */ TCA_FLOWER_KEY_TCP_FLAGS /* be16 */ TCA_FLOWER_KEY_TCP_FLAGS_MASK /* be16 */ TCA_FLOWER_KEY_IP_TOS /* u8 */ TCA_FLOWER_KEY_IP_TOS_MASK /* u8 */ TCA_FLOWER_KEY_IP_TTL /* u8 */ TCA_FLOWER_KEY_IP_TTL_MASK /* u8 */ TCA_FLOWER_KEY_CVLAN_ID /* be16 */ TCA_FLOWER_KEY_CVLAN_PRIO /* u8 */ TCA_FLOWER_KEY_CVLAN_ETH_TYPE /* be16 */ TCA_FLOWER_KEY_ENC_IP_TOS /* u8 */ TCA_FLOWER_KEY_ENC_IP_TOS_MASK /* u8 */ TCA_FLOWER_KEY_ENC_IP_TTL /* u8 */ TCA_FLOWER_KEY_ENC_IP_TTL_MASK /* u8 */ TCA_FLOWER_KEY_ENC_OPTS TCA_FLOWER_KEY_ENC_OPTS_MASK __TCA_FLOWER_MAX ) const TCA_CLS_FLAGS_SKIP_HW = 1 << 0 /* don't offload filter to HW */ const TCA_CLS_FLAGS_SKIP_SW = 1 << 1 /* don't use filter in SW */ // struct tc_sfq_qopt { // unsigned quantum; /* Bytes per round allocated to flow */ // int perturb_period; /* Period of hash perturbation */ // __u32 limit; /* Maximal packets in queue */ // unsigned divisor; /* Hash divisor */ // unsigned flows; /* Maximal number of flows */ // }; type TcSfqQopt struct { Quantum uint8 Perturb int32 Limit uint32 Divisor uint8 Flows uint8 } func (x *TcSfqQopt) Len() int { return SizeofTcSfqQopt } func DeserializeTcSfqQopt(b []byte) *TcSfqQopt { return (*TcSfqQopt)(unsafe.Pointer(&b[0:SizeofTcSfqQopt][0])) } func (x *TcSfqQopt) Serialize() []byte { return (*(*[SizeofTcSfqQopt]byte)(unsafe.Pointer(x)))[:] } // struct tc_sfqred_stats { // __u32 prob_drop; /* Early drops, below max threshold */ // __u32 forced_drop; /* Early drops, after max threshold */ // __u32 prob_mark; /* Marked packets, below max threshold */ // __u32 forced_mark; /* Marked packets, after max threshold */ // __u32 prob_mark_head; /* Marked packets, below max threshold */ // __u32 forced_mark_head;/* Marked packets, after max threshold */ // }; type TcSfqRedStats struct { ProbDrop uint32 ForcedDrop uint32 ProbMark uint32 ForcedMark uint32 ProbMarkHead uint32 ForcedMarkHead uint32 } func (x *TcSfqRedStats) Len() int { return SizeofTcSfqRedStats } func DeserializeTcSfqRedStats(b []byte) *TcSfqRedStats { return (*TcSfqRedStats)(unsafe.Pointer(&b[0:SizeofTcSfqRedStats][0])) } func (x *TcSfqRedStats) Serialize() []byte { return (*(*[SizeofTcSfqRedStats]byte)(unsafe.Pointer(x)))[:] } // struct tc_sfq_qopt_v1 { // struct tc_sfq_qopt v0; // unsigned int depth; /* max number of packets per flow */ // unsigned int headdrop; // // /* SFQRED parameters */ // // __u32 limit; /* HARD maximal flow queue length (bytes) */ // __u32 qth_min; /* Min average length threshold (bytes) */ // __u32 qth_max; /* Max average length threshold (bytes) */ // unsigned char Wlog; /* log(W) */ // unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ // unsigned char Scell_log; /* cell size for idle damping */ // unsigned char flags; // __u32 max_P; /* probability, high resolution */ // // /* SFQRED stats */ // // struct tc_sfqred_stats stats; // }; type TcSfqQoptV1 struct { TcSfqQopt Depth uint32 HeadDrop uint32 Limit uint32 QthMin uint32 QthMax uint32 Wlog byte Plog byte ScellLog byte Flags byte MaxP uint32 TcSfqRedStats } func (x *TcSfqQoptV1) Len() int { return SizeofTcSfqQoptV1 } func DeserializeTcSfqQoptV1(b []byte) *TcSfqQoptV1 { return (*TcSfqQoptV1)(unsafe.Pointer(&b[0:SizeofTcSfqQoptV1][0])) } func (x *TcSfqQoptV1) Serialize() []byte { return (*(*[SizeofTcSfqQoptV1]byte)(unsafe.Pointer(x)))[:] } // IPProto represents Flower ip_proto attribute type IPProto uint8 const ( IPPROTO_TCP IPProto = unix.IPPROTO_TCP IPPROTO_UDP IPProto = unix.IPPROTO_UDP IPPROTO_SCTP IPProto = unix.IPPROTO_SCTP IPPROTO_ICMP IPProto = unix.IPPROTO_ICMP IPPROTO_ICMPV6 IPProto = unix.IPPROTO_ICMPV6 ) func (i IPProto) Serialize() []byte { arr := make([]byte, 1) arr[0] = byte(i) return arr } func (i IPProto) String() string { switch i { case IPPROTO_TCP: return "tcp" case IPPROTO_UDP: return "udp" case IPPROTO_SCTP: return "sctp" case IPPROTO_ICMP: return "icmp" case IPPROTO_ICMPV6: return "icmpv6" } return fmt.Sprintf("%d", i) } const ( MaxOffs = 128 SizeOfPeditSel = 24 SizeOfPeditKey = 24 TCA_PEDIT_KEY_EX_HTYPE = 1 TCA_PEDIT_KEY_EX_CMD = 2 ) const ( TCA_PEDIT_UNSPEC = iota TCA_PEDIT_TM TCA_PEDIT_PARMS TCA_PEDIT_PAD TCA_PEDIT_PARMS_EX TCA_PEDIT_KEYS_EX TCA_PEDIT_KEY_EX ) // /* TCA_PEDIT_KEY_EX_HDR_TYPE_NETWROK is a special case for legacy users. It // * means no specific header type - offset is relative to the network layer // */ type PeditHeaderType uint16 const ( TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK = iota TCA_PEDIT_KEY_EX_HDR_TYPE_ETH TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 TCA_PEDIT_KEY_EX_HDR_TYPE_TCP TCA_PEDIT_KEY_EX_HDR_TYPE_UDP __PEDIT_HDR_TYPE_MAX ) type PeditCmd uint16 const ( TCA_PEDIT_KEY_EX_CMD_SET = 0 TCA_PEDIT_KEY_EX_CMD_ADD = 1 ) type TcPeditSel struct { TcGen NKeys uint8 Flags uint8 } func DeserializeTcPeditKey(b []byte) *TcPeditKey { return (*TcPeditKey)(unsafe.Pointer(&b[0:SizeOfPeditKey][0])) } func DeserializeTcPedit(b []byte) (*TcPeditSel, []TcPeditKey) { x := &TcPeditSel{} copy((*(*[SizeOfPeditSel]byte)(unsafe.Pointer(x)))[:SizeOfPeditSel], b) var keys []TcPeditKey next := SizeOfPeditKey var i uint8 for i = 0; i < x.NKeys; i++ { keys = append(keys, *DeserializeTcPeditKey(b[next:])) next += SizeOfPeditKey } return x, keys } type TcPeditKey struct { Mask uint32 Val uint32 Off uint32 At uint32 OffMask uint32 Shift uint32 } type TcPeditKeyEx struct { HeaderType PeditHeaderType Cmd PeditCmd } type TcPedit struct { Sel TcPeditSel Keys []TcPeditKey KeysEx []TcPeditKeyEx Extend uint8 } func (p *TcPedit) Encode(parent *RtAttr) { parent.AddRtAttr(TCA_ACT_KIND, ZeroTerminated("pedit")) actOpts := parent.AddRtAttr(TCA_ACT_OPTIONS, nil) bbuf := bytes.NewBuffer(make([]byte, 0, int(unsafe.Sizeof(p.Sel)+unsafe.Sizeof(p.Keys)))) bbuf.Write((*(*[SizeOfPeditSel]byte)(unsafe.Pointer(&p.Sel)))[:]) for i := uint8(0); i < p.Sel.NKeys; i++ { bbuf.Write((*(*[SizeOfPeditKey]byte)(unsafe.Pointer(&p.Keys[i])))[:]) } actOpts.AddRtAttr(TCA_PEDIT_PARMS_EX, bbuf.Bytes()) exAttrs := actOpts.AddRtAttr(int(TCA_PEDIT_KEYS_EX|NLA_F_NESTED), nil) for i := uint8(0); i < p.Sel.NKeys; i++ { keyAttr := exAttrs.AddRtAttr(int(TCA_PEDIT_KEY_EX|NLA_F_NESTED), nil) htypeBuf := make([]byte, 2) cmdBuf := make([]byte, 2) NativeEndian().PutUint16(htypeBuf, uint16(p.KeysEx[i].HeaderType)) NativeEndian().PutUint16(cmdBuf, uint16(p.KeysEx[i].Cmd)) keyAttr.AddRtAttr(TCA_PEDIT_KEY_EX_HTYPE, htypeBuf) keyAttr.AddRtAttr(TCA_PEDIT_KEY_EX_CMD, cmdBuf) } } func (p *TcPedit) SetEthDst(mac net.HardwareAddr) { u32 := NativeEndian().Uint32(mac) u16 := NativeEndian().Uint16(mac[4:]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = u32 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = uint32(u16) tKey.Mask = 0xffff0000 tKey.Off = 4 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } func (p *TcPedit) SetEthSrc(mac net.HardwareAddr) { u16 := NativeEndian().Uint16(mac) u32 := NativeEndian().Uint32(mac[2:]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = uint32(u16) << 16 tKey.Mask = 0x0000ffff tKey.Off = 4 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Mask = 0 tKey.Off = 8 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } func (p *TcPedit) SetIPv6Src(ip6 net.IP) { u32 := NativeEndian().Uint32(ip6[:4]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 8 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[4:8]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 12 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[8:12]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 16 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[12:16]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 20 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } func (p *TcPedit) SetDstIP(ip net.IP) { if ip.To4() != nil { p.SetIPv4Dst(ip) } else { p.SetIPv6Dst(ip) } } func (p *TcPedit) SetSrcIP(ip net.IP) { if ip.To4() != nil { p.SetIPv4Src(ip) } else { p.SetIPv6Src(ip) } } func (p *TcPedit) SetIPv6Dst(ip6 net.IP) { u32 := NativeEndian().Uint32(ip6[:4]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 24 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[4:8]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 28 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[8:12]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 32 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ u32 = NativeEndian().Uint32(ip6[12:16]) tKey = TcPeditKey{} tKeyEx = TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 36 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } func (p *TcPedit) SetIPv4Src(ip net.IP) { u32 := NativeEndian().Uint32(ip[:4]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 12 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } func (p *TcPedit) SetIPv4Dst(ip net.IP) { u32 := NativeEndian().Uint32(ip[:4]) tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} tKey.Val = u32 tKey.Off = 16 tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } // SetDstPort only tcp and udp are supported to set port func (p *TcPedit) SetDstPort(dstPort uint16, protocol uint8) { tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} switch protocol { case unix.IPPROTO_TCP: tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP case unix.IPPROTO_UDP: tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_UDP default: return } tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET tKey.Val = uint32(Swap16(dstPort)) << 16 tKey.Mask = 0x0000ffff p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } // SetSrcPort only tcp and udp are supported to set port func (p *TcPedit) SetSrcPort(srcPort uint16, protocol uint8) { tKey := TcPeditKey{} tKeyEx := TcPeditKeyEx{} switch protocol { case unix.IPPROTO_TCP: tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP case unix.IPPROTO_UDP: tKeyEx.HeaderType = TCA_PEDIT_KEY_EX_HDR_TYPE_UDP default: return } tKeyEx.Cmd = TCA_PEDIT_KEY_EX_CMD_SET tKey.Val = uint32(Swap16(srcPort)) tKey.Mask = 0xffff0000 p.Keys = append(p.Keys, tKey) p.KeysEx = append(p.KeysEx, tKeyEx) p.Sel.NKeys++ } netlink-1.3.0/nl/tc_linux_test.go000066400000000000000000000104021466216277000170130ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) /* TcMsg */ func (msg *TcMsg) write(b []byte) { native := NativeEndian() b[0] = msg.Family copy(b[1:4], msg.Pad[:]) native.PutUint32(b[4:8], uint32(msg.Ifindex)) native.PutUint32(b[8:12], msg.Handle) native.PutUint32(b[12:16], msg.Parent) native.PutUint32(b[16:20], msg.Info) } func (msg *TcMsg) serializeSafe() []byte { length := SizeofTcMsg b := make([]byte, length) msg.write(b) return b } func deserializeTcMsgSafe(b []byte) *TcMsg { var msg = TcMsg{} binary.Read(bytes.NewReader(b[0:SizeofTcMsg]), NativeEndian(), &msg) return &msg } func TestTcMsgDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofTcMsg) rand.Read(orig) safemsg := deserializeTcMsgSafe(orig) msg := DeserializeTcMsg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } /* TcActionMsg */ func (msg *TcActionMsg) write(b []byte) { b[0] = msg.Family copy(b[1:4], msg.Pad[:]) } func (msg *TcActionMsg) serializeSafe() []byte { length := SizeofTcActionMsg b := make([]byte, length) msg.write(b) return b } func deserializeTcActionMsgSafe(b []byte) *TcActionMsg { var msg = TcActionMsg{} binary.Read(bytes.NewReader(b[0:SizeofTcActionMsg]), NativeEndian(), &msg) return &msg } func TestTcActionMsgDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofTcActionMsg) rand.Read(orig) safemsg := deserializeTcActionMsgSafe(orig) msg := DeserializeTcActionMsg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } /* TcRateSpec */ func (msg *TcRateSpec) write(b []byte) { native := NativeEndian() b[0] = msg.CellLog b[1] = msg.Linklayer native.PutUint16(b[2:4], msg.Overhead) native.PutUint16(b[4:6], uint16(msg.CellAlign)) native.PutUint16(b[6:8], msg.Mpu) native.PutUint32(b[8:12], msg.Rate) } func (msg *TcRateSpec) serializeSafe() []byte { length := SizeofTcRateSpec b := make([]byte, length) msg.write(b) return b } func deserializeTcRateSpecSafe(b []byte) *TcRateSpec { var msg = TcRateSpec{} binary.Read(bytes.NewReader(b[0:SizeofTcRateSpec]), NativeEndian(), &msg) return &msg } func TestTcRateSpecDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofTcRateSpec) rand.Read(orig) safemsg := deserializeTcRateSpecSafe(orig) msg := DeserializeTcRateSpec(orig) testDeserializeSerialize(t, orig, safemsg, msg) } /* TcTbfQopt */ func (msg *TcTbfQopt) write(b []byte) { native := NativeEndian() msg.Rate.write(b[0:SizeofTcRateSpec]) start := SizeofTcRateSpec msg.Peakrate.write(b[start : start+SizeofTcRateSpec]) start += SizeofTcRateSpec native.PutUint32(b[start:start+4], msg.Limit) start += 4 native.PutUint32(b[start:start+4], msg.Buffer) start += 4 native.PutUint32(b[start:start+4], msg.Mtu) } func (msg *TcTbfQopt) serializeSafe() []byte { length := SizeofTcTbfQopt b := make([]byte, length) msg.write(b) return b } func deserializeTcTbfQoptSafe(b []byte) *TcTbfQopt { var msg = TcTbfQopt{} binary.Read(bytes.NewReader(b[0:SizeofTcTbfQopt]), NativeEndian(), &msg) return &msg } func TestTcTbfQoptDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofTcTbfQopt) rand.Read(orig) safemsg := deserializeTcTbfQoptSafe(orig) msg := DeserializeTcTbfQopt(orig) testDeserializeSerialize(t, orig, safemsg, msg) } /* TcHtbCopt */ func (msg *TcHtbCopt) write(b []byte) { native := NativeEndian() msg.Rate.write(b[0:SizeofTcRateSpec]) start := SizeofTcRateSpec msg.Ceil.write(b[start : start+SizeofTcRateSpec]) start += SizeofTcRateSpec native.PutUint32(b[start:start+4], msg.Buffer) start += 4 native.PutUint32(b[start:start+4], msg.Cbuffer) start += 4 native.PutUint32(b[start:start+4], msg.Quantum) start += 4 native.PutUint32(b[start:start+4], msg.Level) start += 4 native.PutUint32(b[start:start+4], msg.Prio) } func (msg *TcHtbCopt) serializeSafe() []byte { length := SizeofTcHtbCopt b := make([]byte, length) msg.write(b) return b } func deserializeTcHtbCoptSafe(b []byte) *TcHtbCopt { var msg = TcHtbCopt{} binary.Read(bytes.NewReader(b[0:SizeofTcHtbCopt]), NativeEndian(), &msg) return &msg } func TestTcHtbCoptDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofTcHtbCopt) rand.Read(orig) safemsg := deserializeTcHtbCoptSafe(orig) msg := DeserializeTcHtbCopt(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/vdpa_linux.go000066400000000000000000000016161466216277000163070ustar00rootroot00000000000000package nl const ( VDPA_GENL_NAME = "vdpa" VDPA_GENL_VERSION = 0x1 ) const ( VDPA_CMD_UNSPEC = iota VDPA_CMD_MGMTDEV_NEW VDPA_CMD_MGMTDEV_GET /* can dump */ VDPA_CMD_DEV_NEW VDPA_CMD_DEV_DEL VDPA_CMD_DEV_GET /* can dump */ VDPA_CMD_DEV_CONFIG_GET /* can dump */ VDPA_CMD_DEV_VSTATS_GET ) const ( VDPA_ATTR_UNSPEC = iota VDPA_ATTR_MGMTDEV_BUS_NAME VDPA_ATTR_MGMTDEV_DEV_NAME VDPA_ATTR_MGMTDEV_SUPPORTED_CLASSES VDPA_ATTR_DEV_NAME VDPA_ATTR_DEV_ID VDPA_ATTR_DEV_VENDOR_ID VDPA_ATTR_DEV_MAX_VQS VDPA_ATTR_DEV_MAX_VQ_SIZE VDPA_ATTR_DEV_MIN_VQ_SIZE VDPA_ATTR_DEV_NET_CFG_MACADDR VDPA_ATTR_DEV_NET_STATUS VDPA_ATTR_DEV_NET_CFG_MAX_VQP VDPA_ATTR_DEV_NET_CFG_MTU VDPA_ATTR_DEV_NEGOTIATED_FEATURES VDPA_ATTR_DEV_MGMTDEV_MAX_VQS VDPA_ATTR_DEV_SUPPORTED_FEATURES VDPA_ATTR_DEV_QUEUE_INDEX VDPA_ATTR_DEV_VENDOR_ATTR_NAME VDPA_ATTR_DEV_VENDOR_ATTR_VALUE VDPA_ATTR_DEV_FEATURES ) netlink-1.3.0/nl/xfrm_linux.go000066400000000000000000000171631466216277000163350ustar00rootroot00000000000000package nl import ( "bytes" "net" "unsafe" ) // Infinity for packet and byte counts const ( XFRM_INF = ^uint64(0) ) type XfrmMsgType uint8 type XfrmMsg interface { Type() XfrmMsgType } // Message Types const ( XFRM_MSG_BASE XfrmMsgType = 0x10 XFRM_MSG_NEWSA = 0x10 XFRM_MSG_DELSA = 0x11 XFRM_MSG_GETSA = 0x12 XFRM_MSG_NEWPOLICY = 0x13 XFRM_MSG_DELPOLICY = 0x14 XFRM_MSG_GETPOLICY = 0x15 XFRM_MSG_ALLOCSPI = 0x16 XFRM_MSG_ACQUIRE = 0x17 XFRM_MSG_EXPIRE = 0x18 XFRM_MSG_UPDPOLICY = 0x19 XFRM_MSG_UPDSA = 0x1a XFRM_MSG_POLEXPIRE = 0x1b XFRM_MSG_FLUSHSA = 0x1c XFRM_MSG_FLUSHPOLICY = 0x1d XFRM_MSG_NEWAE = 0x1e XFRM_MSG_GETAE = 0x1f XFRM_MSG_REPORT = 0x20 XFRM_MSG_MIGRATE = 0x21 XFRM_MSG_NEWSADINFO = 0x22 XFRM_MSG_GETSADINFO = 0x23 XFRM_MSG_NEWSPDINFO = 0x24 XFRM_MSG_GETSPDINFO = 0x25 XFRM_MSG_MAPPING = 0x26 XFRM_MSG_MAX = 0x26 XFRM_NR_MSGTYPES = 0x17 ) // Attribute types const ( /* Netlink message attributes. */ XFRMA_UNSPEC = iota XFRMA_ALG_AUTH /* struct xfrm_algo */ XFRMA_ALG_CRYPT /* struct xfrm_algo */ XFRMA_ALG_COMP /* struct xfrm_algo */ XFRMA_ENCAP /* struct xfrm_algo + struct xfrm_encap_tmpl */ XFRMA_TMPL /* 1 or more struct xfrm_user_tmpl */ XFRMA_SA /* struct xfrm_usersa_info */ XFRMA_POLICY /* struct xfrm_userpolicy_info */ XFRMA_SEC_CTX /* struct xfrm_sec_ctx */ XFRMA_LTIME_VAL XFRMA_REPLAY_VAL XFRMA_REPLAY_THRESH XFRMA_ETIMER_THRESH XFRMA_SRCADDR /* xfrm_address_t */ XFRMA_COADDR /* xfrm_address_t */ XFRMA_LASTUSED /* unsigned long */ XFRMA_POLICY_TYPE /* struct xfrm_userpolicy_type */ XFRMA_MIGRATE XFRMA_ALG_AEAD /* struct xfrm_algo_aead */ XFRMA_KMADDRESS /* struct xfrm_user_kmaddress */ XFRMA_ALG_AUTH_TRUNC /* struct xfrm_algo_auth */ XFRMA_MARK /* struct xfrm_mark */ XFRMA_TFCPAD /* __u32 */ XFRMA_REPLAY_ESN_VAL /* struct xfrm_replay_esn */ XFRMA_SA_EXTRA_FLAGS /* __u32 */ XFRMA_PROTO /* __u8 */ XFRMA_ADDRESS_FILTER /* struct xfrm_address_filter */ XFRMA_PAD XFRMA_OFFLOAD_DEV /* struct xfrm_state_offload */ XFRMA_SET_MARK /* __u32 */ XFRMA_SET_MARK_MASK /* __u32 */ XFRMA_IF_ID /* __u32 */ XFRMA_MAX = iota - 1 ) const XFRMA_OUTPUT_MARK = XFRMA_SET_MARK const ( SizeofXfrmAddress = 0x10 SizeofXfrmSelector = 0x38 SizeofXfrmLifetimeCfg = 0x40 SizeofXfrmLifetimeCur = 0x20 SizeofXfrmId = 0x18 SizeofXfrmMark = 0x08 ) // Netlink groups const ( XFRMNLGRP_NONE = 0x0 XFRMNLGRP_ACQUIRE = 0x1 XFRMNLGRP_EXPIRE = 0x2 XFRMNLGRP_SA = 0x3 XFRMNLGRP_POLICY = 0x4 XFRMNLGRP_AEVENTS = 0x5 XFRMNLGRP_REPORT = 0x6 XFRMNLGRP_MIGRATE = 0x7 XFRMNLGRP_MAPPING = 0x8 __XFRMNLGRP_MAX = 0x9 ) // typedef union { // __be32 a4; // __be32 a6[4]; // } xfrm_address_t; type XfrmAddress [SizeofXfrmAddress]byte func (x *XfrmAddress) ToIP() net.IP { var empty = [12]byte{} ip := make(net.IP, net.IPv6len) if bytes.Equal(x[4:16], empty[:]) { ip[10] = 0xff ip[11] = 0xff copy(ip[12:16], x[0:4]) } else { copy(ip[:], x[:]) } return ip } // family is only used when x and prefixlen are both 0 func (x *XfrmAddress) ToIPNet(prefixlen uint8, family uint16) *net.IPNet { empty := [SizeofXfrmAddress]byte{} if bytes.Equal(x[:], empty[:]) && prefixlen == 0 { if family == FAMILY_V6 { return &net.IPNet{IP: net.ParseIP("::"), Mask: net.CIDRMask(int(prefixlen), 128)} } return &net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(int(prefixlen), 32)} } ip := x.ToIP() if GetIPFamily(ip) == FAMILY_V4 { return &net.IPNet{IP: ip, Mask: net.CIDRMask(int(prefixlen), 32)} } return &net.IPNet{IP: ip, Mask: net.CIDRMask(int(prefixlen), 128)} } func (x *XfrmAddress) FromIP(ip net.IP) { var empty = [16]byte{} if len(ip) < net.IPv4len { copy(x[4:16], empty[:]) } else if GetIPFamily(ip) == FAMILY_V4 { copy(x[0:4], ip.To4()[0:4]) copy(x[4:16], empty[:12]) } else { copy(x[0:16], ip.To16()[0:16]) } } func DeserializeXfrmAddress(b []byte) *XfrmAddress { return (*XfrmAddress)(unsafe.Pointer(&b[0:SizeofXfrmAddress][0])) } func (x *XfrmAddress) Serialize() []byte { return (*(*[SizeofXfrmAddress]byte)(unsafe.Pointer(x)))[:] } // struct xfrm_selector { // xfrm_address_t daddr; // xfrm_address_t saddr; // __be16 dport; // __be16 dport_mask; // __be16 sport; // __be16 sport_mask; // __u16 family; // __u8 prefixlen_d; // __u8 prefixlen_s; // __u8 proto; // int ifindex; // __kernel_uid32_t user; // }; type XfrmSelector struct { Daddr XfrmAddress Saddr XfrmAddress Dport uint16 // big endian DportMask uint16 // big endian Sport uint16 // big endian SportMask uint16 // big endian Family uint16 PrefixlenD uint8 PrefixlenS uint8 Proto uint8 Pad [3]byte Ifindex int32 User uint32 } func (msg *XfrmSelector) Len() int { return SizeofXfrmSelector } func DeserializeXfrmSelector(b []byte) *XfrmSelector { return (*XfrmSelector)(unsafe.Pointer(&b[0:SizeofXfrmSelector][0])) } func (msg *XfrmSelector) Serialize() []byte { return (*(*[SizeofXfrmSelector]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_lifetime_cfg { // __u64 soft_byte_limit; // __u64 hard_byte_limit; // __u64 soft_packet_limit; // __u64 hard_packet_limit; // __u64 soft_add_expires_seconds; // __u64 hard_add_expires_seconds; // __u64 soft_use_expires_seconds; // __u64 hard_use_expires_seconds; // }; // type XfrmLifetimeCfg struct { SoftByteLimit uint64 HardByteLimit uint64 SoftPacketLimit uint64 HardPacketLimit uint64 SoftAddExpiresSeconds uint64 HardAddExpiresSeconds uint64 SoftUseExpiresSeconds uint64 HardUseExpiresSeconds uint64 } func (msg *XfrmLifetimeCfg) Len() int { return SizeofXfrmLifetimeCfg } func DeserializeXfrmLifetimeCfg(b []byte) *XfrmLifetimeCfg { return (*XfrmLifetimeCfg)(unsafe.Pointer(&b[0:SizeofXfrmLifetimeCfg][0])) } func (msg *XfrmLifetimeCfg) Serialize() []byte { return (*(*[SizeofXfrmLifetimeCfg]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_lifetime_cur { // __u64 bytes; // __u64 packets; // __u64 add_time; // __u64 use_time; // }; type XfrmLifetimeCur struct { Bytes uint64 Packets uint64 AddTime uint64 UseTime uint64 } func (msg *XfrmLifetimeCur) Len() int { return SizeofXfrmLifetimeCur } func DeserializeXfrmLifetimeCur(b []byte) *XfrmLifetimeCur { return (*XfrmLifetimeCur)(unsafe.Pointer(&b[0:SizeofXfrmLifetimeCur][0])) } func (msg *XfrmLifetimeCur) Serialize() []byte { return (*(*[SizeofXfrmLifetimeCur]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_id { // xfrm_address_t daddr; // __be32 spi; // __u8 proto; // }; type XfrmId struct { Daddr XfrmAddress Spi uint32 // big endian Proto uint8 Pad [3]byte } func (msg *XfrmId) Len() int { return SizeofXfrmId } func DeserializeXfrmId(b []byte) *XfrmId { return (*XfrmId)(unsafe.Pointer(&b[0:SizeofXfrmId][0])) } func (msg *XfrmId) Serialize() []byte { return (*(*[SizeofXfrmId]byte)(unsafe.Pointer(msg)))[:] } type XfrmMark struct { Value uint32 Mask uint32 } func (msg *XfrmMark) Len() int { return SizeofXfrmMark } func DeserializeXfrmMark(b []byte) *XfrmMark { return (*XfrmMark)(unsafe.Pointer(&b[0:SizeofXfrmMark][0])) } func (msg *XfrmMark) Serialize() []byte { return (*(*[SizeofXfrmMark]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/xfrm_linux_test.go000066400000000000000000000111001466216277000173550ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *XfrmAddress) write(b []byte) { copy(b[0:SizeofXfrmAddress], msg[:]) } func (msg *XfrmAddress) serializeSafe() []byte { b := make([]byte, SizeofXfrmAddress) msg.write(b) return b } func deserializeXfrmAddressSafe(b []byte) *XfrmAddress { var msg = XfrmAddress{} binary.Read(bytes.NewReader(b[0:SizeofXfrmAddress]), NativeEndian(), &msg) return &msg } func TestXfrmAddressDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmAddress) rand.Read(orig) safemsg := deserializeXfrmAddressSafe(orig) msg := DeserializeXfrmAddress(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmSelector) write(b []byte) { const AddrEnd = SizeofXfrmAddress * 2 native := NativeEndian() msg.Daddr.write(b[0:SizeofXfrmAddress]) msg.Saddr.write(b[SizeofXfrmAddress:AddrEnd]) native.PutUint16(b[AddrEnd:AddrEnd+2], msg.Dport) native.PutUint16(b[AddrEnd+2:AddrEnd+4], msg.DportMask) native.PutUint16(b[AddrEnd+4:AddrEnd+6], msg.Sport) native.PutUint16(b[AddrEnd+6:AddrEnd+8], msg.SportMask) native.PutUint16(b[AddrEnd+8:AddrEnd+10], msg.Family) b[AddrEnd+10] = msg.PrefixlenD b[AddrEnd+11] = msg.PrefixlenS b[AddrEnd+12] = msg.Proto copy(b[AddrEnd+13:AddrEnd+16], msg.Pad[:]) native.PutUint32(b[AddrEnd+16:AddrEnd+20], uint32(msg.Ifindex)) native.PutUint32(b[AddrEnd+20:AddrEnd+24], msg.User) } func (msg *XfrmSelector) serializeSafe() []byte { length := SizeofXfrmSelector b := make([]byte, length) msg.write(b) return b } func deserializeXfrmSelectorSafe(b []byte) *XfrmSelector { var msg = XfrmSelector{} binary.Read(bytes.NewReader(b[0:SizeofXfrmSelector]), NativeEndian(), &msg) return &msg } func TestXfrmSelectorDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmSelector) rand.Read(orig) safemsg := deserializeXfrmSelectorSafe(orig) msg := DeserializeXfrmSelector(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmLifetimeCfg) write(b []byte) { native := NativeEndian() native.PutUint64(b[0:8], msg.SoftByteLimit) native.PutUint64(b[8:16], msg.HardByteLimit) native.PutUint64(b[16:24], msg.SoftPacketLimit) native.PutUint64(b[24:32], msg.HardPacketLimit) native.PutUint64(b[32:40], msg.SoftAddExpiresSeconds) native.PutUint64(b[40:48], msg.HardAddExpiresSeconds) native.PutUint64(b[48:56], msg.SoftUseExpiresSeconds) native.PutUint64(b[56:64], msg.HardUseExpiresSeconds) } func (msg *XfrmLifetimeCfg) serializeSafe() []byte { length := SizeofXfrmLifetimeCfg b := make([]byte, length) msg.write(b) return b } func deserializeXfrmLifetimeCfgSafe(b []byte) *XfrmLifetimeCfg { var msg = XfrmLifetimeCfg{} binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCfg]), NativeEndian(), &msg) return &msg } func TestXfrmLifetimeCfgDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmLifetimeCfg) rand.Read(orig) safemsg := deserializeXfrmLifetimeCfgSafe(orig) msg := DeserializeXfrmLifetimeCfg(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmLifetimeCur) write(b []byte) { native := NativeEndian() native.PutUint64(b[0:8], msg.Bytes) native.PutUint64(b[8:16], msg.Packets) native.PutUint64(b[16:24], msg.AddTime) native.PutUint64(b[24:32], msg.UseTime) } func (msg *XfrmLifetimeCur) serializeSafe() []byte { length := SizeofXfrmLifetimeCur b := make([]byte, length) msg.write(b) return b } func deserializeXfrmLifetimeCurSafe(b []byte) *XfrmLifetimeCur { var msg = XfrmLifetimeCur{} binary.Read(bytes.NewReader(b[0:SizeofXfrmLifetimeCur]), NativeEndian(), &msg) return &msg } func TestXfrmLifetimeCurDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmLifetimeCur) rand.Read(orig) safemsg := deserializeXfrmLifetimeCurSafe(orig) msg := DeserializeXfrmLifetimeCur(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmId) write(b []byte) { native := NativeEndian() msg.Daddr.write(b[0:SizeofXfrmAddress]) native.PutUint32(b[SizeofXfrmAddress:SizeofXfrmAddress+4], msg.Spi) b[SizeofXfrmAddress+4] = msg.Proto copy(b[SizeofXfrmAddress+5:SizeofXfrmAddress+8], msg.Pad[:]) } func (msg *XfrmId) serializeSafe() []byte { b := make([]byte, SizeofXfrmId) msg.write(b) return b } func deserializeXfrmIdSafe(b []byte) *XfrmId { var msg = XfrmId{} binary.Read(bytes.NewReader(b[0:SizeofXfrmId]), NativeEndian(), &msg) return &msg } func TestXfrmIdDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmId) rand.Read(orig) safemsg := deserializeXfrmIdSafe(orig) msg := DeserializeXfrmId(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/xfrm_monitor_linux.go000066400000000000000000000011221466216277000200700ustar00rootroot00000000000000package nl import ( "unsafe" ) const ( SizeofXfrmUserExpire = 0xe8 ) // struct xfrm_user_expire { // struct xfrm_usersa_info state; // __u8 hard; // }; type XfrmUserExpire struct { XfrmUsersaInfo XfrmUsersaInfo Hard uint8 Pad [7]byte } func (msg *XfrmUserExpire) Len() int { return SizeofXfrmUserExpire } func DeserializeXfrmUserExpire(b []byte) *XfrmUserExpire { return (*XfrmUserExpire)(unsafe.Pointer(&b[0:SizeofXfrmUserExpire][0])) } func (msg *XfrmUserExpire) Serialize() []byte { return (*(*[SizeofXfrmUserExpire]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/xfrm_monitor_linux_test.go000066400000000000000000000015221466216277000211330ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *XfrmUserExpire) write(b []byte) { msg.XfrmUsersaInfo.write(b[0:SizeofXfrmUsersaInfo]) b[SizeofXfrmUsersaInfo] = msg.Hard copy(b[SizeofXfrmUsersaInfo+1:SizeofXfrmUserExpire], msg.Pad[:]) } func (msg *XfrmUserExpire) serializeSafe() []byte { b := make([]byte, SizeofXfrmUserExpire) msg.write(b) return b } func deserializeXfrmUserExpireSafe(b []byte) *XfrmUserExpire { var msg = XfrmUserExpire{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUserExpire]), NativeEndian(), &msg) return &msg } func TestXfrmUserExpireDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUserExpire) rand.Read(orig) safemsg := deserializeXfrmUserExpireSafe(orig) msg := DeserializeXfrmUserExpire(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/xfrm_policy_linux.go000066400000000000000000000051101466216277000177010ustar00rootroot00000000000000package nl import ( "unsafe" ) const ( SizeofXfrmUserpolicyId = 0x40 SizeofXfrmUserpolicyInfo = 0xa8 SizeofXfrmUserTmpl = 0x40 ) // struct xfrm_userpolicy_id { // struct xfrm_selector sel; // __u32 index; // __u8 dir; // }; // type XfrmUserpolicyId struct { Sel XfrmSelector Index uint32 Dir uint8 Pad [3]byte } func (msg *XfrmUserpolicyId) Len() int { return SizeofXfrmUserpolicyId } func DeserializeXfrmUserpolicyId(b []byte) *XfrmUserpolicyId { return (*XfrmUserpolicyId)(unsafe.Pointer(&b[0:SizeofXfrmUserpolicyId][0])) } func (msg *XfrmUserpolicyId) Serialize() []byte { return (*(*[SizeofXfrmUserpolicyId]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_userpolicy_info { // struct xfrm_selector sel; // struct xfrm_lifetime_cfg lft; // struct xfrm_lifetime_cur curlft; // __u32 priority; // __u32 index; // __u8 dir; // __u8 action; // #define XFRM_POLICY_ALLOW 0 // #define XFRM_POLICY_BLOCK 1 // __u8 flags; // #define XFRM_POLICY_LOCALOK 1 /* Allow user to override global policy */ // /* Automatically expand selector to include matching ICMP payloads. */ // #define XFRM_POLICY_ICMP 2 // __u8 share; // }; type XfrmUserpolicyInfo struct { Sel XfrmSelector Lft XfrmLifetimeCfg Curlft XfrmLifetimeCur Priority uint32 Index uint32 Dir uint8 Action uint8 Flags uint8 Share uint8 Pad [4]byte } func (msg *XfrmUserpolicyInfo) Len() int { return SizeofXfrmUserpolicyInfo } func DeserializeXfrmUserpolicyInfo(b []byte) *XfrmUserpolicyInfo { return (*XfrmUserpolicyInfo)(unsafe.Pointer(&b[0:SizeofXfrmUserpolicyInfo][0])) } func (msg *XfrmUserpolicyInfo) Serialize() []byte { return (*(*[SizeofXfrmUserpolicyInfo]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_user_tmpl { // struct xfrm_id id; // __u16 family; // xfrm_address_t saddr; // __u32 reqid; // __u8 mode; // __u8 share; // __u8 optional; // __u32 aalgos; // __u32 ealgos; // __u32 calgos; // } type XfrmUserTmpl struct { XfrmId XfrmId Family uint16 Pad1 [2]byte Saddr XfrmAddress Reqid uint32 Mode uint8 Share uint8 Optional uint8 Pad2 byte Aalgos uint32 Ealgos uint32 Calgos uint32 } func (msg *XfrmUserTmpl) Len() int { return SizeofXfrmUserTmpl } func DeserializeXfrmUserTmpl(b []byte) *XfrmUserTmpl { return (*XfrmUserTmpl)(unsafe.Pointer(&b[0:SizeofXfrmUserTmpl][0])) } func (msg *XfrmUserTmpl) Serialize() []byte { return (*(*[SizeofXfrmUserTmpl]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/xfrm_policy_linux_test.go000066400000000000000000000063611466216277000207510ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *XfrmUserpolicyId) write(b []byte) { native := NativeEndian() msg.Sel.write(b[0:SizeofXfrmSelector]) native.PutUint32(b[SizeofXfrmSelector:SizeofXfrmSelector+4], msg.Index) b[SizeofXfrmSelector+4] = msg.Dir copy(b[SizeofXfrmSelector+5:SizeofXfrmSelector+8], msg.Pad[:]) } func (msg *XfrmUserpolicyId) serializeSafe() []byte { b := make([]byte, SizeofXfrmUserpolicyId) msg.write(b) return b } func deserializeXfrmUserpolicyIdSafe(b []byte) *XfrmUserpolicyId { var msg = XfrmUserpolicyId{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyId]), NativeEndian(), &msg) return &msg } func TestXfrmUserpolicyIdDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUserpolicyId) rand.Read(orig) safemsg := deserializeXfrmUserpolicyIdSafe(orig) msg := DeserializeXfrmUserpolicyId(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmUserpolicyInfo) write(b []byte) { const CfgEnd = SizeofXfrmSelector + SizeofXfrmLifetimeCfg const CurEnd = CfgEnd + SizeofXfrmLifetimeCur native := NativeEndian() msg.Sel.write(b[0:SizeofXfrmSelector]) msg.Lft.write(b[SizeofXfrmSelector:CfgEnd]) msg.Curlft.write(b[CfgEnd:CurEnd]) native.PutUint32(b[CurEnd:CurEnd+4], msg.Priority) native.PutUint32(b[CurEnd+4:CurEnd+8], msg.Index) b[CurEnd+8] = msg.Dir b[CurEnd+9] = msg.Action b[CurEnd+10] = msg.Flags b[CurEnd+11] = msg.Share copy(b[CurEnd+12:CurEnd+16], msg.Pad[:]) } func (msg *XfrmUserpolicyInfo) serializeSafe() []byte { b := make([]byte, SizeofXfrmUserpolicyInfo) msg.write(b) return b } func deserializeXfrmUserpolicyInfoSafe(b []byte) *XfrmUserpolicyInfo { var msg = XfrmUserpolicyInfo{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUserpolicyInfo]), NativeEndian(), &msg) return &msg } func TestXfrmUserpolicyInfoDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUserpolicyInfo) rand.Read(orig) safemsg := deserializeXfrmUserpolicyInfoSafe(orig) msg := DeserializeXfrmUserpolicyInfo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmUserTmpl) write(b []byte) { const AddrEnd = SizeofXfrmId + 4 + SizeofXfrmAddress native := NativeEndian() msg.XfrmId.write(b[0:SizeofXfrmId]) native.PutUint16(b[SizeofXfrmId:SizeofXfrmId+2], msg.Family) copy(b[SizeofXfrmId+2:SizeofXfrmId+4], msg.Pad1[:]) msg.Saddr.write(b[SizeofXfrmId+4 : AddrEnd]) native.PutUint32(b[AddrEnd:AddrEnd+4], msg.Reqid) b[AddrEnd+4] = msg.Mode b[AddrEnd+5] = msg.Share b[AddrEnd+6] = msg.Optional b[AddrEnd+7] = msg.Pad2 native.PutUint32(b[AddrEnd+8:AddrEnd+12], msg.Aalgos) native.PutUint32(b[AddrEnd+12:AddrEnd+16], msg.Ealgos) native.PutUint32(b[AddrEnd+16:AddrEnd+20], msg.Calgos) } func (msg *XfrmUserTmpl) serializeSafe() []byte { b := make([]byte, SizeofXfrmUserTmpl) msg.write(b) return b } func deserializeXfrmUserTmplSafe(b []byte) *XfrmUserTmpl { var msg = XfrmUserTmpl{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUserTmpl]), NativeEndian(), &msg) return &msg } func TestXfrmUserTmplDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUserTmpl) rand.Read(orig) safemsg := deserializeXfrmUserTmplSafe(orig) msg := DeserializeXfrmUserTmpl(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/nl/xfrm_state_linux.go000066400000000000000000000203701466216277000175270ustar00rootroot00000000000000package nl import ( "unsafe" ) const ( SizeofXfrmUsersaId = 0x18 SizeofXfrmStats = 0x0c SizeofXfrmUsersaInfo = 0xe0 SizeofXfrmUserSpiInfo = 0xe8 SizeofXfrmAlgo = 0x44 SizeofXfrmAlgoAuth = 0x48 SizeofXfrmAlgoAEAD = 0x48 SizeofXfrmEncapTmpl = 0x18 SizeofXfrmUsersaFlush = 0x1 SizeofXfrmReplayStateEsn = 0x18 SizeofXfrmReplayState = 0x0c ) const ( XFRM_STATE_NOECN = 1 XFRM_STATE_DECAP_DSCP = 2 XFRM_STATE_NOPMTUDISC = 4 XFRM_STATE_WILDRECV = 8 XFRM_STATE_ICMP = 16 XFRM_STATE_AF_UNSPEC = 32 XFRM_STATE_ALIGN4 = 64 XFRM_STATE_ESN = 128 ) const ( XFRM_SA_XFLAG_DONT_ENCAP_DSCP = 1 XFRM_SA_XFLAG_OSEQ_MAY_WRAP = 2 ) // struct xfrm_usersa_id { // xfrm_address_t daddr; // __be32 spi; // __u16 family; // __u8 proto; // }; type XfrmUsersaId struct { Daddr XfrmAddress Spi uint32 // big endian Family uint16 Proto uint8 Pad byte } func (msg *XfrmUsersaId) Len() int { return SizeofXfrmUsersaId } func DeserializeXfrmUsersaId(b []byte) *XfrmUsersaId { return (*XfrmUsersaId)(unsafe.Pointer(&b[0:SizeofXfrmUsersaId][0])) } func (msg *XfrmUsersaId) Serialize() []byte { return (*(*[SizeofXfrmUsersaId]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_stats { // __u32 replay_window; // __u32 replay; // __u32 integrity_failed; // }; type XfrmStats struct { ReplayWindow uint32 Replay uint32 IntegrityFailed uint32 } func (msg *XfrmStats) Len() int { return SizeofXfrmStats } func DeserializeXfrmStats(b []byte) *XfrmStats { return (*XfrmStats)(unsafe.Pointer(&b[0:SizeofXfrmStats][0])) } func (msg *XfrmStats) Serialize() []byte { return (*(*[SizeofXfrmStats]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_usersa_info { // struct xfrm_selector sel; // struct xfrm_id id; // xfrm_address_t saddr; // struct xfrm_lifetime_cfg lft; // struct xfrm_lifetime_cur curlft; // struct xfrm_stats stats; // __u32 seq; // __u32 reqid; // __u16 family; // __u8 mode; /* XFRM_MODE_xxx */ // __u8 replay_window; // __u8 flags; // #define XFRM_STATE_NOECN 1 // #define XFRM_STATE_DECAP_DSCP 2 // #define XFRM_STATE_NOPMTUDISC 4 // #define XFRM_STATE_WILDRECV 8 // #define XFRM_STATE_ICMP 16 // #define XFRM_STATE_AF_UNSPEC 32 // #define XFRM_STATE_ALIGN4 64 // #define XFRM_STATE_ESN 128 // }; // // #define XFRM_SA_XFLAG_DONT_ENCAP_DSCP 1 // #define XFRM_SA_XFLAG_OSEQ_MAY_WRAP 2 // type XfrmUsersaInfo struct { Sel XfrmSelector Id XfrmId Saddr XfrmAddress Lft XfrmLifetimeCfg Curlft XfrmLifetimeCur Stats XfrmStats Seq uint32 Reqid uint32 Family uint16 Mode uint8 ReplayWindow uint8 Flags uint8 Pad [7]byte } func (msg *XfrmUsersaInfo) Len() int { return SizeofXfrmUsersaInfo } func DeserializeXfrmUsersaInfo(b []byte) *XfrmUsersaInfo { return (*XfrmUsersaInfo)(unsafe.Pointer(&b[0:SizeofXfrmUsersaInfo][0])) } func (msg *XfrmUsersaInfo) Serialize() []byte { return (*(*[SizeofXfrmUsersaInfo]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_userspi_info { // struct xfrm_usersa_info info; // __u32 min; // __u32 max; // }; type XfrmUserSpiInfo struct { XfrmUsersaInfo XfrmUsersaInfo Min uint32 Max uint32 } func (msg *XfrmUserSpiInfo) Len() int { return SizeofXfrmUserSpiInfo } func DeserializeXfrmUserSpiInfo(b []byte) *XfrmUserSpiInfo { return (*XfrmUserSpiInfo)(unsafe.Pointer(&b[0:SizeofXfrmUserSpiInfo][0])) } func (msg *XfrmUserSpiInfo) Serialize() []byte { return (*(*[SizeofXfrmUserSpiInfo]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_algo { // char alg_name[64]; // unsigned int alg_key_len; /* in bits */ // char alg_key[0]; // }; type XfrmAlgo struct { AlgName [64]byte AlgKeyLen uint32 AlgKey []byte } func (msg *XfrmAlgo) Len() int { return SizeofXfrmAlgo + int(msg.AlgKeyLen/8) } func DeserializeXfrmAlgo(b []byte) *XfrmAlgo { ret := XfrmAlgo{} copy(ret.AlgName[:], b[0:64]) ret.AlgKeyLen = *(*uint32)(unsafe.Pointer(&b[64])) ret.AlgKey = b[68:ret.Len()] return &ret } func (msg *XfrmAlgo) Serialize() []byte { b := make([]byte, msg.Len()) copy(b[0:64], msg.AlgName[:]) copy(b[64:68], (*(*[4]byte)(unsafe.Pointer(&msg.AlgKeyLen)))[:]) copy(b[68:msg.Len()], msg.AlgKey[:]) return b } // struct xfrm_algo_auth { // char alg_name[64]; // unsigned int alg_key_len; /* in bits */ // unsigned int alg_trunc_len; /* in bits */ // char alg_key[0]; // }; type XfrmAlgoAuth struct { AlgName [64]byte AlgKeyLen uint32 AlgTruncLen uint32 AlgKey []byte } func (msg *XfrmAlgoAuth) Len() int { return SizeofXfrmAlgoAuth + int(msg.AlgKeyLen/8) } func DeserializeXfrmAlgoAuth(b []byte) *XfrmAlgoAuth { ret := XfrmAlgoAuth{} copy(ret.AlgName[:], b[0:64]) ret.AlgKeyLen = *(*uint32)(unsafe.Pointer(&b[64])) ret.AlgTruncLen = *(*uint32)(unsafe.Pointer(&b[68])) ret.AlgKey = b[72:ret.Len()] return &ret } func (msg *XfrmAlgoAuth) Serialize() []byte { b := make([]byte, msg.Len()) copy(b[0:64], msg.AlgName[:]) copy(b[64:68], (*(*[4]byte)(unsafe.Pointer(&msg.AlgKeyLen)))[:]) copy(b[68:72], (*(*[4]byte)(unsafe.Pointer(&msg.AlgTruncLen)))[:]) copy(b[72:msg.Len()], msg.AlgKey[:]) return b } // struct xfrm_algo_aead { // char alg_name[64]; // unsigned int alg_key_len; /* in bits */ // unsigned int alg_icv_len; /* in bits */ // char alg_key[0]; // } type XfrmAlgoAEAD struct { AlgName [64]byte AlgKeyLen uint32 AlgICVLen uint32 AlgKey []byte } func (msg *XfrmAlgoAEAD) Len() int { return SizeofXfrmAlgoAEAD + int(msg.AlgKeyLen/8) } func DeserializeXfrmAlgoAEAD(b []byte) *XfrmAlgoAEAD { ret := XfrmAlgoAEAD{} copy(ret.AlgName[:], b[0:64]) ret.AlgKeyLen = *(*uint32)(unsafe.Pointer(&b[64])) ret.AlgICVLen = *(*uint32)(unsafe.Pointer(&b[68])) ret.AlgKey = b[72:ret.Len()] return &ret } func (msg *XfrmAlgoAEAD) Serialize() []byte { b := make([]byte, msg.Len()) copy(b[0:64], msg.AlgName[:]) copy(b[64:68], (*(*[4]byte)(unsafe.Pointer(&msg.AlgKeyLen)))[:]) copy(b[68:72], (*(*[4]byte)(unsafe.Pointer(&msg.AlgICVLen)))[:]) copy(b[72:msg.Len()], msg.AlgKey[:]) return b } // struct xfrm_encap_tmpl { // __u16 encap_type; // __be16 encap_sport; // __be16 encap_dport; // xfrm_address_t encap_oa; // }; type XfrmEncapTmpl struct { EncapType uint16 EncapSport uint16 // big endian EncapDport uint16 // big endian Pad [2]byte EncapOa XfrmAddress } func (msg *XfrmEncapTmpl) Len() int { return SizeofXfrmEncapTmpl } func DeserializeXfrmEncapTmpl(b []byte) *XfrmEncapTmpl { return (*XfrmEncapTmpl)(unsafe.Pointer(&b[0:SizeofXfrmEncapTmpl][0])) } func (msg *XfrmEncapTmpl) Serialize() []byte { return (*(*[SizeofXfrmEncapTmpl]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_usersa_flush { // __u8 proto; // }; type XfrmUsersaFlush struct { Proto uint8 } func (msg *XfrmUsersaFlush) Len() int { return SizeofXfrmUsersaFlush } func DeserializeXfrmUsersaFlush(b []byte) *XfrmUsersaFlush { return (*XfrmUsersaFlush)(unsafe.Pointer(&b[0:SizeofXfrmUsersaFlush][0])) } func (msg *XfrmUsersaFlush) Serialize() []byte { return (*(*[SizeofXfrmUsersaFlush]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_replay_state_esn { // unsigned int bmp_len; // __u32 oseq; // __u32 seq; // __u32 oseq_hi; // __u32 seq_hi; // __u32 replay_window; // __u32 bmp[0]; // }; type XfrmReplayStateEsn struct { BmpLen uint32 OSeq uint32 Seq uint32 OSeqHi uint32 SeqHi uint32 ReplayWindow uint32 Bmp []uint32 } func (msg *XfrmReplayStateEsn) Serialize() []byte { // We deliberately do not pass Bmp, as it gets set by the kernel. return (*(*[SizeofXfrmReplayStateEsn]byte)(unsafe.Pointer(msg)))[:] } // struct xfrm_replay_state { // __u32 oseq; // __u32 seq; // __u32 bitmap; // }; type XfrmReplayState struct { OSeq uint32 Seq uint32 BitMap uint32 } func DeserializeXfrmReplayState(b []byte) *XfrmReplayState { return (*XfrmReplayState)(unsafe.Pointer(&b[0:SizeofXfrmReplayState][0])) } func (msg *XfrmReplayState) Serialize() []byte { return (*(*[SizeofXfrmReplayState]byte)(unsafe.Pointer(msg)))[:] } netlink-1.3.0/nl/xfrm_state_linux_test.go000066400000000000000000000211161466216277000205650ustar00rootroot00000000000000package nl import ( "bytes" "crypto/rand" "encoding/binary" "testing" ) func (msg *XfrmUsersaId) write(b []byte) { native := NativeEndian() msg.Daddr.write(b[0:SizeofXfrmAddress]) native.PutUint32(b[SizeofXfrmAddress:SizeofXfrmAddress+4], msg.Spi) native.PutUint16(b[SizeofXfrmAddress+4:SizeofXfrmAddress+6], msg.Family) b[SizeofXfrmAddress+6] = msg.Proto b[SizeofXfrmAddress+7] = msg.Pad } func (msg *XfrmUsersaId) serializeSafe() []byte { b := make([]byte, SizeofXfrmUsersaId) msg.write(b) return b } func deserializeXfrmUsersaIdSafe(b []byte) *XfrmUsersaId { var msg = XfrmUsersaId{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUsersaId]), NativeEndian(), &msg) return &msg } func TestXfrmUsersaIdDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUsersaId) rand.Read(orig) safemsg := deserializeXfrmUsersaIdSafe(orig) msg := DeserializeXfrmUsersaId(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmStats) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], msg.ReplayWindow) native.PutUint32(b[4:8], msg.Replay) native.PutUint32(b[8:12], msg.IntegrityFailed) } func (msg *XfrmStats) serializeSafe() []byte { b := make([]byte, SizeofXfrmStats) msg.write(b) return b } func deserializeXfrmStatsSafe(b []byte) *XfrmStats { var msg = XfrmStats{} binary.Read(bytes.NewReader(b[0:SizeofXfrmStats]), NativeEndian(), &msg) return &msg } func TestXfrmStatsDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmStats) rand.Read(orig) safemsg := deserializeXfrmStatsSafe(orig) msg := DeserializeXfrmStats(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmUsersaInfo) write(b []byte) { const IdEnd = SizeofXfrmSelector + SizeofXfrmId const AddressEnd = IdEnd + SizeofXfrmAddress const CfgEnd = AddressEnd + SizeofXfrmLifetimeCfg const CurEnd = CfgEnd + SizeofXfrmLifetimeCur const StatsEnd = CurEnd + SizeofXfrmStats native := NativeEndian() msg.Sel.write(b[0:SizeofXfrmSelector]) msg.Id.write(b[SizeofXfrmSelector:IdEnd]) msg.Saddr.write(b[IdEnd:AddressEnd]) msg.Lft.write(b[AddressEnd:CfgEnd]) msg.Curlft.write(b[CfgEnd:CurEnd]) msg.Stats.write(b[CurEnd:StatsEnd]) native.PutUint32(b[StatsEnd:StatsEnd+4], msg.Seq) native.PutUint32(b[StatsEnd+4:StatsEnd+8], msg.Reqid) native.PutUint16(b[StatsEnd+8:StatsEnd+10], msg.Family) b[StatsEnd+10] = msg.Mode b[StatsEnd+11] = msg.ReplayWindow b[StatsEnd+12] = msg.Flags copy(b[StatsEnd+13:StatsEnd+20], msg.Pad[:]) } func (msg *XfrmUsersaInfo) serializeSafe() []byte { b := make([]byte, SizeofXfrmUsersaInfo) msg.write(b) return b } func deserializeXfrmUsersaInfoSafe(b []byte) *XfrmUsersaInfo { var msg = XfrmUsersaInfo{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUsersaInfo]), NativeEndian(), &msg) return &msg } func TestXfrmUsersaInfoDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUsersaInfo) rand.Read(orig) safemsg := deserializeXfrmUsersaInfoSafe(orig) msg := DeserializeXfrmUsersaInfo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmAlgo) write(b []byte) { native := NativeEndian() copy(b[0:64], msg.AlgName[:]) native.PutUint32(b[64:68], msg.AlgKeyLen) copy(b[68:msg.Len()], msg.AlgKey[:]) } func (msg *XfrmAlgo) serializeSafe() []byte { b := make([]byte, msg.Len()) msg.write(b) return b } func (msg *XfrmUserSpiInfo) write(b []byte) { native := NativeEndian() msg.XfrmUsersaInfo.write(b[0:SizeofXfrmUsersaInfo]) native.PutUint32(b[SizeofXfrmUsersaInfo:SizeofXfrmUsersaInfo+4], msg.Min) native.PutUint32(b[SizeofXfrmUsersaInfo+4:SizeofXfrmUsersaInfo+8], msg.Max) } func (msg *XfrmUserSpiInfo) serializeSafe() []byte { b := make([]byte, SizeofXfrmUserSpiInfo) msg.write(b) return b } func deserializeXfrmUserSpiInfoSafe(b []byte) *XfrmUserSpiInfo { var msg = XfrmUserSpiInfo{} binary.Read(bytes.NewReader(b[0:SizeofXfrmUserSpiInfo]), NativeEndian(), &msg) return &msg } func TestXfrmUserSpiInfoDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmUserSpiInfo) rand.Read(orig) safemsg := deserializeXfrmUserSpiInfoSafe(orig) msg := DeserializeXfrmUserSpiInfo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func deserializeXfrmAlgoSafe(b []byte) *XfrmAlgo { var msg = XfrmAlgo{} copy(msg.AlgName[:], b[0:64]) binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen) msg.AlgKey = b[68:msg.Len()] return &msg } func TestXfrmAlgoDeserializeSerialize(t *testing.T) { native := NativeEndian() // use a 32 byte key len var orig = make([]byte, SizeofXfrmAlgo+32) rand.Read(orig) // set the key len to 256 bits var KeyLen uint32 = 0x00000100 // Little Endian Big Endian // orig[64] = 0 orig[64] = 0 // orig[65] = 1 orig[65] = 0 // orig[66] = 0 orig[66] = 1 // orig[67] = 0 orig[67] = 0 native.PutUint32(orig[64:68], KeyLen) safemsg := deserializeXfrmAlgoSafe(orig) msg := DeserializeXfrmAlgo(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmAlgoAuth) write(b []byte) { native := NativeEndian() copy(b[0:64], msg.AlgName[:]) native.PutUint32(b[64:68], msg.AlgKeyLen) native.PutUint32(b[68:72], msg.AlgTruncLen) copy(b[72:msg.Len()], msg.AlgKey[:]) } func (msg *XfrmAlgoAuth) serializeSafe() []byte { b := make([]byte, msg.Len()) msg.write(b) return b } func deserializeXfrmAlgoAuthSafe(b []byte) *XfrmAlgoAuth { var msg = XfrmAlgoAuth{} copy(msg.AlgName[:], b[0:64]) binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen) binary.Read(bytes.NewReader(b[68:72]), NativeEndian(), &msg.AlgTruncLen) msg.AlgKey = b[72:msg.Len()] return &msg } func TestXfrmAlgoAuthDeserializeSerialize(t *testing.T) { native := NativeEndian() // use a 32 byte key len var orig = make([]byte, SizeofXfrmAlgoAuth+32) rand.Read(orig) // set the key len to 256 bits var KeyLen uint32 = 0x00000100 // Little Endian Big Endian // orig[64] = 0 orig[64] = 0 // orig[65] = 1 orig[65] = 0 // orig[66] = 0 orig[66] = 1 // orig[67] = 0 orig[67] = 0 native.PutUint32(orig[64:68], KeyLen) safemsg := deserializeXfrmAlgoAuthSafe(orig) msg := DeserializeXfrmAlgoAuth(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmEncapTmpl) write(b []byte) { native := NativeEndian() native.PutUint16(b[0:2], msg.EncapType) native.PutUint16(b[2:4], msg.EncapSport) native.PutUint16(b[4:6], msg.EncapDport) copy(b[6:8], msg.Pad[:]) msg.EncapOa.write(b[8:SizeofXfrmAddress]) } func (msg *XfrmEncapTmpl) serializeSafe() []byte { b := make([]byte, SizeofXfrmEncapTmpl) msg.write(b) return b } func deserializeXfrmEncapTmplSafe(b []byte) *XfrmEncapTmpl { var msg = XfrmEncapTmpl{} binary.Read(bytes.NewReader(b[0:SizeofXfrmEncapTmpl]), NativeEndian(), &msg) return &msg } func TestXfrmEncapTmplDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmEncapTmpl) rand.Read(orig) safemsg := deserializeXfrmEncapTmplSafe(orig) msg := DeserializeXfrmEncapTmpl(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmMark) write(b []byte) { native := NativeEndian() native.PutUint32(b[0:4], msg.Value) native.PutUint32(b[4:8], msg.Mask) } func (msg *XfrmMark) serializeSafe() []byte { b := make([]byte, SizeofXfrmMark) msg.write(b) return b } func deserializeXfrmMarkSafe(b []byte) *XfrmMark { var msg = XfrmMark{} binary.Read(bytes.NewReader(b[0:SizeofXfrmMark]), NativeEndian(), &msg) return &msg } func TestXfrmMarkDeserializeSerialize(t *testing.T) { var orig = make([]byte, SizeofXfrmMark) rand.Read(orig) safemsg := deserializeXfrmMarkSafe(orig) msg := DeserializeXfrmMark(orig) testDeserializeSerialize(t, orig, safemsg, msg) } func (msg *XfrmAlgoAEAD) write(b []byte) { native := NativeEndian() copy(b[0:64], msg.AlgName[:]) native.PutUint32(b[64:68], msg.AlgKeyLen) native.PutUint32(b[68:72], msg.AlgICVLen) copy(b[72:msg.Len()], msg.AlgKey[:]) } func (msg *XfrmAlgoAEAD) serializeSafe() []byte { b := make([]byte, msg.Len()) msg.write(b) return b } func deserializeXfrmAlgoAEADSafe(b []byte) *XfrmAlgoAEAD { var msg = XfrmAlgoAEAD{} copy(msg.AlgName[:], b[0:64]) binary.Read(bytes.NewReader(b[64:68]), NativeEndian(), &msg.AlgKeyLen) binary.Read(bytes.NewReader(b[68:72]), NativeEndian(), &msg.AlgICVLen) msg.AlgKey = b[72:msg.Len()] return &msg } func TestXfrmXfrmAlgoAeadDeserializeSerialize(t *testing.T) { native := NativeEndian() // use a 32 byte key len var orig = make([]byte, SizeofXfrmAlgoAEAD+36) rand.Read(orig) // set the key len to (256 + 32) bits var KeyLen uint32 = 0x00000120 native.PutUint32(orig[64:68], KeyLen) safemsg := deserializeXfrmAlgoAEADSafe(orig) msg := DeserializeXfrmAlgoAEAD(orig) testDeserializeSerialize(t, orig, safemsg, msg) } netlink-1.3.0/order.go000066400000000000000000000010271466216277000146340ustar00rootroot00000000000000package netlink import ( "encoding/binary" "github.com/vishvananda/netlink/nl" ) var ( native = nl.NativeEndian() networkOrder = binary.BigEndian ) func htonl(val uint32) []byte { bytes := make([]byte, 4) binary.BigEndian.PutUint32(bytes, val) return bytes } func htons(val uint16) []byte { bytes := make([]byte, 2) binary.BigEndian.PutUint16(bytes, val) return bytes } func ntohl(buf []byte) uint32 { return binary.BigEndian.Uint32(buf) } func ntohs(buf []byte) uint16 { return binary.BigEndian.Uint16(buf) } netlink-1.3.0/proc_event_linux.go000066400000000000000000000072701466216277000171120ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "fmt" "os" "syscall" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) const CN_IDX_PROC = 0x1 const ( PROC_EVENT_NONE = 0x00000000 PROC_EVENT_FORK = 0x00000001 PROC_EVENT_EXEC = 0x00000002 PROC_EVENT_UID = 0x00000004 PROC_EVENT_GID = 0x00000040 PROC_EVENT_SID = 0x00000080 PROC_EVENT_PTRACE = 0x00000100 PROC_EVENT_COMM = 0x00000200 PROC_EVENT_COREDUMP = 0x40000000 PROC_EVENT_EXIT = 0x80000000 ) const ( CN_VAL_PROC = 0x1 PROC_CN_MCAST_LISTEN = 0x1 ) type ProcEventMsg interface { Pid() uint32 Tgid() uint32 } type ProcEventHeader struct { What uint32 CPU uint32 Timestamp uint64 } type ProcEvent struct { ProcEventHeader Msg ProcEventMsg } func (pe *ProcEvent) setHeader(h ProcEventHeader) { pe.What = h.What pe.CPU = h.CPU pe.Timestamp = h.Timestamp } type ExitProcEvent struct { ProcessPid uint32 ProcessTgid uint32 ExitCode uint32 ExitSignal uint32 ParentPid uint32 ParentTgid uint32 } func (e *ExitProcEvent) Pid() uint32 { return e.ProcessPid } func (e *ExitProcEvent) Tgid() uint32 { return e.ProcessTgid } type ExecProcEvent struct { ProcessPid uint32 ProcessTgid uint32 } func (e *ExecProcEvent) Pid() uint32 { return e.ProcessPid } func (e *ExecProcEvent) Tgid() uint32 { return e.ProcessTgid } type ForkProcEvent struct { ParentPid uint32 ParentTgid uint32 ChildPid uint32 ChildTgid uint32 } func (e *ForkProcEvent) Pid() uint32 { return e.ParentPid } func (e *ForkProcEvent) Tgid() uint32 { return e.ParentTgid } type CommProcEvent struct { ProcessPid uint32 ProcessTgid uint32 Comm [16]byte } func (e *CommProcEvent) Pid() uint32 { return e.ProcessPid } func (e *CommProcEvent) Tgid() uint32 { return e.ProcessTgid } func ProcEventMonitor(ch chan<- ProcEvent, done <-chan struct{}, errorChan chan<- error) error { h, err := NewHandle() if err != nil { return err } defer h.Delete() s, err := nl.SubscribeAt(netns.None(), netns.None(), unix.NETLINK_CONNECTOR, CN_IDX_PROC) if err != nil { return err } var nlmsg nl.NetlinkRequest nlmsg.Pid = uint32(os.Getpid()) nlmsg.Type = unix.NLMSG_DONE nlmsg.Len = uint32(unix.SizeofNlMsghdr) cm := nl.NewCnMsg(CN_IDX_PROC, CN_VAL_PROC, PROC_CN_MCAST_LISTEN) nlmsg.AddData(cm) s.Send(&nlmsg) if done != nil { go func() { <-done s.Close() }() } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { errorChan <- err return } if from.Pid != nl.PidKernel { errorChan <- fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) return } for _, m := range msgs { e := parseNetlinkMessage(m) if e != nil { ch <- *e } } } }() return nil } func parseNetlinkMessage(m syscall.NetlinkMessage) *ProcEvent { if m.Header.Type == unix.NLMSG_DONE { buf := bytes.NewBuffer(m.Data) msg := &nl.CnMsg{} hdr := &ProcEventHeader{} binary.Read(buf, nl.NativeEndian(), msg) binary.Read(buf, nl.NativeEndian(), hdr) pe := &ProcEvent{} pe.setHeader(*hdr) switch hdr.What { case PROC_EVENT_EXIT: event := &ExitProcEvent{} binary.Read(buf, nl.NativeEndian(), event) pe.Msg = event return pe case PROC_EVENT_FORK: event := &ForkProcEvent{} binary.Read(buf, nl.NativeEndian(), event) pe.Msg = event return pe case PROC_EVENT_EXEC: event := &ExecProcEvent{} binary.Read(buf, nl.NativeEndian(), event) pe.Msg = event return pe case PROC_EVENT_COMM: event := &CommProcEvent{} binary.Read(buf, nl.NativeEndian(), event) pe.Msg = event return pe } return nil } return nil } netlink-1.3.0/proc_event_test.go000066400000000000000000000025551466216277000167330ustar00rootroot00000000000000// +build linux package netlink import ( "github.com/vishvananda/netns" "os" "os/exec" "runtime" "testing" ) func TestSubscribeProcEvent(t *testing.T) { skipUnlessRoot(t) runtime.LockOSThread() defer runtime.UnlockOSThread() pid1ns, err := netns.GetFromPid(1) if err != nil { panic(err) } err = netns.Set(pid1ns) if err != nil { panic(err) } ch := make(chan ProcEvent) done := make(chan struct{}) defer close(done) errChan := make(chan error) if err := ProcEventMonitor(ch, done, errChan); err != nil { t.Fatal(err) } cmd := exec.Command("false") if err := cmd.Start(); err != nil { t.Fatal(err) } // first we wait for proc - i.e. childTgid is cmd.Process.Pid for { e := <-ch t.Logf("pid: %+v e: %+v", os.Getpid(), e) if e.Msg.Tgid() == uint32(os.Getpid()) { if forkEvent, ok := e.Msg.(*ForkProcEvent); ok { if forkEvent.ChildTgid == uint32(cmd.Process.Pid) { break } } } } // wait for exec event for { e := <-ch if e.Msg.Tgid() == uint32(cmd.Process.Pid) { if _, ok := e.Msg.(*ExecProcEvent); ok { break } } } cmd.Wait() for { e := <-ch if e.Msg.Tgid() == uint32(cmd.Process.Pid) { if exitEvent, ok := e.Msg.(*ExitProcEvent); ok { if exitEvent.ExitCode != 256 { t.Errorf("Expected error code 256 (-1), but got %+v", exitEvent) } break } } } done <- struct{}{} } netlink-1.3.0/protinfo.go000066400000000000000000000025221466216277000153620ustar00rootroot00000000000000package netlink import ( "strings" ) // Protinfo represents bridge flags from netlink. type Protinfo struct { Hairpin bool Guard bool FastLeave bool RootBlock bool Learning bool Flood bool ProxyArp bool ProxyArpWiFi bool Isolated bool NeighSuppress bool } // String returns a list of enabled flags func (prot *Protinfo) String() string { if prot == nil { return "" } var boolStrings []string if prot.Hairpin { boolStrings = append(boolStrings, "Hairpin") } if prot.Guard { boolStrings = append(boolStrings, "Guard") } if prot.FastLeave { boolStrings = append(boolStrings, "FastLeave") } if prot.RootBlock { boolStrings = append(boolStrings, "RootBlock") } if prot.Learning { boolStrings = append(boolStrings, "Learning") } if prot.Flood { boolStrings = append(boolStrings, "Flood") } if prot.ProxyArp { boolStrings = append(boolStrings, "ProxyArp") } if prot.ProxyArpWiFi { boolStrings = append(boolStrings, "ProxyArpWiFi") } if prot.Isolated { boolStrings = append(boolStrings, "Isolated") } if prot.NeighSuppress { boolStrings = append(boolStrings, "NeighSuppress") } return strings.Join(boolStrings, " ") } func boolToByte(x bool) []byte { if x { return []byte{1} } return []byte{0} } func byteToBool(x byte) bool { return uint8(x) != 0 } netlink-1.3.0/protinfo_linux.go000066400000000000000000000036671466216277000166140ustar00rootroot00000000000000package netlink import ( "fmt" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) func LinkGetProtinfo(link Link) (Protinfo, error) { return pkgHandle.LinkGetProtinfo(link) } func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { base := link.Attrs() h.ensureIndex(base) var pi Protinfo req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) if err != nil { return pi, err } for _, m := range msgs { ans := nl.DeserializeIfInfomsg(m) if int(ans.Index) != base.Index { continue } attrs, err := nl.ParseRouteAttr(m[ans.Len():]) if err != nil { return pi, err } for _, attr := range attrs { if attr.Attr.Type != unix.IFLA_PROTINFO|unix.NLA_F_NESTED { continue } infos, err := nl.ParseRouteAttr(attr.Value) if err != nil { return pi, err } pi = parseProtinfo(infos) return pi, nil } } return pi, fmt.Errorf("Device with index %d not found", base.Index) } func parseProtinfo(infos []syscall.NetlinkRouteAttr) (pi Protinfo) { for _, info := range infos { switch info.Attr.Type { case nl.IFLA_BRPORT_MODE: pi.Hairpin = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_GUARD: pi.Guard = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_FAST_LEAVE: pi.FastLeave = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_PROTECT: pi.RootBlock = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_LEARNING: pi.Learning = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_UNICAST_FLOOD: pi.Flood = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_PROXYARP: pi.ProxyArp = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_PROXYARP_WIFI: pi.ProxyArpWiFi = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_ISOLATED: pi.Isolated = byteToBool(info.Value[0]) case nl.IFLA_BRPORT_NEIGH_SUPPRESS: pi.NeighSuppress = byteToBool(info.Value[0]) } } return } netlink-1.3.0/protinfo_test.go000066400000000000000000000127441466216277000164300ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "testing" ) func TestProtinfo(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() master := &Bridge{LinkAttrs: LinkAttrs{Name: "foo"}} if err := LinkAdd(master); err != nil { t.Fatal(err) } iface1 := &Dummy{LinkAttrs{Name: "bar1", MasterIndex: master.Index}} iface2 := &Dummy{LinkAttrs{Name: "bar2", MasterIndex: master.Index}} iface3 := &Dummy{LinkAttrs{Name: "bar3"}} iface4 := &Dummy{LinkAttrs{Name: "bar4", MasterIndex: master.Index}} if err := LinkAdd(iface1); err != nil { t.Fatal(err) } if err := LinkAdd(iface2); err != nil { t.Fatal(err) } if err := LinkAdd(iface3); err != nil { t.Fatal(err) } if err := LinkAdd(iface4); err != nil { t.Fatal(err) } oldpi1, err := LinkGetProtinfo(iface1) if err != nil { t.Fatal(err) } oldpi2, err := LinkGetProtinfo(iface2) if err != nil { t.Fatal(err) } oldpi4, err := LinkGetProtinfo(iface4) if err != nil { t.Fatal(err) } if err := LinkSetHairpin(iface1, true); err != nil { t.Fatal(err) } if err := LinkSetRootBlock(iface1, true); err != nil { t.Fatal(err) } pi1, err := LinkGetProtinfo(iface1) if err != nil { t.Fatal(err) } if !pi1.Hairpin { t.Fatalf("Hairpin mode is not enabled for %s, but should", iface1.Name) } if !pi1.RootBlock { t.Fatalf("RootBlock is not enabled for %s, but should", iface1.Name) } if pi1.Isolated { t.Fatalf("Isolated mode is enabled for %s, but shouldn't", iface1.Name) } if pi1.ProxyArp != oldpi1.ProxyArp { t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface1.Name) } if pi1.ProxyArpWiFi != oldpi1.ProxyArp { t.Fatalf("ProxyArpWiFi ProxyArp field was changed for %s but shouldn't", iface1.Name) } if pi1.Guard != oldpi1.Guard { t.Fatalf("Guard field was changed for %s but shouldn't", iface1.Name) } if pi1.FastLeave != oldpi1.FastLeave { t.Fatalf("FastLeave field was changed for %s but shouldn't", iface1.Name) } if pi1.Learning != oldpi1.Learning { t.Fatalf("Learning field was changed for %s but shouldn't", iface1.Name) } if pi1.Flood != oldpi1.Flood { t.Fatalf("Flood field was changed for %s but shouldn't", iface1.Name) } if pi1.NeighSuppress != oldpi1.NeighSuppress { t.Fatalf("NeighSuppress field was changed for %s but shouldn't", iface1.Name) } if err := LinkSetGuard(iface2, true); err != nil { t.Fatal(err) } if err := LinkSetLearning(iface2, false); err != nil { t.Fatal(err) } pi2, err := LinkGetProtinfo(iface2) if err != nil { t.Fatal(err) } if pi2.Hairpin { t.Fatalf("Hairpin mode is enabled for %s, but shouldn't", iface2.Name) } if !pi2.Guard { t.Fatalf("Guard is not enabled for %s, but should", iface2.Name) } if pi2.ProxyArp != oldpi2.ProxyArp { t.Fatalf("ProxyArp field was changed for %s but shouldn't", iface2.Name) } if pi2.ProxyArpWiFi != oldpi2.ProxyArpWiFi { t.Fatalf("ProxyArpWiFi field was changed for %s but shouldn't", iface2.Name) } if pi2.Learning { t.Fatalf("Learning is enabled for %s, but shouldn't", iface2.Name) } if pi2.RootBlock != oldpi2.RootBlock { t.Fatalf("RootBlock field was changed for %s but shouldn't", iface2.Name) } if pi2.FastLeave != oldpi2.FastLeave { t.Fatalf("FastLeave field was changed for %s but shouldn't", iface2.Name) } if pi2.Flood != oldpi2.Flood { t.Fatalf("Flood field was changed for %s but shouldn't", iface2.Name) } if pi2.NeighSuppress != oldpi2.NeighSuppress { t.Fatalf("NeighSuppress field was changed for %s but shouldn't", iface2.Name) } if err := LinkSetHairpin(iface3, true); err == nil || err.Error() != "operation not supported" { t.Fatalf("Set protinfo attrs for link without master is not supported, but err: %s", err) } // Setting kernel requirement for next tests which require BR_PROXYARP minKernelRequired(t, 3, 19) if err := LinkSetBrProxyArp(iface4, true); err != nil { t.Fatal(err) } if err := LinkSetBrProxyArpWiFi(iface4, true); err != nil { t.Fatal(err) } pi4, err := LinkGetProtinfo(iface4) if err != nil { t.Fatal(err) } if pi4.Hairpin != oldpi4.Hairpin { t.Fatalf("Hairpin field was changed for %s but shouldn't", iface4.Name) } if pi4.Guard != oldpi4.Guard { t.Fatalf("Guard field was changed for %s but shouldn't", iface4.Name) } if pi4.Learning != oldpi4.Learning { t.Fatalf("Learning field was changed for %s but shouldn't", iface4.Name) } if !pi4.ProxyArp { t.Fatalf("ProxyArp is not enabled for %s, but should", iface4.Name) } if !pi4.ProxyArpWiFi { t.Fatalf("ProxyArpWiFi is not enabled for %s, but should", iface4.Name) } if pi4.RootBlock != oldpi4.RootBlock { t.Fatalf("RootBlock field was changed for %s but shouldn't", iface4.Name) } if pi4.FastLeave != oldpi4.FastLeave { t.Fatalf("FastLeave field was changed for %s but shouldn't", iface4.Name) } if pi4.Flood != oldpi4.Flood { t.Fatalf("Flood field was changed for %s but shouldn't", iface4.Name) } // BR_NEIGH_SUPPRESS added on 4.15 minKernelRequired(t, 4, 15) if err := LinkSetBrNeighSuppress(iface1, true); err != nil { t.Fatal(err) } pi1, err = LinkGetProtinfo(iface1) if err != nil { t.Fatal(err) } if !pi1.NeighSuppress { t.Fatalf("NeighSuppress is not enabled for %s but should", iface1.Name) } // Setting kernel requirement for next tests which require BRPORT_ISOLATED minKernelRequired(t, 4, 18) if err := LinkSetIsolated(iface1, true); err != nil { t.Fatal(err) } pi1, err = LinkGetProtinfo(iface1) if err != nil { t.Fatal(err) } if !pi1.Isolated { t.Fatalf("Isolated mode is not enabled for %s, but should", iface1.Name) } } netlink-1.3.0/qdisc.go000066400000000000000000000205601466216277000146270ustar00rootroot00000000000000package netlink import ( "fmt" "math" ) const ( HANDLE_NONE = 0 HANDLE_INGRESS = 0xFFFFFFF1 HANDLE_CLSACT = HANDLE_INGRESS HANDLE_ROOT = 0xFFFFFFFF PRIORITY_MAP_LEN = 16 ) const ( HANDLE_MIN_INGRESS = 0xFFFFFFF2 HANDLE_MIN_EGRESS = 0xFFFFFFF3 ) const ( HORIZON_DROP_POLICY_CAP = 0 HORIZON_DROP_POLICY_DROP = 1 HORIZON_DROP_POLICY_DEFAULT = 255 ) type Qdisc interface { Attrs() *QdiscAttrs Type() string } type QdiscStatistics ClassStatistics // QdiscAttrs represents a netlink qdisc. A qdisc is associated with a link, // has a handle, a parent and a refcnt. The root qdisc of a device should // have parent == HANDLE_ROOT. type QdiscAttrs struct { LinkIndex int Handle uint32 Parent uint32 Refcnt uint32 // read only IngressBlock *uint32 Statistics *QdiscStatistics } func (q QdiscAttrs) String() string { return fmt.Sprintf("{LinkIndex: %d, Handle: %s, Parent: %s, Refcnt: %d}", q.LinkIndex, HandleStr(q.Handle), HandleStr(q.Parent), q.Refcnt) } func MakeHandle(major, minor uint16) uint32 { return (uint32(major) << 16) | uint32(minor) } func MajorMinor(handle uint32) (uint16, uint16) { return uint16((handle & 0xFFFF0000) >> 16), uint16(handle & 0x0000FFFFF) } func HandleStr(handle uint32) string { switch handle { case HANDLE_NONE: return "none" case HANDLE_INGRESS: return "ingress" case HANDLE_ROOT: return "root" default: major, minor := MajorMinor(handle) return fmt.Sprintf("%x:%x", major, minor) } } func Percentage2u32(percentage float32) uint32 { // FIXME this is most likely not the best way to convert from % to uint32 if percentage == 100 { return math.MaxUint32 } return uint32(math.MaxUint32 * (percentage / 100)) } // PfifoFast is the default qdisc created by the kernel if one has not // been defined for the interface type PfifoFast struct { QdiscAttrs Bands uint8 PriorityMap [PRIORITY_MAP_LEN]uint8 } func (qdisc *PfifoFast) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *PfifoFast) Type() string { return "pfifo_fast" } // Prio is a basic qdisc that works just like PfifoFast type Prio struct { QdiscAttrs Bands uint8 PriorityMap [PRIORITY_MAP_LEN]uint8 } func NewPrio(attrs QdiscAttrs) *Prio { return &Prio{ QdiscAttrs: attrs, Bands: 3, PriorityMap: [PRIORITY_MAP_LEN]uint8{1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, } } func (qdisc *Prio) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Prio) Type() string { return "prio" } // Htb is a classful qdisc that rate limits based on tokens type Htb struct { QdiscAttrs Version uint32 Rate2Quantum uint32 Defcls uint32 Debug uint32 DirectPkts uint32 DirectQlen *uint32 } func NewHtb(attrs QdiscAttrs) *Htb { return &Htb{ QdiscAttrs: attrs, Version: 3, Defcls: 0, Rate2Quantum: 10, Debug: 0, DirectPkts: 0, DirectQlen: nil, } } func (qdisc *Htb) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Htb) Type() string { return "htb" } // Netem is a classless qdisc that rate limits based on tokens type NetemQdiscAttrs struct { Latency uint32 // in us DelayCorr float32 // in % Limit uint32 Loss float32 // in % LossCorr float32 // in % Gap uint32 Duplicate float32 // in % DuplicateCorr float32 // in % Jitter uint32 // in us ReorderProb float32 // in % ReorderCorr float32 // in % CorruptProb float32 // in % CorruptCorr float32 // in % Rate64 uint64 } func (q NetemQdiscAttrs) String() string { return fmt.Sprintf( "{Latency: %d, Limit: %d, Loss: %f, Gap: %d, Duplicate: %f, Jitter: %d}", q.Latency, q.Limit, q.Loss, q.Gap, q.Duplicate, q.Jitter, ) } type Netem struct { QdiscAttrs Latency uint32 DelayCorr uint32 Limit uint32 Loss uint32 LossCorr uint32 Gap uint32 Duplicate uint32 DuplicateCorr uint32 Jitter uint32 ReorderProb uint32 ReorderCorr uint32 CorruptProb uint32 CorruptCorr uint32 Rate64 uint64 } func (netem *Netem) String() string { return fmt.Sprintf( "{Latency: %v, Limit: %v, Loss: %v, Gap: %v, Duplicate: %v, Jitter: %v}", netem.Latency, netem.Limit, netem.Loss, netem.Gap, netem.Duplicate, netem.Jitter, ) } func (qdisc *Netem) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Netem) Type() string { return "netem" } // Tbf is a classless qdisc that rate limits based on tokens type Tbf struct { QdiscAttrs Rate uint64 Limit uint32 Buffer uint32 Peakrate uint64 Minburst uint32 // TODO: handle other settings } func (qdisc *Tbf) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Tbf) Type() string { return "tbf" } // Clsact is a qdisc for adding filters type Clsact struct { QdiscAttrs } func (qdisc *Clsact) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Clsact) Type() string { return "clsact" } // Ingress is a qdisc for adding ingress filters type Ingress struct { QdiscAttrs } func (qdisc *Ingress) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Ingress) Type() string { return "ingress" } // GenericQdisc qdiscs represent types that are not currently understood // by this netlink library. type GenericQdisc struct { QdiscAttrs QdiscType string } func (qdisc *GenericQdisc) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *GenericQdisc) Type() string { return qdisc.QdiscType } type Hfsc struct { QdiscAttrs Defcls uint16 } func NewHfsc(attrs QdiscAttrs) *Hfsc { return &Hfsc{ QdiscAttrs: attrs, Defcls: 1, } } func (hfsc *Hfsc) Attrs() *QdiscAttrs { return &hfsc.QdiscAttrs } func (hfsc *Hfsc) Type() string { return "hfsc" } func (hfsc *Hfsc) String() string { return fmt.Sprintf( "{%v -- default: %d}", hfsc.Attrs(), hfsc.Defcls, ) } // Fq is a classless packet scheduler meant to be mostly used for locally generated traffic. type Fq struct { QdiscAttrs PacketLimit uint32 FlowPacketLimit uint32 // In bytes Quantum uint32 InitialQuantum uint32 // called RateEnable under the hood Pacing uint32 FlowDefaultRate uint32 FlowMaxRate uint32 // called BucketsLog under the hood Buckets uint32 FlowRefillDelay uint32 LowRateThreshold uint32 Horizon uint32 HorizonDropPolicy uint8 } func (fq *Fq) String() string { return fmt.Sprintf( "{PacketLimit: %v, FlowPacketLimit: %v, Quantum: %v, InitialQuantum: %v, Pacing: %v, FlowDefaultRate: %v, FlowMaxRate: %v, Buckets: %v, FlowRefillDelay: %v, LowRateThreshold: %v, Horizon: %v, HorizonDropPolicy: %v}", fq.PacketLimit, fq.FlowPacketLimit, fq.Quantum, fq.InitialQuantum, fq.Pacing, fq.FlowDefaultRate, fq.FlowMaxRate, fq.Buckets, fq.FlowRefillDelay, fq.LowRateThreshold, fq.Horizon, fq.HorizonDropPolicy, ) } func NewFq(attrs QdiscAttrs) *Fq { return &Fq{ QdiscAttrs: attrs, Pacing: 1, HorizonDropPolicy: HORIZON_DROP_POLICY_DEFAULT, } } func (qdisc *Fq) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Fq) Type() string { return "fq" } // FQ_Codel (Fair Queuing Controlled Delay) is queuing discipline that combines Fair Queuing with the CoDel AQM scheme. type FqCodel struct { QdiscAttrs Target uint32 Limit uint32 Interval uint32 ECN uint32 Flows uint32 Quantum uint32 CEThreshold uint32 DropBatchSize uint32 MemoryLimit uint32 } func (fqcodel *FqCodel) String() string { return fmt.Sprintf( "{%v -- Target: %v, Limit: %v, Interval: %v, ECM: %v, Flows: %v, Quantum: %v}", fqcodel.Attrs(), fqcodel.Target, fqcodel.Limit, fqcodel.Interval, fqcodel.ECN, fqcodel.Flows, fqcodel.Quantum, ) } func NewFqCodel(attrs QdiscAttrs) *FqCodel { return &FqCodel{ QdiscAttrs: attrs, ECN: 1, } } func (qdisc *FqCodel) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *FqCodel) Type() string { return "fq_codel" } type Sfq struct { QdiscAttrs // TODO: Only the simplified options for SFQ are handled here. Support for the extended one can be added later. Quantum uint8 Perturb uint8 Limit uint32 Divisor uint8 } func (sfq *Sfq) String() string { return fmt.Sprintf( "{%v -- Quantum: %v, Perturb: %v, Limit: %v, Divisor: %v}", sfq.Attrs(), sfq.Quantum, sfq.Perturb, sfq.Limit, sfq.Divisor, ) } func (qdisc *Sfq) Attrs() *QdiscAttrs { return &qdisc.QdiscAttrs } func (qdisc *Sfq) Type() string { return "sfq" } netlink-1.3.0/qdisc_linux.go000066400000000000000000000515751466216277000160600ustar00rootroot00000000000000package netlink import ( "fmt" "io/ioutil" "strconv" "strings" "sync" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // NOTE function is here because it uses other linux functions func NewNetem(attrs QdiscAttrs, nattrs NetemQdiscAttrs) *Netem { var limit uint32 = 1000 var lossCorr, delayCorr, duplicateCorr uint32 var reorderProb, reorderCorr uint32 var corruptProb, corruptCorr uint32 var rate64 uint64 latency := nattrs.Latency loss := Percentage2u32(nattrs.Loss) gap := nattrs.Gap duplicate := Percentage2u32(nattrs.Duplicate) jitter := nattrs.Jitter // Correlation if latency > 0 && jitter > 0 { delayCorr = Percentage2u32(nattrs.DelayCorr) } if loss > 0 { lossCorr = Percentage2u32(nattrs.LossCorr) } if duplicate > 0 { duplicateCorr = Percentage2u32(nattrs.DuplicateCorr) } // FIXME should validate values(like loss/duplicate are percentages...) latency = time2Tick(latency) if nattrs.Limit != 0 { limit = nattrs.Limit } // Jitter is only value if latency is > 0 if latency > 0 { jitter = time2Tick(jitter) } reorderProb = Percentage2u32(nattrs.ReorderProb) reorderCorr = Percentage2u32(nattrs.ReorderCorr) if reorderProb > 0 { // ERROR if lantency == 0 if gap == 0 { gap = 1 } } corruptProb = Percentage2u32(nattrs.CorruptProb) corruptCorr = Percentage2u32(nattrs.CorruptCorr) rate64 = nattrs.Rate64 return &Netem{ QdiscAttrs: attrs, Latency: latency, DelayCorr: delayCorr, Limit: limit, Loss: loss, LossCorr: lossCorr, Gap: gap, Duplicate: duplicate, DuplicateCorr: duplicateCorr, Jitter: jitter, ReorderProb: reorderProb, ReorderCorr: reorderCorr, CorruptProb: corruptProb, CorruptCorr: corruptCorr, Rate64: rate64, } } // QdiscDel will delete a qdisc from the system. // Equivalent to: `tc qdisc del $qdisc` func QdiscDel(qdisc Qdisc) error { return pkgHandle.QdiscDel(qdisc) } // QdiscDel will delete a qdisc from the system. // Equivalent to: `tc qdisc del $qdisc` func (h *Handle) QdiscDel(qdisc Qdisc) error { return h.qdiscModify(unix.RTM_DELQDISC, 0, qdisc) } // QdiscChange will change a qdisc in place // Equivalent to: `tc qdisc change $qdisc` // The parent and handle MUST NOT be changed. func QdiscChange(qdisc Qdisc) error { return pkgHandle.QdiscChange(qdisc) } // QdiscChange will change a qdisc in place // Equivalent to: `tc qdisc change $qdisc` // The parent and handle MUST NOT be changed. func (h *Handle) QdiscChange(qdisc Qdisc) error { return h.qdiscModify(unix.RTM_NEWQDISC, 0, qdisc) } // QdiscReplace will replace a qdisc to the system. // Equivalent to: `tc qdisc replace $qdisc` // The handle MUST change. func QdiscReplace(qdisc Qdisc) error { return pkgHandle.QdiscReplace(qdisc) } // QdiscReplace will replace a qdisc to the system. // Equivalent to: `tc qdisc replace $qdisc` // The handle MUST change. func (h *Handle) QdiscReplace(qdisc Qdisc) error { return h.qdiscModify( unix.RTM_NEWQDISC, unix.NLM_F_CREATE|unix.NLM_F_REPLACE, qdisc) } // QdiscAdd will add a qdisc to the system. // Equivalent to: `tc qdisc add $qdisc` func QdiscAdd(qdisc Qdisc) error { return pkgHandle.QdiscAdd(qdisc) } // QdiscAdd will add a qdisc to the system. // Equivalent to: `tc qdisc add $qdisc` func (h *Handle) QdiscAdd(qdisc Qdisc) error { return h.qdiscModify( unix.RTM_NEWQDISC, unix.NLM_F_CREATE|unix.NLM_F_EXCL, qdisc) } func (h *Handle) qdiscModify(cmd, flags int, qdisc Qdisc) error { req := h.newNetlinkRequest(cmd, flags|unix.NLM_F_ACK) base := qdisc.Attrs() msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: int32(base.LinkIndex), Handle: base.Handle, Parent: base.Parent, } req.AddData(msg) // When deleting don't bother building the rest of the netlink payload if cmd != unix.RTM_DELQDISC { if err := qdiscPayload(req, qdisc); err != nil { return err } } _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(qdisc.Type()))) if qdisc.Attrs().IngressBlock != nil { req.AddData(nl.NewRtAttr(nl.TCA_INGRESS_BLOCK, nl.Uint32Attr(*qdisc.Attrs().IngressBlock))) } options := nl.NewRtAttr(nl.TCA_OPTIONS, nil) switch qdisc := qdisc.(type) { case *Prio: tcmap := nl.TcPrioMap{ Bands: int32(qdisc.Bands), Priomap: qdisc.PriorityMap, } options = nl.NewRtAttr(nl.TCA_OPTIONS, tcmap.Serialize()) case *Tbf: opt := nl.TcTbfQopt{} opt.Rate.Rate = uint32(qdisc.Rate) opt.Peakrate.Rate = uint32(qdisc.Peakrate) opt.Limit = qdisc.Limit opt.Buffer = qdisc.Buffer options.AddRtAttr(nl.TCA_TBF_PARMS, opt.Serialize()) if qdisc.Rate >= uint64(1<<32) { options.AddRtAttr(nl.TCA_TBF_RATE64, nl.Uint64Attr(qdisc.Rate)) } if qdisc.Peakrate >= uint64(1<<32) { options.AddRtAttr(nl.TCA_TBF_PRATE64, nl.Uint64Attr(qdisc.Peakrate)) } if qdisc.Peakrate > 0 { options.AddRtAttr(nl.TCA_TBF_PBURST, nl.Uint32Attr(qdisc.Minburst)) } case *Htb: opt := nl.TcHtbGlob{} opt.Version = qdisc.Version opt.Rate2Quantum = qdisc.Rate2Quantum opt.Defcls = qdisc.Defcls // TODO: Handle Debug properly. For now default to 0 opt.Debug = qdisc.Debug opt.DirectPkts = qdisc.DirectPkts options.AddRtAttr(nl.TCA_HTB_INIT, opt.Serialize()) if qdisc.DirectQlen != nil { options.AddRtAttr(nl.TCA_HTB_DIRECT_QLEN, nl.Uint32Attr(*qdisc.DirectQlen)) } case *Hfsc: opt := nl.TcHfscOpt{} opt.Defcls = qdisc.Defcls options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) case *Netem: opt := nl.TcNetemQopt{} opt.Latency = qdisc.Latency opt.Limit = qdisc.Limit opt.Loss = qdisc.Loss opt.Gap = qdisc.Gap opt.Duplicate = qdisc.Duplicate opt.Jitter = qdisc.Jitter options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) // Correlation corr := nl.TcNetemCorr{} corr.DelayCorr = qdisc.DelayCorr corr.LossCorr = qdisc.LossCorr corr.DupCorr = qdisc.DuplicateCorr if corr.DelayCorr > 0 || corr.LossCorr > 0 || corr.DupCorr > 0 { options.AddRtAttr(nl.TCA_NETEM_CORR, corr.Serialize()) } // Corruption corruption := nl.TcNetemCorrupt{} corruption.Probability = qdisc.CorruptProb corruption.Correlation = qdisc.CorruptCorr if corruption.Probability > 0 { options.AddRtAttr(nl.TCA_NETEM_CORRUPT, corruption.Serialize()) } // Reorder reorder := nl.TcNetemReorder{} reorder.Probability = qdisc.ReorderProb reorder.Correlation = qdisc.ReorderCorr if reorder.Probability > 0 { options.AddRtAttr(nl.TCA_NETEM_REORDER, reorder.Serialize()) } // Rate if qdisc.Rate64 > 0 { rate := nl.TcNetemRate{} if qdisc.Rate64 >= uint64(1<<32) { options.AddRtAttr(nl.TCA_NETEM_RATE64, nl.Uint64Attr(qdisc.Rate64)) rate.Rate = ^uint32(0) } else { rate.Rate = uint32(qdisc.Rate64) } options.AddRtAttr(nl.TCA_NETEM_RATE, rate.Serialize()) } case *Clsact: options = nil case *Ingress: // ingress filters must use the proper handle if qdisc.Attrs().Parent != HANDLE_INGRESS { return fmt.Errorf("Ingress filters must set Parent to HANDLE_INGRESS") } case *FqCodel: options.AddRtAttr(nl.TCA_FQ_CODEL_ECN, nl.Uint32Attr((uint32(qdisc.ECN)))) if qdisc.Limit > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_LIMIT, nl.Uint32Attr((uint32(qdisc.Limit)))) } if qdisc.Interval > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_INTERVAL, nl.Uint32Attr((uint32(qdisc.Interval)))) } if qdisc.Flows > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_FLOWS, nl.Uint32Attr((uint32(qdisc.Flows)))) } if qdisc.Quantum > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_QUANTUM, nl.Uint32Attr((uint32(qdisc.Quantum)))) } if qdisc.CEThreshold > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_CE_THRESHOLD, nl.Uint32Attr(qdisc.CEThreshold)) } if qdisc.DropBatchSize > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_DROP_BATCH_SIZE, nl.Uint32Attr(qdisc.DropBatchSize)) } if qdisc.MemoryLimit > 0 { options.AddRtAttr(nl.TCA_FQ_CODEL_MEMORY_LIMIT, nl.Uint32Attr(qdisc.MemoryLimit)) } case *Fq: options.AddRtAttr(nl.TCA_FQ_RATE_ENABLE, nl.Uint32Attr((uint32(qdisc.Pacing)))) if qdisc.Buckets > 0 { options.AddRtAttr(nl.TCA_FQ_BUCKETS_LOG, nl.Uint32Attr((uint32(qdisc.Buckets)))) } if qdisc.PacketLimit > 0 { options.AddRtAttr(nl.TCA_FQ_PLIMIT, nl.Uint32Attr((uint32(qdisc.PacketLimit)))) } if qdisc.LowRateThreshold > 0 { options.AddRtAttr(nl.TCA_FQ_LOW_RATE_THRESHOLD, nl.Uint32Attr((uint32(qdisc.LowRateThreshold)))) } if qdisc.Quantum > 0 { options.AddRtAttr(nl.TCA_FQ_QUANTUM, nl.Uint32Attr((uint32(qdisc.Quantum)))) } if qdisc.InitialQuantum > 0 { options.AddRtAttr(nl.TCA_FQ_INITIAL_QUANTUM, nl.Uint32Attr((uint32(qdisc.InitialQuantum)))) } if qdisc.FlowRefillDelay > 0 { options.AddRtAttr(nl.TCA_FQ_FLOW_REFILL_DELAY, nl.Uint32Attr((uint32(qdisc.FlowRefillDelay)))) } if qdisc.FlowPacketLimit > 0 { options.AddRtAttr(nl.TCA_FQ_FLOW_PLIMIT, nl.Uint32Attr((uint32(qdisc.FlowPacketLimit)))) } if qdisc.FlowMaxRate > 0 { options.AddRtAttr(nl.TCA_FQ_FLOW_MAX_RATE, nl.Uint32Attr((uint32(qdisc.FlowMaxRate)))) } if qdisc.FlowDefaultRate > 0 { options.AddRtAttr(nl.TCA_FQ_FLOW_DEFAULT_RATE, nl.Uint32Attr((uint32(qdisc.FlowDefaultRate)))) } if qdisc.Horizon > 0 { options.AddRtAttr(nl.TCA_FQ_HORIZON, nl.Uint32Attr(qdisc.Horizon)) } if qdisc.HorizonDropPolicy != HORIZON_DROP_POLICY_DEFAULT { options.AddRtAttr(nl.TCA_FQ_HORIZON_DROP, nl.Uint8Attr(qdisc.HorizonDropPolicy)) } case *Sfq: opt := nl.TcSfqQoptV1{} opt.TcSfqQopt.Quantum = qdisc.Quantum opt.TcSfqQopt.Perturb = int32(qdisc.Perturb) opt.TcSfqQopt.Limit = qdisc.Limit opt.TcSfqQopt.Divisor = qdisc.Divisor options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize()) default: options = nil } if options != nil { req.AddData(options) } return nil } // QdiscList gets a list of qdiscs in the system. // Equivalent to: `tc qdisc show`. // The list can be filtered by link. func QdiscList(link Link) ([]Qdisc, error) { return pkgHandle.QdiscList(link) } // QdiscList gets a list of qdiscs in the system. // Equivalent to: `tc qdisc show`. // The list can be filtered by link. func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { req := h.newNetlinkRequest(unix.RTM_GETQDISC, unix.NLM_F_DUMP) index := int32(0) if link != nil { base := link.Attrs() h.ensureIndex(base) index = int32(base.Index) } msg := &nl.TcMsg{ Family: nl.FAMILY_ALL, Ifindex: index, } req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC) if err != nil { return nil, err } var res []Qdisc for _, m := range msgs { msg := nl.DeserializeTcMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } // skip qdiscs from other interfaces if link != nil && msg.Ifindex != index { continue } base := QdiscAttrs{ LinkIndex: int(msg.Ifindex), Handle: msg.Handle, Parent: msg.Parent, Refcnt: msg.Info, } var qdisc Qdisc qdiscType := "" for _, attr := range attrs { switch attr.Attr.Type { case nl.TCA_KIND: qdiscType = string(attr.Value[:len(attr.Value)-1]) switch qdiscType { case "pfifo_fast": qdisc = &PfifoFast{} case "prio": qdisc = &Prio{} case "tbf": qdisc = &Tbf{} case "ingress": qdisc = &Ingress{} case "htb": qdisc = &Htb{} case "fq": qdisc = &Fq{} case "hfsc": qdisc = &Hfsc{} case "fq_codel": qdisc = &FqCodel{} case "netem": qdisc = &Netem{} case "sfq": qdisc = &Sfq{} case "clsact": qdisc = &Clsact{} default: qdisc = &GenericQdisc{QdiscType: qdiscType} } case nl.TCA_OPTIONS: switch qdiscType { case "pfifo_fast": // pfifo returns TcPrioMap directly without wrapping it in rtattr if err := parsePfifoFastData(qdisc, attr.Value); err != nil { return nil, err } case "prio": // prio returns TcPrioMap directly without wrapping it in rtattr if err := parsePrioData(qdisc, attr.Value); err != nil { return nil, err } case "tbf": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } if err := parseTbfData(qdisc, data); err != nil { return nil, err } case "hfsc": if err := parseHfscData(qdisc, attr.Value); err != nil { return nil, err } case "htb": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } if err := parseHtbData(qdisc, data); err != nil { return nil, err } case "fq": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } if err := parseFqData(qdisc, data); err != nil { return nil, err } case "fq_codel": data, err := nl.ParseRouteAttr(attr.Value) if err != nil { return nil, err } if err := parseFqCodelData(qdisc, data); err != nil { return nil, err } case "netem": if err := parseNetemData(qdisc, attr.Value); err != nil { return nil, err } case "sfq": if err := parseSfqData(qdisc, attr.Value); err != nil { return nil, err } // no options for ingress } case nl.TCA_INGRESS_BLOCK: ingressBlock := new(uint32) *ingressBlock = native.Uint32(attr.Value) base.IngressBlock = ingressBlock case nl.TCA_STATS: s, err := parseTcStats(attr.Value) if err != nil { return nil, err } base.Statistics = (*QdiscStatistics)(s) case nl.TCA_STATS2: s, err := parseTcStats2(attr.Value) if err != nil { return nil, err } base.Statistics = (*QdiscStatistics)(s) } } *qdisc.Attrs() = base res = append(res, qdisc) } return res, nil } func parsePfifoFastData(qdisc Qdisc, value []byte) error { pfifo := qdisc.(*PfifoFast) tcmap := nl.DeserializeTcPrioMap(value) pfifo.PriorityMap = tcmap.Priomap pfifo.Bands = uint8(tcmap.Bands) return nil } func parsePrioData(qdisc Qdisc, value []byte) error { prio := qdisc.(*Prio) tcmap := nl.DeserializeTcPrioMap(value) prio.PriorityMap = tcmap.Priomap prio.Bands = uint8(tcmap.Bands) return nil } func parseHtbData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { htb := qdisc.(*Htb) for _, datum := range data { switch datum.Attr.Type { case nl.TCA_HTB_INIT: opt := nl.DeserializeTcHtbGlob(datum.Value) htb.Version = opt.Version htb.Rate2Quantum = opt.Rate2Quantum htb.Defcls = opt.Defcls htb.Debug = opt.Debug htb.DirectPkts = opt.DirectPkts case nl.TCA_HTB_DIRECT_QLEN: directQlen := native.Uint32(datum.Value) htb.DirectQlen = &directQlen } } return nil } func parseFqCodelData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { fqCodel := qdisc.(*FqCodel) for _, datum := range data { switch datum.Attr.Type { case nl.TCA_FQ_CODEL_TARGET: fqCodel.Target = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_LIMIT: fqCodel.Limit = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_INTERVAL: fqCodel.Interval = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_ECN: fqCodel.ECN = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_FLOWS: fqCodel.Flows = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_QUANTUM: fqCodel.Quantum = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_CE_THRESHOLD: fqCodel.CEThreshold = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_DROP_BATCH_SIZE: fqCodel.DropBatchSize = native.Uint32(datum.Value) case nl.TCA_FQ_CODEL_MEMORY_LIMIT: fqCodel.MemoryLimit = native.Uint32(datum.Value) } } return nil } func parseHfscData(qdisc Qdisc, data []byte) error { Hfsc := qdisc.(*Hfsc) Hfsc.Defcls = native.Uint16(data) return nil } func parseFqData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { fq := qdisc.(*Fq) for _, datum := range data { switch datum.Attr.Type { case nl.TCA_FQ_BUCKETS_LOG: fq.Buckets = native.Uint32(datum.Value) case nl.TCA_FQ_LOW_RATE_THRESHOLD: fq.LowRateThreshold = native.Uint32(datum.Value) case nl.TCA_FQ_QUANTUM: fq.Quantum = native.Uint32(datum.Value) case nl.TCA_FQ_RATE_ENABLE: fq.Pacing = native.Uint32(datum.Value) case nl.TCA_FQ_INITIAL_QUANTUM: fq.InitialQuantum = native.Uint32(datum.Value) case nl.TCA_FQ_ORPHAN_MASK: // TODO case nl.TCA_FQ_FLOW_REFILL_DELAY: fq.FlowRefillDelay = native.Uint32(datum.Value) case nl.TCA_FQ_FLOW_PLIMIT: fq.FlowPacketLimit = native.Uint32(datum.Value) case nl.TCA_FQ_PLIMIT: fq.PacketLimit = native.Uint32(datum.Value) case nl.TCA_FQ_FLOW_MAX_RATE: fq.FlowMaxRate = native.Uint32(datum.Value) case nl.TCA_FQ_FLOW_DEFAULT_RATE: fq.FlowDefaultRate = native.Uint32(datum.Value) case nl.TCA_FQ_HORIZON: fq.Horizon = native.Uint32(datum.Value) case nl.TCA_FQ_HORIZON_DROP: fq.HorizonDropPolicy = datum.Value[0] } } return nil } func parseNetemData(qdisc Qdisc, value []byte) error { netem := qdisc.(*Netem) opt := nl.DeserializeTcNetemQopt(value) netem.Latency = opt.Latency netem.Limit = opt.Limit netem.Loss = opt.Loss netem.Gap = opt.Gap netem.Duplicate = opt.Duplicate netem.Jitter = opt.Jitter data, err := nl.ParseRouteAttr(value[nl.SizeofTcNetemQopt:]) if err != nil { return err } var rate *nl.TcNetemRate var rate64 uint64 for _, datum := range data { switch datum.Attr.Type { case nl.TCA_NETEM_CORR: opt := nl.DeserializeTcNetemCorr(datum.Value) netem.DelayCorr = opt.DelayCorr netem.LossCorr = opt.LossCorr netem.DuplicateCorr = opt.DupCorr case nl.TCA_NETEM_CORRUPT: opt := nl.DeserializeTcNetemCorrupt(datum.Value) netem.CorruptProb = opt.Probability netem.CorruptCorr = opt.Correlation case nl.TCA_NETEM_REORDER: opt := nl.DeserializeTcNetemReorder(datum.Value) netem.ReorderProb = opt.Probability netem.ReorderCorr = opt.Correlation case nl.TCA_NETEM_RATE: rate = nl.DeserializeTcNetemRate(datum.Value) case nl.TCA_NETEM_RATE64: rate64 = native.Uint64(datum.Value) } } if rate != nil { netem.Rate64 = uint64(rate.Rate) if rate64 > 0 { netem.Rate64 = rate64 } } return nil } func parseTbfData(qdisc Qdisc, data []syscall.NetlinkRouteAttr) error { tbf := qdisc.(*Tbf) for _, datum := range data { switch datum.Attr.Type { case nl.TCA_TBF_PARMS: opt := nl.DeserializeTcTbfQopt(datum.Value) tbf.Rate = uint64(opt.Rate.Rate) tbf.Peakrate = uint64(opt.Peakrate.Rate) tbf.Limit = opt.Limit tbf.Buffer = opt.Buffer case nl.TCA_TBF_RATE64: tbf.Rate = native.Uint64(datum.Value[0:8]) case nl.TCA_TBF_PRATE64: tbf.Peakrate = native.Uint64(datum.Value[0:8]) case nl.TCA_TBF_PBURST: tbf.Minburst = native.Uint32(datum.Value[0:4]) } } return nil } func parseSfqData(qdisc Qdisc, value []byte) error { sfq := qdisc.(*Sfq) opt := nl.DeserializeTcSfqQoptV1(value) sfq.Quantum = opt.TcSfqQopt.Quantum sfq.Perturb = uint8(opt.TcSfqQopt.Perturb) sfq.Limit = opt.TcSfqQopt.Limit sfq.Divisor = opt.TcSfqQopt.Divisor return nil } const ( TIME_UNITS_PER_SEC = 1000000 ) var ( tickInUsec float64 clockFactor float64 hz float64 // Without this, the go race detector may report races. initClockMutex sync.Mutex ) func initClock() { data, err := ioutil.ReadFile("/proc/net/psched") if err != nil { return } parts := strings.Split(strings.TrimSpace(string(data)), " ") if len(parts) < 4 { return } var vals [4]uint64 for i := range vals { val, err := strconv.ParseUint(parts[i], 16, 32) if err != nil { return } vals[i] = val } // compatibility if vals[2] == 1000000000 { vals[0] = vals[1] } clockFactor = float64(vals[2]) / TIME_UNITS_PER_SEC tickInUsec = float64(vals[0]) / float64(vals[1]) * clockFactor if vals[2] == 1000000 { // ref https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/utils.c#n963 hz = float64(vals[3]) } else { hz = 100 } } func TickInUsec() float64 { initClockMutex.Lock() defer initClockMutex.Unlock() if tickInUsec == 0.0 { initClock() } return tickInUsec } func ClockFactor() float64 { initClockMutex.Lock() defer initClockMutex.Unlock() if clockFactor == 0.0 { initClock() } return clockFactor } func Hz() float64 { initClockMutex.Lock() defer initClockMutex.Unlock() if hz == 0.0 { initClock() } return hz } func time2Tick(time uint32) uint32 { return uint32(float64(time) * TickInUsec()) } func tick2Time(tick uint32) uint32 { return uint32(float64(tick) / TickInUsec()) } func time2Ktime(time uint32) uint32 { return uint32(float64(time) * ClockFactor()) } func ktime2Time(ktime uint32) uint32 { return uint32(float64(ktime) / ClockFactor()) } func burst(rate uint64, buffer uint32) uint32 { return uint32(float64(rate) * float64(tick2Time(buffer)) / TIME_UNITS_PER_SEC) } func latency(rate uint64, limit, buffer uint32) float64 { return TIME_UNITS_PER_SEC*(float64(limit)/float64(rate)) - float64(tick2Time(buffer)) } func Xmittime(rate uint64, size uint32) uint32 { // https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/tc/tc_core.c#n62 return time2Tick(uint32(TIME_UNITS_PER_SEC * (float64(size) / float64(rate)))) } func Xmitsize(rate uint64, ticks uint32) uint32 { return uint32((float64(rate) * float64(tick2Time(ticks))) / TIME_UNITS_PER_SEC) } netlink-1.3.0/qdisc_test.go000066400000000000000000000311771466216277000156740ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "testing" ) func TestTbfAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := &Tbf{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, }, Rate: 131072, Limit: 1220703, Buffer: 16793, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } tbf, ok := qdiscs[0].(*Tbf) if !ok { t.Fatal("Qdisc is the wrong type") } if tbf.Rate != qdisc.Rate { t.Fatal("Rate doesn't match") } if tbf.Limit != qdisc.Limit { t.Fatal("Limit doesn't match") } if tbf.Buffer != qdisc.Buffer { t.Fatal("Buffer doesn't match") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestHtbAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } qdisc := NewHtb(attrs) qdisc.Rate2Quantum = 5 directQlen := uint32(10) qdisc.DirectQlen = &directQlen if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } htb, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } if htb.Defcls != qdisc.Defcls { t.Fatal("Defcls doesn't match") } if htb.Rate2Quantum != qdisc.Rate2Quantum { t.Fatal("Rate2Quantum doesn't match") } if htb.Debug != qdisc.Debug { t.Fatal("Debug doesn't match") } if htb.DirectQlen == nil || *htb.DirectQlen != directQlen { t.Fatalf("DirectQlen doesn't match. Expected %d, got %v", directQlen, htb.DirectQlen) } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestSfqAddDel(t *testing.T) { tearDown := setUpNetlinkTestWithKModule(t, "sch_sfq") defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } qdisc := Sfq{ QdiscAttrs: attrs, Quantum: 2, Perturb: 11, Limit: 123, Divisor: 4, } if err := QdiscAdd(&qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } sfq, ok := qdiscs[0].(*Sfq) if !ok { t.Fatal("Qdisc is the wrong type") } if sfq.Quantum != qdisc.Quantum { t.Fatal("Quantum doesn't match") } if sfq.Perturb != qdisc.Perturb { t.Fatal("Perturb doesn't match") } if sfq.Limit != qdisc.Limit { t.Fatal("Limit doesn't match") } if sfq.Divisor != qdisc.Divisor { t.Fatal("Divisor doesn't match") } if err := QdiscDel(&qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestPrioAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := NewPrio(QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, }) if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } _, ok := qdiscs[0].(*Prio) if !ok { t.Fatal("Qdisc is the wrong type") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestTbfAddHtbReplaceDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } // Add attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } qdisc := &Tbf{ QdiscAttrs: attrs, Rate: 131072, Limit: 1220703, Buffer: 16793, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } tbf, ok := qdiscs[0].(*Tbf) if !ok { t.Fatal("Qdisc is the wrong type") } if tbf.Rate != qdisc.Rate { t.Fatal("Rate doesn't match") } if tbf.Limit != qdisc.Limit { t.Fatal("Limit doesn't match") } if tbf.Buffer != qdisc.Buffer { t.Fatal("Buffer doesn't match") } // Replace // For replace to work, the handle MUST be different that the running one attrs.Handle = MakeHandle(2, 0) qdisc2 := NewHtb(attrs) qdisc2.Rate2Quantum = 5 if err := QdiscReplace(qdisc2); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } htb, ok := qdiscs[0].(*Htb) if !ok { t.Fatal("Qdisc is the wrong type") } if htb.Defcls != qdisc2.Defcls { t.Fatal("Defcls doesn't match") } if htb.Rate2Quantum != qdisc2.Rate2Quantum { t.Fatal("Rate2Quantum doesn't match") } if htb.Debug != qdisc2.Debug { t.Fatal("Debug doesn't match") } if err := QdiscDel(qdisc2); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestTbfAddTbfChangeDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } // Add attrs := QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, } qdisc := &Tbf{ QdiscAttrs: attrs, Rate: 131072, Limit: 1220703, Buffer: 16793, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } tbf, ok := qdiscs[0].(*Tbf) if !ok { t.Fatal("Qdisc is the wrong type") } if tbf.Rate != qdisc.Rate { t.Fatal("Rate doesn't match") } if tbf.Limit != qdisc.Limit { t.Fatal("Limit doesn't match") } if tbf.Buffer != qdisc.Buffer { t.Fatal("Buffer doesn't match") } // Change // For change to work, the handle MUST not change qdisc.Rate = 23456 if err := QdiscChange(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } tbf, ok = qdiscs[0].(*Tbf) if !ok { t.Fatal("Qdisc is the wrong type") } if tbf.Rate != qdisc.Rate { t.Fatal("Rate doesn't match") } if tbf.Limit != qdisc.Limit { t.Fatal("Limit doesn't match") } if tbf.Buffer != qdisc.Buffer { t.Fatal("Buffer doesn't match") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestFqAddChangeDel(t *testing.T) { minKernelRequired(t, 3, 11) tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := &Fq{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, }, FlowPacketLimit: 123, Pacing: 0, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } fq, ok := qdiscs[0].(*Fq) if !ok { t.Fatal("Qdisc is the wrong type") } if fq.FlowPacketLimit != qdisc.FlowPacketLimit { t.Fatal("Flow Packet Limit does not match") } if fq.Pacing != qdisc.Pacing { t.Fatal("Pacing does not match") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestFqHorizon(t *testing.T) { minKernelRequired(t, 5, 7) tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := &Fq{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, }, Horizon: 1000, HorizonDropPolicy: HORIZON_DROP_POLICY_CAP, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } fq, ok := qdiscs[0].(*Fq) if !ok { t.Fatal("Qdisc is the wrong type") } if fq.Horizon != qdisc.Horizon { t.Fatal("Horizon does not match") } if fq.HorizonDropPolicy != qdisc.HorizonDropPolicy { t.Fatal("HorizonDropPolicy does not match") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestFqCodelAddChangeDel(t *testing.T) { minKernelRequired(t, 3, 4) tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } qdisc := &FqCodel{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Handle: MakeHandle(1, 0), Parent: HANDLE_ROOT, }, ECN: 1, Quantum: 9000, } if err := QdiscAdd(qdisc); err != nil { t.Fatal(err) } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } fqcodel, ok := qdiscs[0].(*FqCodel) if !ok { t.Fatal("Qdisc is the wrong type") } if fqcodel.Quantum != qdisc.Quantum { t.Fatal("Quantum does not match") } if err := QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } func TestIngressAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil { t.Fatal(err) } link, err := LinkByName("foo") if err != nil { t.Fatal(err) } ingressBlock := new(uint32) *ingressBlock = 8 qdisc := &Ingress{ QdiscAttrs: QdiscAttrs{ LinkIndex: link.Attrs().Index, Parent: HANDLE_INGRESS, IngressBlock: ingressBlock, }, } err = QdiscAdd(qdisc) if err != nil { t.Fatal("Failed to add qdisc") } qdiscs, err := SafeQdiscList(link) if err != nil { t.Fatal("Failed to list qdisc") } if len(qdiscs) != 1 { t.Fatal("Failed to add qdisc") } if *qdiscs[0].Attrs().IngressBlock != *ingressBlock { t.Fatal("IngressBlock does not match") } if qdiscs[0].Attrs().Statistics == nil { t.Fatal("Statistics is nil") } if qdiscs[0].Attrs().Statistics.Basic.Bytes != 0 || qdiscs[0].Attrs().Statistics.Basic.Packets != 0 { t.Fatal("Statistics is not zero") } if err = QdiscDel(qdisc); err != nil { t.Fatal(err) } qdiscs, err = SafeQdiscList(link) if err != nil { t.Fatal(err) } if len(qdiscs) != 0 { t.Fatal("Failed to remove qdisc") } } netlink-1.3.0/rdma_link_linux.go000066400000000000000000000226561466216277000167130ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "fmt" "net" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // LinkAttrs represents data shared by most link types type RdmaLinkAttrs struct { Index uint32 Name string FirmwareVersion string NodeGuid string SysImageGuid string } // Link represents a rdma device from netlink. type RdmaLink struct { Attrs RdmaLinkAttrs } func getProtoField(clientType int, op int) int { return ((clientType << nl.RDMA_NL_GET_CLIENT_SHIFT) | op) } func uint64ToGuidString(guid uint64) string { //Convert to byte array sysGuidBytes := new(bytes.Buffer) binary.Write(sysGuidBytes, binary.LittleEndian, guid) //Convert to HardwareAddr sysGuidNet := net.HardwareAddr(sysGuidBytes.Bytes()) //Get the String return sysGuidNet.String() } func executeOneGetRdmaLink(data []byte) (*RdmaLink, error) { link := RdmaLink{} reader := bytes.NewReader(data) for reader.Len() >= 4 { _, attrType, len, value := parseNfAttrTLV(reader) switch attrType { case nl.RDMA_NLDEV_ATTR_DEV_INDEX: var Index uint32 r := bytes.NewReader(value) binary.Read(r, nl.NativeEndian(), &Index) link.Attrs.Index = Index case nl.RDMA_NLDEV_ATTR_DEV_NAME: link.Attrs.Name = string(value[0 : len-1]) case nl.RDMA_NLDEV_ATTR_FW_VERSION: link.Attrs.FirmwareVersion = string(value[0 : len-1]) case nl.RDMA_NLDEV_ATTR_NODE_GUID: var guid uint64 r := bytes.NewReader(value) binary.Read(r, nl.NativeEndian(), &guid) link.Attrs.NodeGuid = uint64ToGuidString(guid) case nl.RDMA_NLDEV_ATTR_SYS_IMAGE_GUID: var sysGuid uint64 r := bytes.NewReader(value) binary.Read(r, nl.NativeEndian(), &sysGuid) link.Attrs.SysImageGuid = uint64ToGuidString(sysGuid) } if (len % 4) != 0 { // Skip pad bytes reader.Seek(int64(4-(len%4)), seekCurrent) } } return &link, nil } func execRdmaSetLink(req *nl.NetlinkRequest) error { _, err := req.Execute(unix.NETLINK_RDMA, 0) return err } // RdmaLinkList gets a list of RDMA link devices. // Equivalent to: `rdma dev show` func RdmaLinkList() ([]*RdmaLink, error) { return pkgHandle.RdmaLinkList() } // RdmaLinkList gets a list of RDMA link devices. // Equivalent to: `rdma dev show` func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_GET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK|unix.NLM_F_DUMP) msgs, err := req.Execute(unix.NETLINK_RDMA, 0) if err != nil { return nil, err } var res []*RdmaLink for _, m := range msgs { link, err := executeOneGetRdmaLink(m) if err != nil { return nil, err } res = append(res, link) } return res, nil } // RdmaLinkByName finds a link by name and returns a pointer to the object if // found and nil error, otherwise returns error code. func RdmaLinkByName(name string) (*RdmaLink, error) { return pkgHandle.RdmaLinkByName(name) } // RdmaLinkByName finds a link by name and returns a pointer to the object if // found and nil error, otherwise returns error code. func (h *Handle) RdmaLinkByName(name string) (*RdmaLink, error) { links, err := h.RdmaLinkList() if err != nil { return nil, err } for _, link := range links { if link.Attrs.Name == name { return link, nil } } return nil, fmt.Errorf("Rdma device %v not found", name) } // RdmaLinkSetName sets the name of the rdma link device. Return nil on success // or error otherwise. // Equivalent to: `rdma dev set $old_devname name $name` func RdmaLinkSetName(link *RdmaLink, name string) error { return pkgHandle.RdmaLinkSetName(link, name) } // RdmaLinkSetName sets the name of the rdma link device. Return nil on success // or error otherwise. // Equivalent to: `rdma dev set $old_devname name $name` func (h *Handle) RdmaLinkSetName(link *RdmaLink, name string) error { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_SET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) b := make([]byte, 4) native.PutUint32(b, uint32(link.Attrs.Index)) data := nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_DEV_INDEX, b) req.AddData(data) b = make([]byte, len(name)+1) copy(b, name) data = nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_DEV_NAME, b) req.AddData(data) return execRdmaSetLink(req) } func netnsModeToString(mode uint8) string { switch mode { case 0: return "exclusive" case 1: return "shared" default: return "unknown" } } func executeOneGetRdmaNetnsMode(data []byte) (string, error) { reader := bytes.NewReader(data) for reader.Len() >= 4 { _, attrType, len, value := parseNfAttrTLV(reader) switch attrType { case nl.RDMA_NLDEV_SYS_ATTR_NETNS_MODE: var mode uint8 r := bytes.NewReader(value) binary.Read(r, nl.NativeEndian(), &mode) return netnsModeToString(mode), nil } if (len % 4) != 0 { // Skip pad bytes reader.Seek(int64(4-(len%4)), seekCurrent) } } return "", fmt.Errorf("Invalid netns mode") } // RdmaSystemGetNetnsMode gets the net namespace mode for RDMA subsystem // Returns mode string and error status as nil on success or returns error // otherwise. // Equivalent to: `rdma system show netns' func RdmaSystemGetNetnsMode() (string, error) { return pkgHandle.RdmaSystemGetNetnsMode() } // RdmaSystemGetNetnsMode gets the net namespace mode for RDMA subsystem // Returns mode string and error status as nil on success or returns error // otherwise. // Equivalent to: `rdma system show netns' func (h *Handle) RdmaSystemGetNetnsMode() (string, error) { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_SYS_GET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) msgs, err := req.Execute(unix.NETLINK_RDMA, 0) if err != nil { return "", err } if len(msgs) == 0 { return "", fmt.Errorf("No valid response from kernel") } return executeOneGetRdmaNetnsMode(msgs[0]) } func netnsModeStringToUint8(mode string) (uint8, error) { switch mode { case "exclusive": return 0, nil case "shared": return 1, nil default: return 0, fmt.Errorf("Invalid mode; %q", mode) } } // RdmaSystemSetNetnsMode sets the net namespace mode for RDMA subsystem // Returns nil on success or appropriate error code. // Equivalent to: `rdma system set netns { shared | exclusive }' func RdmaSystemSetNetnsMode(NewMode string) error { return pkgHandle.RdmaSystemSetNetnsMode(NewMode) } // RdmaSystemSetNetnsMode sets the net namespace mode for RDMA subsystem // Returns nil on success or appropriate error code. // Equivalent to: `rdma system set netns { shared | exclusive }' func (h *Handle) RdmaSystemSetNetnsMode(NewMode string) error { value, err := netnsModeStringToUint8(NewMode) if err != nil { return err } proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_SYS_SET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) data := nl.NewRtAttr(nl.RDMA_NLDEV_SYS_ATTR_NETNS_MODE, []byte{value}) req.AddData(data) _, err = req.Execute(unix.NETLINK_RDMA, 0) return err } // RdmaLinkSetNsFd puts the RDMA device into a new network namespace. The // fd must be an open file descriptor to a network namespace. // Similar to: `rdma dev set $dev netns $ns` func RdmaLinkSetNsFd(link *RdmaLink, fd uint32) error { return pkgHandle.RdmaLinkSetNsFd(link, fd) } // RdmaLinkSetNsFd puts the RDMA device into a new network namespace. The // fd must be an open file descriptor to a network namespace. // Similar to: `rdma dev set $dev netns $ns` func (h *Handle) RdmaLinkSetNsFd(link *RdmaLink, fd uint32) error { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_SET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) data := nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_DEV_INDEX, nl.Uint32Attr(link.Attrs.Index)) req.AddData(data) data = nl.NewRtAttr(nl.RDMA_NLDEV_NET_NS_FD, nl.Uint32Attr(fd)) req.AddData(data) return execRdmaSetLink(req) } // RdmaLinkDel deletes an rdma link // // Similar to: rdma link delete NAME // REF: https://man7.org/linux/man-pages/man8/rdma-link.8.html func RdmaLinkDel(name string) error { return pkgHandle.RdmaLinkDel(name) } // RdmaLinkDel deletes an rdma link. func (h *Handle) RdmaLinkDel(name string) error { link, err := h.RdmaLinkByName(name) if err != nil { return err } proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_DELLINK) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) b := make([]byte, 4) native.PutUint32(b, link.Attrs.Index) req.AddData(nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_DEV_INDEX, b)) _, err = req.Execute(unix.NETLINK_RDMA, 0) return err } // RdmaLinkAdd adds an rdma link for the specified type to the network device. // Similar to: rdma link add NAME type TYPE netdev NETDEV // NAME - specifies the new name of the rdma link to add // TYPE - specifies which rdma type to use. Link types: // rxe - Soft RoCE driver // siw - Soft iWARP driver // NETDEV - specifies the network device to which the link is bound // // REF: https://man7.org/linux/man-pages/man8/rdma-link.8.html func RdmaLinkAdd(linkName, linkType, netdev string) error { return pkgHandle.RdmaLinkAdd(linkName, linkType, netdev) } // RdmaLinkAdd adds an rdma link for the specified type to the network device. func (h *Handle) RdmaLinkAdd(linkName string, linkType string, netdev string) error { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_NEWLINK) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK) req.AddData(nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_DEV_NAME, nl.ZeroTerminated(linkName))) req.AddData(nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_LINK_TYPE, nl.ZeroTerminated(linkType))) req.AddData(nl.NewRtAttr(nl.RDMA_NLDEV_ATTR_NDEV_NAME, nl.ZeroTerminated(netdev))) _, err := req.Execute(unix.NETLINK_RDMA, 0) return err } netlink-1.3.0/rdma_link_test.go000066400000000000000000000100411466216277000165140ustar00rootroot00000000000000// +build linux package netlink import ( "io/ioutil" "strings" "testing" "github.com/vishvananda/netns" ) func setupRdmaKModule(t *testing.T, name string) { skipUnlessRoot(t) file, err := ioutil.ReadFile("/proc/modules") if err != nil { t.Fatal("Failed to open /proc/modules", err) } for _, line := range strings.Split(string(file), "\n") { n := strings.Split(line, " ")[0] if n == name { return } } t.Skipf("Test requires kmodule %q.", name) } func TestRdmaGetRdmaLink(t *testing.T) { minKernelRequired(t, 4, 16) setupRdmaKModule(t, "ib_core") _, err := RdmaLinkByName("foo") if err != nil { t.Fatal(err) } } func TestRdmaSetRdmaLinkName(t *testing.T) { minKernelRequired(t, 4, 19) setupRdmaKModule(t, "ib_core") link, err := RdmaLinkByName("foo") if err != nil { t.Fatal(err) } // Set new name err = RdmaLinkSetName(link, "bar") if err != nil { t.Fatal(err) } // Revert back to old name err = RdmaLinkSetName(link, "foo") if err != nil { t.Fatal(err) } } func TestRdmaSystemGetNetnsMode(t *testing.T) { minKernelRequired(t, 5, 2) setupRdmaKModule(t, "ib_core") mode, err := RdmaSystemGetNetnsMode() if err != nil { t.Fatal(err) } t.Log("rdma system netns mode =", mode) } func TestRdmaSystemSetNetnsMode(t *testing.T) { var newMode string var mode string var err error minKernelRequired(t, 5, 2) setupRdmaKModule(t, "ib_core") mode, err = RdmaSystemGetNetnsMode() if err != nil { t.Fatal(err) } t.Log("current rdma system mode =", mode) err = RdmaSystemSetNetnsMode(mode) if err != nil { t.Fatal(err) } // Flip the mode from current mode if mode == "exclusive" { RdmaSystemSetNetnsMode("shared") } else { RdmaSystemSetNetnsMode("exclusive") } newMode, err = RdmaSystemGetNetnsMode() if err != nil { t.Fatal(err) } t.Log("new rdma system mode =", newMode) // Change back to original mode err = RdmaSystemSetNetnsMode(mode) if err != nil { t.Fatal(err) } } func TestRdmaLinkSetNsFd(t *testing.T) { minKernelRequired(t, 5, 2) setupRdmaKModule(t, "ib_core") mode, err := RdmaSystemGetNetnsMode() if err != nil { t.Fatal(err) } t.Log("current rdma netns mode", mode) err = RdmaSystemSetNetnsMode("exclusive") if err != nil { t.Fatal(err) } basens, err := netns.Get() if err != nil { RdmaSystemSetNetnsMode(mode) t.Fatal("Failed to get basens") } defer basens.Close() newns, err := netns.New() if err != nil { RdmaSystemSetNetnsMode(mode) t.Fatal("Failed to create newns") } netns.Set(basens) link, err := RdmaLinkByName("foo") if err != nil { // Remove the namespace as RDMA subsystem requires // no namespace to exist when changing net namespace mode newns.Close() RdmaSystemSetNetnsMode(mode) t.Fatal(err) } t.Log("rdma link: ", link) err = RdmaLinkSetNsFd(link, uint32(newns)) if err != nil { newns.Close() RdmaSystemSetNetnsMode(mode) t.Fatal(err) } newns.Close() //Set the old mode back at start of the test err = RdmaSystemSetNetnsMode(mode) if err != nil { t.Fatal(err) } } func TestRdmaLinkList(t *testing.T) { minKernelRequired(t, 4, 16) setupRdmaKModule(t, "ib_core") links, err := RdmaLinkList() if err != nil { t.Fatal(err) } t.Log("RDMA devices:") for _, link := range links { t.Logf("%d: %s", link.Attrs.Index, link.Attrs.Name) } } func TestRdmaLinkAddAndDel(t *testing.T) { // related commit is https://github.com/torvalds/linux/commit/3856ec4b93c9463d36ee39098dde1fbbd29ec6dd. minKernelRequired(t, 5, 1) setupRdmaKModule(t, "rdma_rxe") checkPresence := func(name string, exist bool) { links, err := RdmaLinkList() if err != nil { t.Fatal(err) } found := false for _, link := range links { if link.Attrs.Name == name { found = true break } } if found != exist { t.Fatalf("expected rdma link %s presence=%v, but got presence=%v", name, exist, found) } } linkName := t.Name() if err := RdmaLinkAdd(linkName, "rxe", "lo"); err != nil { t.Fatal(err) } checkPresence(linkName, true) if err := RdmaLinkDel(linkName); err != nil { t.Fatal(err) } checkPresence(linkName, false) } netlink-1.3.0/route.go000066400000000000000000000132231466216277000146600ustar00rootroot00000000000000package netlink import ( "fmt" "net" "strings" ) // Scope is an enum representing a route scope. type Scope uint8 type NextHopFlag int const ( RT_FILTER_PROTOCOL uint64 = 1 << (1 + iota) RT_FILTER_SCOPE RT_FILTER_TYPE RT_FILTER_TOS RT_FILTER_IIF RT_FILTER_OIF RT_FILTER_DST RT_FILTER_SRC RT_FILTER_GW RT_FILTER_TABLE RT_FILTER_HOPLIMIT RT_FILTER_PRIORITY RT_FILTER_MARK RT_FILTER_MASK RT_FILTER_REALM ) type Destination interface { Family() int Decode([]byte) error Encode() ([]byte, error) String() string Equal(Destination) bool } type Encap interface { Type() int Decode([]byte) error Encode() ([]byte, error) String() string Equal(Encap) bool } //Protocol describe what was the originator of the route type RouteProtocol int // Route represents a netlink route. type Route struct { LinkIndex int ILinkIndex int Scope Scope Dst *net.IPNet Src net.IP Gw net.IP MultiPath []*NexthopInfo Protocol RouteProtocol Priority int Family int Table int Type int Tos int Flags int MPLSDst *int NewDst Destination Encap Encap Via Destination Realm int MTU int Window int Rtt int RttVar int Ssthresh int Cwnd int AdvMSS int Reordering int Hoplimit int InitCwnd int Features int RtoMin int InitRwnd int QuickACK int Congctl string FastOpenNoCookie int } func (r Route) String() string { elems := []string{} if len(r.MultiPath) == 0 { elems = append(elems, fmt.Sprintf("Ifindex: %d", r.LinkIndex)) } if r.MPLSDst != nil { elems = append(elems, fmt.Sprintf("Dst: %d", r.MPLSDst)) } else { elems = append(elems, fmt.Sprintf("Dst: %s", r.Dst)) } if r.NewDst != nil { elems = append(elems, fmt.Sprintf("NewDst: %s", r.NewDst)) } if r.Encap != nil { elems = append(elems, fmt.Sprintf("Encap: %s", r.Encap)) } if r.Via != nil { elems = append(elems, fmt.Sprintf("Via: %s", r.Via)) } elems = append(elems, fmt.Sprintf("Src: %s", r.Src)) if len(r.MultiPath) > 0 { elems = append(elems, fmt.Sprintf("Gw: %s", r.MultiPath)) } else { elems = append(elems, fmt.Sprintf("Gw: %s", r.Gw)) } elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags())) elems = append(elems, fmt.Sprintf("Table: %d", r.Table)) elems = append(elems, fmt.Sprintf("Realm: %d", r.Realm)) return fmt.Sprintf("{%s}", strings.Join(elems, " ")) } func (r Route) Equal(x Route) bool { return r.LinkIndex == x.LinkIndex && r.ILinkIndex == x.ILinkIndex && r.Scope == x.Scope && ipNetEqual(r.Dst, x.Dst) && r.Src.Equal(x.Src) && r.Gw.Equal(x.Gw) && nexthopInfoSlice(r.MultiPath).Equal(x.MultiPath) && r.Protocol == x.Protocol && r.Priority == x.Priority && r.Realm == x.Realm && r.Table == x.Table && r.Type == x.Type && r.Tos == x.Tos && r.Hoplimit == x.Hoplimit && r.Flags == x.Flags && (r.MPLSDst == x.MPLSDst || (r.MPLSDst != nil && x.MPLSDst != nil && *r.MPLSDst == *x.MPLSDst)) && (r.NewDst == x.NewDst || (r.NewDst != nil && r.NewDst.Equal(x.NewDst))) && (r.Via == x.Via || (r.Via != nil && r.Via.Equal(x.Via))) && (r.Encap == x.Encap || (r.Encap != nil && r.Encap.Equal(x.Encap))) } func (r *Route) SetFlag(flag NextHopFlag) { r.Flags |= int(flag) } func (r *Route) ClearFlag(flag NextHopFlag) { r.Flags &^= int(flag) } type flagString struct { f NextHopFlag s string } // RouteUpdate is sent when a route changes - type is RTM_NEWROUTE or RTM_DELROUTE // NlFlags is only non-zero for RTM_NEWROUTE, the following flags can be set: // - unix.NLM_F_REPLACE - Replace existing matching config object with this request // - unix.NLM_F_EXCL - Don't replace the config object if it already exists // - unix.NLM_F_CREATE - Create config object if it doesn't already exist // - unix.NLM_F_APPEND - Add to the end of the object list type RouteUpdate struct { Type uint16 NlFlags uint16 Route } type NexthopInfo struct { LinkIndex int Hops int Gw net.IP Flags int NewDst Destination Encap Encap Via Destination } func (n *NexthopInfo) String() string { elems := []string{} elems = append(elems, fmt.Sprintf("Ifindex: %d", n.LinkIndex)) if n.NewDst != nil { elems = append(elems, fmt.Sprintf("NewDst: %s", n.NewDst)) } if n.Encap != nil { elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap)) } if n.Via != nil { elems = append(elems, fmt.Sprintf("Via: %s", n.Via)) } elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1)) elems = append(elems, fmt.Sprintf("Gw: %s", n.Gw)) elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags())) return fmt.Sprintf("{%s}", strings.Join(elems, " ")) } func (n NexthopInfo) Equal(x NexthopInfo) bool { return n.LinkIndex == x.LinkIndex && n.Hops == x.Hops && n.Gw.Equal(x.Gw) && n.Flags == x.Flags && (n.NewDst == x.NewDst || (n.NewDst != nil && n.NewDst.Equal(x.NewDst))) && (n.Encap == x.Encap || (n.Encap != nil && n.Encap.Equal(x.Encap))) } type nexthopInfoSlice []*NexthopInfo func (n nexthopInfoSlice) Equal(x []*NexthopInfo) bool { if len(n) != len(x) { return false } for i := range n { if n[i] == nil || x[i] == nil { return false } if !n[i].Equal(*x[i]) { return false } } return true } // ipNetEqual returns true iff both IPNet are equal func ipNetEqual(ipn1 *net.IPNet, ipn2 *net.IPNet) bool { if ipn1 == ipn2 { return true } if ipn1 == nil || ipn2 == nil { return false } m1, _ := ipn1.Mask.Size() m2, _ := ipn2.Mask.Size() return m1 == m2 && ipn1.IP.Equal(ipn2.IP) } netlink-1.3.0/route_linux.go000066400000000000000000001413601466216277000161030ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/binary" "fmt" "net" "strconv" "strings" "syscall" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) // RtAttr is shared so it is in netlink_linux.go const ( SCOPE_UNIVERSE Scope = unix.RT_SCOPE_UNIVERSE SCOPE_SITE Scope = unix.RT_SCOPE_SITE SCOPE_LINK Scope = unix.RT_SCOPE_LINK SCOPE_HOST Scope = unix.RT_SCOPE_HOST SCOPE_NOWHERE Scope = unix.RT_SCOPE_NOWHERE ) func (s Scope) String() string { switch s { case SCOPE_UNIVERSE: return "universe" case SCOPE_SITE: return "site" case SCOPE_LINK: return "link" case SCOPE_HOST: return "host" case SCOPE_NOWHERE: return "nowhere" default: return "unknown" } } const ( FLAG_ONLINK NextHopFlag = unix.RTNH_F_ONLINK FLAG_PERVASIVE NextHopFlag = unix.RTNH_F_PERVASIVE ) var testFlags = []flagString{ {f: FLAG_ONLINK, s: "onlink"}, {f: FLAG_PERVASIVE, s: "pervasive"}, } func listFlags(flag int) []string { var flags []string for _, tf := range testFlags { if flag&int(tf.f) != 0 { flags = append(flags, tf.s) } } return flags } func (r *Route) ListFlags() []string { return listFlags(r.Flags) } func (n *NexthopInfo) ListFlags() []string { return listFlags(n.Flags) } type MPLSDestination struct { Labels []int } func (d *MPLSDestination) Family() int { return nl.FAMILY_MPLS } func (d *MPLSDestination) Decode(buf []byte) error { d.Labels = nl.DecodeMPLSStack(buf) return nil } func (d *MPLSDestination) Encode() ([]byte, error) { return nl.EncodeMPLSStack(d.Labels...), nil } func (d *MPLSDestination) String() string { s := make([]string, 0, len(d.Labels)) for _, l := range d.Labels { s = append(s, fmt.Sprintf("%d", l)) } return strings.Join(s, "/") } func (d *MPLSDestination) Equal(x Destination) bool { o, ok := x.(*MPLSDestination) if !ok { return false } if d == nil && o == nil { return true } if d == nil || o == nil { return false } if d.Labels == nil && o.Labels == nil { return true } if d.Labels == nil || o.Labels == nil { return false } if len(d.Labels) != len(o.Labels) { return false } for i := range d.Labels { if d.Labels[i] != o.Labels[i] { return false } } return true } type MPLSEncap struct { Labels []int } func (e *MPLSEncap) Type() int { return nl.LWTUNNEL_ENCAP_MPLS } func (e *MPLSEncap) Decode(buf []byte) error { if len(buf) < 4 { return fmt.Errorf("lack of bytes") } l := native.Uint16(buf) if len(buf) < int(l) { return fmt.Errorf("lack of bytes") } buf = buf[:l] typ := native.Uint16(buf[2:]) if typ != nl.MPLS_IPTUNNEL_DST { return fmt.Errorf("unknown MPLS Encap Type: %d", typ) } e.Labels = nl.DecodeMPLSStack(buf[4:]) return nil } func (e *MPLSEncap) Encode() ([]byte, error) { s := nl.EncodeMPLSStack(e.Labels...) hdr := make([]byte, 4) native.PutUint16(hdr, uint16(len(s)+4)) native.PutUint16(hdr[2:], nl.MPLS_IPTUNNEL_DST) return append(hdr, s...), nil } func (e *MPLSEncap) String() string { s := make([]string, 0, len(e.Labels)) for _, l := range e.Labels { s = append(s, fmt.Sprintf("%d", l)) } return strings.Join(s, "/") } func (e *MPLSEncap) Equal(x Encap) bool { o, ok := x.(*MPLSEncap) if !ok { return false } if e == nil && o == nil { return true } if e == nil || o == nil { return false } if e.Labels == nil && o.Labels == nil { return true } if e.Labels == nil || o.Labels == nil { return false } if len(e.Labels) != len(o.Labels) { return false } for i := range e.Labels { if e.Labels[i] != o.Labels[i] { return false } } return true } // SEG6 definitions type SEG6Encap struct { Mode int Segments []net.IP } func (e *SEG6Encap) Type() int { return nl.LWTUNNEL_ENCAP_SEG6 } func (e *SEG6Encap) Decode(buf []byte) error { if len(buf) < 4 { return fmt.Errorf("lack of bytes") } // Get Length(l) & Type(typ) : 2 + 2 bytes l := native.Uint16(buf) if len(buf) < int(l) { return fmt.Errorf("lack of bytes") } buf = buf[:l] // make sure buf size upper limit is Length typ := native.Uint16(buf[2:]) // LWTUNNEL_ENCAP_SEG6 has only one attr type SEG6_IPTUNNEL_SRH if typ != nl.SEG6_IPTUNNEL_SRH { return fmt.Errorf("unknown SEG6 Type: %d", typ) } var err error e.Mode, e.Segments, err = nl.DecodeSEG6Encap(buf[4:]) return err } func (e *SEG6Encap) Encode() ([]byte, error) { s, err := nl.EncodeSEG6Encap(e.Mode, e.Segments) hdr := make([]byte, 4) native.PutUint16(hdr, uint16(len(s)+4)) native.PutUint16(hdr[2:], nl.SEG6_IPTUNNEL_SRH) return append(hdr, s...), err } func (e *SEG6Encap) String() string { segs := make([]string, 0, len(e.Segments)) // append segment backwards (from n to 0) since seg#0 is the last segment. for i := len(e.Segments); i > 0; i-- { segs = append(segs, e.Segments[i-1].String()) } str := fmt.Sprintf("mode %s segs %d [ %s ]", nl.SEG6EncapModeString(e.Mode), len(e.Segments), strings.Join(segs, " ")) return str } func (e *SEG6Encap) Equal(x Encap) bool { o, ok := x.(*SEG6Encap) if !ok { return false } if e == o { return true } if e == nil || o == nil { return false } if e.Mode != o.Mode { return false } if len(e.Segments) != len(o.Segments) { return false } for i := range e.Segments { if !e.Segments[i].Equal(o.Segments[i]) { return false } } return true } // SEG6LocalEncap definitions type SEG6LocalEncap struct { Flags [nl.SEG6_LOCAL_MAX]bool Action int Segments []net.IP // from SRH in seg6_local_lwt Table int // table id for End.T and End.DT6 InAddr net.IP In6Addr net.IP Iif int Oif int bpf bpfObj } func (e *SEG6LocalEncap) SetProg(progFd int, progName string) error { if progFd <= 0 { return fmt.Errorf("seg6local bpf SetProg: invalid fd") } e.bpf.progFd = progFd e.bpf.progName = progName return nil } func (e *SEG6LocalEncap) Type() int { return nl.LWTUNNEL_ENCAP_SEG6_LOCAL } func (e *SEG6LocalEncap) Decode(buf []byte) error { attrs, err := nl.ParseRouteAttr(buf) if err != nil { return err } for _, attr := range attrs { switch attr.Attr.Type { case nl.SEG6_LOCAL_ACTION: e.Action = int(native.Uint32(attr.Value[0:4])) e.Flags[nl.SEG6_LOCAL_ACTION] = true case nl.SEG6_LOCAL_SRH: e.Segments, err = nl.DecodeSEG6Srh(attr.Value[:]) e.Flags[nl.SEG6_LOCAL_SRH] = true case nl.SEG6_LOCAL_TABLE: e.Table = int(native.Uint32(attr.Value[0:4])) e.Flags[nl.SEG6_LOCAL_TABLE] = true case nl.SEG6_LOCAL_NH4: e.InAddr = net.IP(attr.Value[0:4]) e.Flags[nl.SEG6_LOCAL_NH4] = true case nl.SEG6_LOCAL_NH6: e.In6Addr = net.IP(attr.Value[0:16]) e.Flags[nl.SEG6_LOCAL_NH6] = true case nl.SEG6_LOCAL_IIF: e.Iif = int(native.Uint32(attr.Value[0:4])) e.Flags[nl.SEG6_LOCAL_IIF] = true case nl.SEG6_LOCAL_OIF: e.Oif = int(native.Uint32(attr.Value[0:4])) e.Flags[nl.SEG6_LOCAL_OIF] = true case nl.SEG6_LOCAL_BPF: var bpfAttrs []syscall.NetlinkRouteAttr bpfAttrs, err = nl.ParseRouteAttr(attr.Value) bpfobj := bpfObj{} for _, bpfAttr := range bpfAttrs { switch bpfAttr.Attr.Type { case nl.LWT_BPF_PROG_FD: bpfobj.progFd = int(native.Uint32(bpfAttr.Value)) case nl.LWT_BPF_PROG_NAME: bpfobj.progName = string(bpfAttr.Value) default: err = fmt.Errorf("seg6local bpf decode: unknown attribute: Type %d", bpfAttr.Attr) } } e.bpf = bpfobj e.Flags[nl.SEG6_LOCAL_BPF] = true } } return err } func (e *SEG6LocalEncap) Encode() ([]byte, error) { var err error res := make([]byte, 8) native.PutUint16(res, 8) // length native.PutUint16(res[2:], nl.SEG6_LOCAL_ACTION) native.PutUint32(res[4:], uint32(e.Action)) if e.Flags[nl.SEG6_LOCAL_SRH] { srh, err := nl.EncodeSEG6Srh(e.Segments) if err != nil { return nil, err } attr := make([]byte, 4) native.PutUint16(attr, uint16(len(srh)+4)) native.PutUint16(attr[2:], nl.SEG6_LOCAL_SRH) attr = append(attr, srh...) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_TABLE] { attr := make([]byte, 8) native.PutUint16(attr, 8) native.PutUint16(attr[2:], nl.SEG6_LOCAL_TABLE) native.PutUint32(attr[4:], uint32(e.Table)) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_NH4] { attr := make([]byte, 4) native.PutUint16(attr, 8) native.PutUint16(attr[2:], nl.SEG6_LOCAL_NH4) ipv4 := e.InAddr.To4() if ipv4 == nil { err = fmt.Errorf("SEG6_LOCAL_NH4 has invalid IPv4 address") return nil, err } attr = append(attr, ipv4...) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_NH6] { attr := make([]byte, 4) native.PutUint16(attr, 20) native.PutUint16(attr[2:], nl.SEG6_LOCAL_NH6) attr = append(attr, e.In6Addr...) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_IIF] { attr := make([]byte, 8) native.PutUint16(attr, 8) native.PutUint16(attr[2:], nl.SEG6_LOCAL_IIF) native.PutUint32(attr[4:], uint32(e.Iif)) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_OIF] { attr := make([]byte, 8) native.PutUint16(attr, 8) native.PutUint16(attr[2:], nl.SEG6_LOCAL_OIF) native.PutUint32(attr[4:], uint32(e.Oif)) res = append(res, attr...) } if e.Flags[nl.SEG6_LOCAL_BPF] { attr := nl.NewRtAttr(nl.SEG6_LOCAL_BPF, []byte{}) if e.bpf.progFd != 0 { attr.AddRtAttr(nl.LWT_BPF_PROG_FD, nl.Uint32Attr(uint32(e.bpf.progFd))) } if e.bpf.progName != "" { attr.AddRtAttr(nl.LWT_BPF_PROG_NAME, nl.ZeroTerminated(e.bpf.progName)) } res = append(res, attr.Serialize()...) } return res, err } func (e *SEG6LocalEncap) String() string { strs := make([]string, 0, nl.SEG6_LOCAL_MAX) strs = append(strs, fmt.Sprintf("action %s", nl.SEG6LocalActionString(e.Action))) if e.Flags[nl.SEG6_LOCAL_TABLE] { strs = append(strs, fmt.Sprintf("table %d", e.Table)) } if e.Flags[nl.SEG6_LOCAL_NH4] { strs = append(strs, fmt.Sprintf("nh4 %s", e.InAddr)) } if e.Flags[nl.SEG6_LOCAL_NH6] { strs = append(strs, fmt.Sprintf("nh6 %s", e.In6Addr)) } if e.Flags[nl.SEG6_LOCAL_IIF] { link, err := LinkByIndex(e.Iif) if err != nil { strs = append(strs, fmt.Sprintf("iif %d", e.Iif)) } else { strs = append(strs, fmt.Sprintf("iif %s", link.Attrs().Name)) } } if e.Flags[nl.SEG6_LOCAL_OIF] { link, err := LinkByIndex(e.Oif) if err != nil { strs = append(strs, fmt.Sprintf("oif %d", e.Oif)) } else { strs = append(strs, fmt.Sprintf("oif %s", link.Attrs().Name)) } } if e.Flags[nl.SEG6_LOCAL_SRH] { segs := make([]string, 0, len(e.Segments)) // append segment backwards (from n to 0) since seg#0 is the last segment. for i := len(e.Segments); i > 0; i-- { segs = append(segs, e.Segments[i-1].String()) } strs = append(strs, fmt.Sprintf("segs %d [ %s ]", len(e.Segments), strings.Join(segs, " "))) } if e.Flags[nl.SEG6_LOCAL_BPF] { strs = append(strs, fmt.Sprintf("bpf %s[%d]", e.bpf.progName, e.bpf.progFd)) } return strings.Join(strs, " ") } func (e *SEG6LocalEncap) Equal(x Encap) bool { o, ok := x.(*SEG6LocalEncap) if !ok { return false } if e == o { return true } if e == nil || o == nil { return false } // compare all arrays first for i := range e.Flags { if e.Flags[i] != o.Flags[i] { return false } } if len(e.Segments) != len(o.Segments) { return false } for i := range e.Segments { if !e.Segments[i].Equal(o.Segments[i]) { return false } } // compare values if !e.InAddr.Equal(o.InAddr) || !e.In6Addr.Equal(o.In6Addr) { return false } if e.Action != o.Action || e.Table != o.Table || e.Iif != o.Iif || e.Oif != o.Oif || e.bpf != o.bpf { return false } return true } // Encap BPF definitions type bpfObj struct { progFd int progName string } type BpfEncap struct { progs [nl.LWT_BPF_MAX]bpfObj headroom int } // SetProg adds a bpf function to the route via netlink RTA_ENCAP. The fd must be a bpf // program loaded with bpf(type=BPF_PROG_TYPE_LWT_*) matching the direction the program should // be applied to (LWT_BPF_IN, LWT_BPF_OUT, LWT_BPF_XMIT). func (e *BpfEncap) SetProg(mode, progFd int, progName string) error { if progFd <= 0 { return fmt.Errorf("lwt bpf SetProg: invalid fd") } if mode <= nl.LWT_BPF_UNSPEC || mode >= nl.LWT_BPF_XMIT_HEADROOM { return fmt.Errorf("lwt bpf SetProg:invalid mode") } e.progs[mode].progFd = progFd e.progs[mode].progName = fmt.Sprintf("%s[fd:%d]", progName, progFd) return nil } // SetXmitHeadroom sets the xmit headroom (LWT_BPF_MAX_HEADROOM) via netlink RTA_ENCAP. // maximum headroom is LWT_BPF_MAX_HEADROOM func (e *BpfEncap) SetXmitHeadroom(headroom int) error { if headroom > nl.LWT_BPF_MAX_HEADROOM || headroom < 0 { return fmt.Errorf("invalid headroom size. range is 0 - %d", nl.LWT_BPF_MAX_HEADROOM) } e.headroom = headroom return nil } func (e *BpfEncap) Type() int { return nl.LWTUNNEL_ENCAP_BPF } func (e *BpfEncap) Decode(buf []byte) error { if len(buf) < 4 { return fmt.Errorf("lwt bpf decode: lack of bytes") } native := nl.NativeEndian() attrs, err := nl.ParseRouteAttr(buf) if err != nil { return fmt.Errorf("lwt bpf decode: failed parsing attribute. err: %v", err) } for _, attr := range attrs { if int(attr.Attr.Type) < 1 { // nl.LWT_BPF_UNSPEC continue } if int(attr.Attr.Type) > nl.LWT_BPF_MAX { return fmt.Errorf("lwt bpf decode: received unknown attribute type: %d", attr.Attr.Type) } switch int(attr.Attr.Type) { case nl.LWT_BPF_MAX_HEADROOM: e.headroom = int(native.Uint32(attr.Value)) default: bpfO := bpfObj{} parsedAttrs, err := nl.ParseRouteAttr(attr.Value) if err != nil { return fmt.Errorf("lwt bpf decode: failed parsing route attribute") } for _, parsedAttr := range parsedAttrs { switch int(parsedAttr.Attr.Type) { case nl.LWT_BPF_PROG_FD: bpfO.progFd = int(native.Uint32(parsedAttr.Value)) case nl.LWT_BPF_PROG_NAME: bpfO.progName = string(parsedAttr.Value) default: return fmt.Errorf("lwt bpf decode: received unknown attribute: type: %d, len: %d", parsedAttr.Attr.Type, parsedAttr.Attr.Len) } } e.progs[attr.Attr.Type] = bpfO } } return nil } func (e *BpfEncap) Encode() ([]byte, error) { buf := make([]byte, 0) native = nl.NativeEndian() for index, attr := range e.progs { nlMsg := nl.NewRtAttr(index, []byte{}) if attr.progFd != 0 { nlMsg.AddRtAttr(nl.LWT_BPF_PROG_FD, nl.Uint32Attr(uint32(attr.progFd))) } if attr.progName != "" { nlMsg.AddRtAttr(nl.LWT_BPF_PROG_NAME, nl.ZeroTerminated(attr.progName)) } if nlMsg.Len() > 4 { buf = append(buf, nlMsg.Serialize()...) } } if len(buf) <= 4 { return nil, fmt.Errorf("lwt bpf encode: bpf obj definitions returned empty buffer") } if e.headroom > 0 { hRoom := nl.NewRtAttr(nl.LWT_BPF_XMIT_HEADROOM, nl.Uint32Attr(uint32(e.headroom))) buf = append(buf, hRoom.Serialize()...) } return buf, nil } func (e *BpfEncap) String() string { progs := make([]string, 0) for index, obj := range e.progs { empty := bpfObj{} switch index { case nl.LWT_BPF_IN: if obj != empty { progs = append(progs, fmt.Sprintf("in: %s", obj.progName)) } case nl.LWT_BPF_OUT: if obj != empty { progs = append(progs, fmt.Sprintf("out: %s", obj.progName)) } case nl.LWT_BPF_XMIT: if obj != empty { progs = append(progs, fmt.Sprintf("xmit: %s", obj.progName)) } } } if e.headroom > 0 { progs = append(progs, fmt.Sprintf("xmit headroom: %d", e.headroom)) } return strings.Join(progs, " ") } func (e *BpfEncap) Equal(x Encap) bool { o, ok := x.(*BpfEncap) if !ok { return false } if e.headroom != o.headroom { return false } for i := range o.progs { if o.progs[i] != e.progs[i] { return false } } return true } // IP6tnlEncap definition type IP6tnlEncap struct { ID uint64 Dst net.IP Src net.IP Hoplimit uint8 TC uint8 Flags uint16 } func (e *IP6tnlEncap) Type() int { return nl.LWTUNNEL_ENCAP_IP6 } func (e *IP6tnlEncap) Decode(buf []byte) error { attrs, err := nl.ParseRouteAttr(buf) if err != nil { return err } for _, attr := range attrs { switch attr.Attr.Type { case nl.LWTUNNEL_IP6_ID: e.ID = uint64(native.Uint64(attr.Value[0:4])) case nl.LWTUNNEL_IP6_DST: e.Dst = net.IP(attr.Value[:]) case nl.LWTUNNEL_IP6_SRC: e.Src = net.IP(attr.Value[:]) case nl.LWTUNNEL_IP6_HOPLIMIT: e.Hoplimit = attr.Value[0] case nl.LWTUNNEL_IP6_TC: // e.TC = attr.Value[0] err = fmt.Errorf("decoding TC in IP6tnlEncap is not supported") case nl.LWTUNNEL_IP6_FLAGS: // e.Flags = uint16(native.Uint16(attr.Value[0:2])) err = fmt.Errorf("decoding FLAG in IP6tnlEncap is not supported") case nl.LWTUNNEL_IP6_PAD: err = fmt.Errorf("decoding PAD in IP6tnlEncap is not supported") case nl.LWTUNNEL_IP6_OPTS: err = fmt.Errorf("decoding OPTS in IP6tnlEncap is not supported") } } return err } func (e *IP6tnlEncap) Encode() ([]byte, error) { final := []byte{} resID := make([]byte, 12) native.PutUint16(resID, 12) // 2+2+8 native.PutUint16(resID[2:], nl.LWTUNNEL_IP6_ID) native.PutUint64(resID[4:], 0) final = append(final, resID...) resDst := make([]byte, 4) native.PutUint16(resDst, 20) // 2+2+16 native.PutUint16(resDst[2:], nl.LWTUNNEL_IP6_DST) resDst = append(resDst, e.Dst...) final = append(final, resDst...) resSrc := make([]byte, 4) native.PutUint16(resSrc, 20) native.PutUint16(resSrc[2:], nl.LWTUNNEL_IP6_SRC) resSrc = append(resSrc, e.Src...) final = append(final, resSrc...) // resTc := make([]byte, 5) // native.PutUint16(resTc, 5) // native.PutUint16(resTc[2:], nl.LWTUNNEL_IP6_TC) // resTc[4] = e.TC // final = append(final,resTc...) resHops := make([]byte, 5) native.PutUint16(resHops, 5) native.PutUint16(resHops[2:], nl.LWTUNNEL_IP6_HOPLIMIT) resHops[4] = e.Hoplimit final = append(final, resHops...) // resFlags := make([]byte, 6) // native.PutUint16(resFlags, 6) // native.PutUint16(resFlags[2:], nl.LWTUNNEL_IP6_FLAGS) // native.PutUint16(resFlags[4:], e.Flags) // final = append(final,resFlags...) return final, nil } func (e *IP6tnlEncap) String() string { return fmt.Sprintf("id %d src %s dst %s hoplimit %d tc %d flags 0x%.4x", e.ID, e.Src, e.Dst, e.Hoplimit, e.TC, e.Flags) } func (e *IP6tnlEncap) Equal(x Encap) bool { o, ok := x.(*IP6tnlEncap) if !ok { return false } if e.ID != o.ID || e.Flags != o.Flags || e.Hoplimit != o.Hoplimit || e.Src.Equal(o.Src) || e.Dst.Equal(o.Dst) || e.TC != o.TC { return false } return true } type Via struct { AddrFamily int Addr net.IP } func (v *Via) Equal(x Destination) bool { o, ok := x.(*Via) if !ok { return false } if v.AddrFamily == x.Family() && v.Addr.Equal(o.Addr) { return true } return false } func (v *Via) String() string { return fmt.Sprintf("Family: %d, Address: %s", v.AddrFamily, v.Addr.String()) } func (v *Via) Family() int { return v.AddrFamily } func (v *Via) Encode() ([]byte, error) { buf := &bytes.Buffer{} err := binary.Write(buf, native, uint16(v.AddrFamily)) if err != nil { return nil, err } err = binary.Write(buf, native, v.Addr) if err != nil { return nil, err } return buf.Bytes(), nil } func (v *Via) Decode(b []byte) error { if len(b) < 6 { return fmt.Errorf("decoding failed: buffer too small (%d bytes)", len(b)) } v.AddrFamily = int(native.Uint16(b[0:2])) if v.AddrFamily == nl.FAMILY_V4 { v.Addr = net.IP(b[2:6]) return nil } else if v.AddrFamily == nl.FAMILY_V6 { if len(b) < 18 { return fmt.Errorf("decoding failed: buffer too small (%d bytes)", len(b)) } v.Addr = net.IP(b[2:]) return nil } return fmt.Errorf("decoding failed: address family %d unknown", v.AddrFamily) } // RouteAdd will add a route to the system. // Equivalent to: `ip route add $route` func RouteAdd(route *Route) error { return pkgHandle.RouteAdd(route) } // RouteAdd will add a route to the system. // Equivalent to: `ip route add $route` func (h *Handle) RouteAdd(route *Route) error { flags := unix.NLM_F_CREATE | unix.NLM_F_EXCL | unix.NLM_F_ACK req := h.newNetlinkRequest(unix.RTM_NEWROUTE, flags) _, err := h.routeHandle(route, req, nl.NewRtMsg()) return err } // RouteAppend will append a route to the system. // Equivalent to: `ip route append $route` func RouteAppend(route *Route) error { return pkgHandle.RouteAppend(route) } // RouteAppend will append a route to the system. // Equivalent to: `ip route append $route` func (h *Handle) RouteAppend(route *Route) error { flags := unix.NLM_F_CREATE | unix.NLM_F_APPEND | unix.NLM_F_ACK req := h.newNetlinkRequest(unix.RTM_NEWROUTE, flags) _, err := h.routeHandle(route, req, nl.NewRtMsg()) return err } // RouteAddEcmp will add a route to the system. func RouteAddEcmp(route *Route) error { return pkgHandle.RouteAddEcmp(route) } // RouteAddEcmp will add a route to the system. func (h *Handle) RouteAddEcmp(route *Route) error { flags := unix.NLM_F_CREATE | unix.NLM_F_ACK req := h.newNetlinkRequest(unix.RTM_NEWROUTE, flags) _, err := h.routeHandle(route, req, nl.NewRtMsg()) return err } // RouteChange will change an existing route in the system. // Equivalent to: `ip route change $route` func RouteChange(route *Route) error { return pkgHandle.RouteChange(route) } // RouteChange will change an existing route in the system. // Equivalent to: `ip route change $route` func (h *Handle) RouteChange(route *Route) error { flags := unix.NLM_F_REPLACE | unix.NLM_F_ACK req := h.newNetlinkRequest(unix.RTM_NEWROUTE, flags) _, err := h.routeHandle(route, req, nl.NewRtMsg()) return err } // RouteReplace will add a route to the system. // Equivalent to: `ip route replace $route` func RouteReplace(route *Route) error { return pkgHandle.RouteReplace(route) } // RouteReplace will add a route to the system. // Equivalent to: `ip route replace $route` func (h *Handle) RouteReplace(route *Route) error { flags := unix.NLM_F_CREATE | unix.NLM_F_REPLACE | unix.NLM_F_ACK req := h.newNetlinkRequest(unix.RTM_NEWROUTE, flags) _, err := h.routeHandle(route, req, nl.NewRtMsg()) return err } // RouteDel will delete a route from the system. // Equivalent to: `ip route del $route` func RouteDel(route *Route) error { return pkgHandle.RouteDel(route) } // RouteDel will delete a route from the system. // Equivalent to: `ip route del $route` func (h *Handle) RouteDel(route *Route) error { req := h.newNetlinkRequest(unix.RTM_DELROUTE, unix.NLM_F_ACK) _, err := h.routeHandle(route, req, nl.NewRtDelMsg()) return err } func (h *Handle) routeHandle(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg) ([][]byte, error) { if err := h.prepareRouteReq(route, req, msg); err != nil { return nil, err } return req.Execute(unix.NETLINK_ROUTE, 0) } func (h *Handle) routeHandleIter(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg, f func(msg []byte) bool) error { if err := h.prepareRouteReq(route, req, msg); err != nil { return err } return req.ExecuteIter(unix.NETLINK_ROUTE, 0, f) } func (h *Handle) prepareRouteReq(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg) error { if req.NlMsghdr.Type != unix.RTM_GETROUTE && (route.Dst == nil || route.Dst.IP == nil) && route.Src == nil && route.Gw == nil && route.MPLSDst == nil { return fmt.Errorf("either Dst.IP, Src.IP or Gw must be set") } family := -1 var rtAttrs []*nl.RtAttr if route.Dst != nil && route.Dst.IP != nil { dstLen, _ := route.Dst.Mask.Size() msg.Dst_len = uint8(dstLen) dstFamily := nl.GetIPFamily(route.Dst.IP) family = dstFamily var dstData []byte if dstFamily == FAMILY_V4 { dstData = route.Dst.IP.To4() } else { dstData = route.Dst.IP.To16() } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_DST, dstData)) } else if route.MPLSDst != nil { family = nl.FAMILY_MPLS msg.Dst_len = uint8(20) msg.Type = unix.RTN_UNICAST rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_DST, nl.EncodeMPLSStack(*route.MPLSDst))) } if route.NewDst != nil { if family != -1 && family != route.NewDst.Family() { return fmt.Errorf("new destination and destination are not the same address family") } buf, err := route.NewDst.Encode() if err != nil { return err } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_NEWDST, buf)) } if route.Encap != nil { buf := make([]byte, 2) native.PutUint16(buf, uint16(route.Encap.Type())) rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_ENCAP_TYPE, buf)) buf, err := route.Encap.Encode() if err != nil { return err } switch route.Encap.Type() { case nl.LWTUNNEL_ENCAP_BPF: rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_ENCAP|unix.NLA_F_NESTED, buf)) default: rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_ENCAP, buf)) } } if route.Src != nil { srcFamily := nl.GetIPFamily(route.Src) if family != -1 && family != srcFamily { return fmt.Errorf("source and destination ip are not the same IP family") } family = srcFamily var srcData []byte if srcFamily == FAMILY_V4 { srcData = route.Src.To4() } else { srcData = route.Src.To16() } // The commonly used src ip for routes is actually PREFSRC rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_PREFSRC, srcData)) } if route.Gw != nil { gwFamily := nl.GetIPFamily(route.Gw) if family != -1 && family != gwFamily { return fmt.Errorf("gateway, source, and destination ip are not the same IP family") } family = gwFamily var gwData []byte if gwFamily == FAMILY_V4 { gwData = route.Gw.To4() } else { gwData = route.Gw.To16() } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_GATEWAY, gwData)) } if route.Via != nil { buf, err := route.Via.Encode() if err != nil { return fmt.Errorf("failed to encode RTA_VIA: %v", err) } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_VIA, buf)) } if len(route.MultiPath) > 0 { buf := []byte{} for _, nh := range route.MultiPath { rtnh := &nl.RtNexthop{ RtNexthop: unix.RtNexthop{ Hops: uint8(nh.Hops), Ifindex: int32(nh.LinkIndex), Flags: uint8(nh.Flags), }, } children := []nl.NetlinkRequestData{} if nh.Gw != nil { gwFamily := nl.GetIPFamily(nh.Gw) if family != -1 && family != gwFamily { return fmt.Errorf("gateway, source, and destination ip are not the same IP family") } if gwFamily == FAMILY_V4 { children = append(children, nl.NewRtAttr(unix.RTA_GATEWAY, []byte(nh.Gw.To4()))) } else { children = append(children, nl.NewRtAttr(unix.RTA_GATEWAY, []byte(nh.Gw.To16()))) } } if nh.NewDst != nil { if family != -1 && family != nh.NewDst.Family() { return fmt.Errorf("new destination and destination are not the same address family") } buf, err := nh.NewDst.Encode() if err != nil { return err } children = append(children, nl.NewRtAttr(unix.RTA_NEWDST, buf)) } if nh.Encap != nil { buf := make([]byte, 2) native.PutUint16(buf, uint16(nh.Encap.Type())) children = append(children, nl.NewRtAttr(unix.RTA_ENCAP_TYPE, buf)) buf, err := nh.Encap.Encode() if err != nil { return err } children = append(children, nl.NewRtAttr(unix.RTA_ENCAP, buf)) } if nh.Via != nil { buf, err := nh.Via.Encode() if err != nil { return err } children = append(children, nl.NewRtAttr(unix.RTA_VIA, buf)) } rtnh.Children = children buf = append(buf, rtnh.Serialize()...) } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_MULTIPATH, buf)) } if route.Table > 0 { if route.Table >= 256 { msg.Table = unix.RT_TABLE_UNSPEC b := make([]byte, 4) native.PutUint32(b, uint32(route.Table)) rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_TABLE, b)) } else { msg.Table = uint8(route.Table) } } if route.Priority > 0 { b := make([]byte, 4) native.PutUint32(b, uint32(route.Priority)) rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_PRIORITY, b)) } if route.Realm > 0 { b := make([]byte, 4) native.PutUint32(b, uint32(route.Realm)) rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_FLOW, b)) } if route.Tos > 0 { msg.Tos = uint8(route.Tos) } if route.Protocol > 0 { msg.Protocol = uint8(route.Protocol) } if route.Type > 0 { msg.Type = uint8(route.Type) } var metrics []*nl.RtAttr if route.MTU > 0 { b := nl.Uint32Attr(uint32(route.MTU)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_MTU, b)) } if route.Window > 0 { b := nl.Uint32Attr(uint32(route.Window)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_WINDOW, b)) } if route.Rtt > 0 { b := nl.Uint32Attr(uint32(route.Rtt)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_RTT, b)) } if route.RttVar > 0 { b := nl.Uint32Attr(uint32(route.RttVar)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_RTTVAR, b)) } if route.Ssthresh > 0 { b := nl.Uint32Attr(uint32(route.Ssthresh)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_SSTHRESH, b)) } if route.Cwnd > 0 { b := nl.Uint32Attr(uint32(route.Cwnd)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_CWND, b)) } if route.AdvMSS > 0 { b := nl.Uint32Attr(uint32(route.AdvMSS)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_ADVMSS, b)) } if route.Reordering > 0 { b := nl.Uint32Attr(uint32(route.Reordering)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_REORDERING, b)) } if route.Hoplimit > 0 { b := nl.Uint32Attr(uint32(route.Hoplimit)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_HOPLIMIT, b)) } if route.InitCwnd > 0 { b := nl.Uint32Attr(uint32(route.InitCwnd)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_INITCWND, b)) } if route.Features > 0 { b := nl.Uint32Attr(uint32(route.Features)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_FEATURES, b)) } if route.RtoMin > 0 { b := nl.Uint32Attr(uint32(route.RtoMin)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_RTO_MIN, b)) } if route.InitRwnd > 0 { b := nl.Uint32Attr(uint32(route.InitRwnd)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_INITRWND, b)) } if route.QuickACK > 0 { b := nl.Uint32Attr(uint32(route.QuickACK)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_QUICKACK, b)) } if route.Congctl != "" { b := nl.ZeroTerminated(route.Congctl) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_CC_ALGO, b)) } if route.FastOpenNoCookie > 0 { b := nl.Uint32Attr(uint32(route.FastOpenNoCookie)) metrics = append(metrics, nl.NewRtAttr(unix.RTAX_FASTOPEN_NO_COOKIE, b)) } if metrics != nil { attr := nl.NewRtAttr(unix.RTA_METRICS, nil) for _, metric := range metrics { attr.AddChild(metric) } rtAttrs = append(rtAttrs, attr) } msg.Flags = uint32(route.Flags) msg.Scope = uint8(route.Scope) // only overwrite family if it was not set in msg if msg.Family == 0 { msg.Family = uint8(family) } req.AddData(msg) for _, attr := range rtAttrs { req.AddData(attr) } if (req.NlMsghdr.Type != unix.RTM_GETROUTE) || (req.NlMsghdr.Type == unix.RTM_GETROUTE && route.LinkIndex > 0) { b := make([]byte, 4) native.PutUint32(b, uint32(route.LinkIndex)) req.AddData(nl.NewRtAttr(unix.RTA_OIF, b)) } return nil } // RouteList gets a list of routes in the system. // Equivalent to: `ip route show`. // The list can be filtered by link and ip family. func RouteList(link Link, family int) ([]Route, error) { return pkgHandle.RouteList(link, family) } // RouteList gets a list of routes in the system. // Equivalent to: `ip route show`. // The list can be filtered by link and ip family. func (h *Handle) RouteList(link Link, family int) ([]Route, error) { routeFilter := &Route{} if link != nil { routeFilter.LinkIndex = link.Attrs().Index return h.RouteListFiltered(family, routeFilter, RT_FILTER_OIF) } return h.RouteListFiltered(family, routeFilter, 0) } // RouteListFiltered gets a list of routes in the system filtered with specified rules. // All rules must be defined in RouteFilter struct func RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { return pkgHandle.RouteListFiltered(family, filter, filterMask) } // RouteListFiltered gets a list of routes in the system filtered with specified rules. // All rules must be defined in RouteFilter struct func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { var res []Route err := h.RouteListFilteredIter(family, filter, filterMask, func(route Route) (cont bool) { res = append(res, route) return true }) if err != nil { return nil, err } return res, nil } // RouteListFilteredIter passes each route that matches the filter to the given iterator func. Iteration continues // until all routes are loaded or the func returns false. func RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { return pkgHandle.RouteListFilteredIter(family, filter, filterMask, f) } func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { req := h.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_DUMP) rtmsg := &nl.RtMsg{} rtmsg.Family = uint8(family) var parseErr error err := h.routeHandleIter(filter, req, rtmsg, func(m []byte) bool { msg := nl.DeserializeRtMsg(m) if family != FAMILY_ALL && msg.Family != uint8(family) { // Ignore routes not matching requested family return true } if msg.Flags&unix.RTM_F_CLONED != 0 { // Ignore cloned routes return true } if msg.Table != unix.RT_TABLE_MAIN { if filter == nil || filterMask&RT_FILTER_TABLE == 0 { // Ignore non-main tables return true } } route, err := deserializeRoute(m) if err != nil { parseErr = err return false } if filter != nil { switch { case filterMask&RT_FILTER_TABLE != 0 && filter.Table != unix.RT_TABLE_UNSPEC && route.Table != filter.Table: return true case filterMask&RT_FILTER_PROTOCOL != 0 && route.Protocol != filter.Protocol: return true case filterMask&RT_FILTER_SCOPE != 0 && route.Scope != filter.Scope: return true case filterMask&RT_FILTER_TYPE != 0 && route.Type != filter.Type: return true case filterMask&RT_FILTER_TOS != 0 && route.Tos != filter.Tos: return true case filterMask&RT_FILTER_REALM != 0 && route.Realm != filter.Realm: return true case filterMask&RT_FILTER_OIF != 0 && route.LinkIndex != filter.LinkIndex: return true case filterMask&RT_FILTER_IIF != 0 && route.ILinkIndex != filter.ILinkIndex: return true case filterMask&RT_FILTER_GW != 0 && !route.Gw.Equal(filter.Gw): return true case filterMask&RT_FILTER_SRC != 0 && !route.Src.Equal(filter.Src): return true case filterMask&RT_FILTER_DST != 0: if filter.MPLSDst == nil || route.MPLSDst == nil || (*filter.MPLSDst) != (*route.MPLSDst) { if filter.Dst == nil { filter.Dst = genZeroIPNet(family) } if !ipNetEqual(route.Dst, filter.Dst) { return true } } case filterMask&RT_FILTER_HOPLIMIT != 0 && route.Hoplimit != filter.Hoplimit: return true } } return f(route) }) if err != nil { return err } if parseErr != nil { return parseErr } return nil } // deserializeRoute decodes a binary netlink message into a Route struct func deserializeRoute(m []byte) (Route, error) { msg := nl.DeserializeRtMsg(m) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return Route{}, err } route := Route{ Scope: Scope(msg.Scope), Protocol: RouteProtocol(int(msg.Protocol)), Table: int(msg.Table), Type: int(msg.Type), Tos: int(msg.Tos), Flags: int(msg.Flags), Family: int(msg.Family), } var encap, encapType syscall.NetlinkRouteAttr for _, attr := range attrs { switch attr.Attr.Type { case unix.RTA_GATEWAY: route.Gw = net.IP(attr.Value) case unix.RTA_PREFSRC: route.Src = net.IP(attr.Value) case unix.RTA_DST: if msg.Family == nl.FAMILY_MPLS { stack := nl.DecodeMPLSStack(attr.Value) if len(stack) == 0 || len(stack) > 1 { return route, fmt.Errorf("invalid MPLS RTA_DST") } route.MPLSDst = &stack[0] } else { route.Dst = &net.IPNet{ IP: attr.Value, Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)), } } case unix.RTA_OIF: route.LinkIndex = int(native.Uint32(attr.Value[0:4])) case unix.RTA_IIF: route.ILinkIndex = int(native.Uint32(attr.Value[0:4])) case unix.RTA_PRIORITY: route.Priority = int(native.Uint32(attr.Value[0:4])) case unix.RTA_FLOW: route.Realm = int(native.Uint32(attr.Value[0:4])) case unix.RTA_TABLE: route.Table = int(native.Uint32(attr.Value[0:4])) case unix.RTA_MULTIPATH: parseRtNexthop := func(value []byte) (*NexthopInfo, []byte, error) { if len(value) < unix.SizeofRtNexthop { return nil, nil, fmt.Errorf("lack of bytes") } nh := nl.DeserializeRtNexthop(value) if len(value) < int(nh.RtNexthop.Len) { return nil, nil, fmt.Errorf("lack of bytes") } info := &NexthopInfo{ LinkIndex: int(nh.RtNexthop.Ifindex), Hops: int(nh.RtNexthop.Hops), Flags: int(nh.RtNexthop.Flags), } attrs, err := nl.ParseRouteAttr(value[unix.SizeofRtNexthop:int(nh.RtNexthop.Len)]) if err != nil { return nil, nil, err } var encap, encapType syscall.NetlinkRouteAttr for _, attr := range attrs { switch attr.Attr.Type { case unix.RTA_GATEWAY: info.Gw = net.IP(attr.Value) case unix.RTA_NEWDST: var d Destination switch msg.Family { case nl.FAMILY_MPLS: d = &MPLSDestination{} } if err := d.Decode(attr.Value); err != nil { return nil, nil, err } info.NewDst = d case unix.RTA_ENCAP_TYPE: encapType = attr case unix.RTA_ENCAP: encap = attr case unix.RTA_VIA: d := &Via{} if err := d.Decode(attr.Value); err != nil { return nil, nil, err } info.Via = d } } if len(encap.Value) != 0 && len(encapType.Value) != 0 { typ := int(native.Uint16(encapType.Value[0:2])) var e Encap switch typ { case nl.LWTUNNEL_ENCAP_MPLS: e = &MPLSEncap{} if err := e.Decode(encap.Value); err != nil { return nil, nil, err } } info.Encap = e } return info, value[int(nh.RtNexthop.Len):], nil } rest := attr.Value for len(rest) > 0 { info, buf, err := parseRtNexthop(rest) if err != nil { return route, err } route.MultiPath = append(route.MultiPath, info) rest = buf } case unix.RTA_NEWDST: var d Destination switch msg.Family { case nl.FAMILY_MPLS: d = &MPLSDestination{} } if err := d.Decode(attr.Value); err != nil { return route, err } route.NewDst = d case unix.RTA_VIA: v := &Via{} if err := v.Decode(attr.Value); err != nil { return route, err } route.Via = v case unix.RTA_ENCAP_TYPE: encapType = attr case unix.RTA_ENCAP: encap = attr case unix.RTA_METRICS: metrics, err := nl.ParseRouteAttr(attr.Value) if err != nil { return route, err } for _, metric := range metrics { switch metric.Attr.Type { case unix.RTAX_MTU: route.MTU = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_WINDOW: route.Window = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_RTT: route.Rtt = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_RTTVAR: route.RttVar = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_SSTHRESH: route.Ssthresh = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_CWND: route.Cwnd = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_ADVMSS: route.AdvMSS = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_REORDERING: route.Reordering = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_HOPLIMIT: route.Hoplimit = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_INITCWND: route.InitCwnd = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_FEATURES: route.Features = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_RTO_MIN: route.RtoMin = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_INITRWND: route.InitRwnd = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_QUICKACK: route.QuickACK = int(native.Uint32(metric.Value[0:4])) case unix.RTAX_CC_ALGO: route.Congctl = nl.BytesToString(metric.Value) case unix.RTAX_FASTOPEN_NO_COOKIE: route.FastOpenNoCookie = int(native.Uint32(metric.Value[0:4])) } } } } // Same logic to generate "default" dst with iproute2 implementation if route.Dst == nil { var addLen int var ip net.IP switch msg.Family { case FAMILY_V4: addLen = net.IPv4len ip = net.IPv4zero case FAMILY_V6: addLen = net.IPv6len ip = net.IPv6zero } if addLen != 0 { route.Dst = &net.IPNet{ IP: ip, Mask: net.CIDRMask(int(msg.Dst_len), 8*addLen), } } } if len(encap.Value) != 0 && len(encapType.Value) != 0 { typ := int(native.Uint16(encapType.Value[0:2])) var e Encap switch typ { case nl.LWTUNNEL_ENCAP_MPLS: e = &MPLSEncap{} if err := e.Decode(encap.Value); err != nil { return route, err } case nl.LWTUNNEL_ENCAP_SEG6: e = &SEG6Encap{} if err := e.Decode(encap.Value); err != nil { return route, err } case nl.LWTUNNEL_ENCAP_SEG6_LOCAL: e = &SEG6LocalEncap{} if err := e.Decode(encap.Value); err != nil { return route, err } case nl.LWTUNNEL_ENCAP_BPF: e = &BpfEncap{} if err := e.Decode(encap.Value); err != nil { return route, err } } route.Encap = e } return route, nil } // RouteGetOptions contains a set of options to use with // RouteGetWithOptions type RouteGetOptions struct { Iif string IifIndex int Oif string VrfName string SrcAddr net.IP UID *uint32 Mark uint32 FIBMatch bool } // RouteGetWithOptions gets a route to a specific destination from the host system. // Equivalent to: 'ip route get <> vrf '. func RouteGetWithOptions(destination net.IP, options *RouteGetOptions) ([]Route, error) { return pkgHandle.RouteGetWithOptions(destination, options) } // RouteGet gets a route to a specific destination from the host system. // Equivalent to: 'ip route get'. func RouteGet(destination net.IP) ([]Route, error) { return pkgHandle.RouteGet(destination) } // RouteGetWithOptions gets a route to a specific destination from the host system. // Equivalent to: 'ip route get <> vrf '. func (h *Handle) RouteGetWithOptions(destination net.IP, options *RouteGetOptions) ([]Route, error) { req := h.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_REQUEST) family := nl.GetIPFamily(destination) var destinationData []byte var bitlen uint8 if family == FAMILY_V4 { destinationData = destination.To4() bitlen = 32 } else { destinationData = destination.To16() bitlen = 128 } msg := &nl.RtMsg{} msg.Family = uint8(family) msg.Dst_len = bitlen if options != nil && options.SrcAddr != nil { msg.Src_len = bitlen } msg.Flags = unix.RTM_F_LOOKUP_TABLE if options != nil && options.FIBMatch { msg.Flags |= unix.RTM_F_FIB_MATCH } req.AddData(msg) rtaDst := nl.NewRtAttr(unix.RTA_DST, destinationData) req.AddData(rtaDst) if options != nil { if options.VrfName != "" { link, err := h.LinkByName(options.VrfName) if err != nil { return nil, err } b := make([]byte, 4) native.PutUint32(b, uint32(link.Attrs().Index)) req.AddData(nl.NewRtAttr(unix.RTA_OIF, b)) } iifIndex := 0 if len(options.Iif) > 0 { link, err := h.LinkByName(options.Iif) if err != nil { return nil, err } iifIndex = link.Attrs().Index } else if options.IifIndex > 0 { iifIndex = options.IifIndex } if iifIndex > 0 { b := make([]byte, 4) native.PutUint32(b, uint32(iifIndex)) req.AddData(nl.NewRtAttr(unix.RTA_IIF, b)) } if len(options.Oif) > 0 { link, err := h.LinkByName(options.Oif) if err != nil { return nil, err } b := make([]byte, 4) native.PutUint32(b, uint32(link.Attrs().Index)) req.AddData(nl.NewRtAttr(unix.RTA_OIF, b)) } if options.SrcAddr != nil { var srcAddr []byte if family == FAMILY_V4 { srcAddr = options.SrcAddr.To4() } else { srcAddr = options.SrcAddr.To16() } req.AddData(nl.NewRtAttr(unix.RTA_SRC, srcAddr)) } if options.UID != nil { uid := *options.UID b := make([]byte, 4) native.PutUint32(b, uid) req.AddData(nl.NewRtAttr(unix.RTA_UID, b)) } if options.Mark > 0 { b := make([]byte, 4) native.PutUint32(b, options.Mark) req.AddData(nl.NewRtAttr(unix.RTA_MARK, b)) } } msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWROUTE) if err != nil { return nil, err } var res []Route for _, m := range msgs { route, err := deserializeRoute(m) if err != nil { return nil, err } res = append(res, route) } return res, nil } // RouteGet gets a route to a specific destination from the host system. // Equivalent to: 'ip route get'. func (h *Handle) RouteGet(destination net.IP) ([]Route, error) { return h.RouteGetWithOptions(destination, nil) } // RouteSubscribe takes a chan down which notifications will be sent // when routes are added or deleted. Close the 'done' chan to stop subscription. func RouteSubscribe(ch chan<- RouteUpdate, done <-chan struct{}) error { return routeSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false) } // RouteSubscribeAt works like RouteSubscribe plus it allows the caller // to choose the network namespace in which to subscribe (ns). func RouteSubscribeAt(ns netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}) error { return routeSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false) } // RouteSubscribeOptions contains a set of options to use with // RouteSubscribeWithOptions. type RouteSubscribeOptions struct { Namespace *netns.NsHandle ErrorCallback func(error) ListExisting bool ReceiveBufferSize int ReceiveBufferForceSize bool ReceiveTimeout *unix.Timeval } // RouteSubscribeWithOptions work like RouteSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error { if options.Namespace == nil { none := netns.None() options.Namespace = &none } return routeSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting, options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize) } func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}, cberr func(error), listExisting bool, rcvbuf int, rcvTimeout *unix.Timeval, rcvbufForce bool) error { s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_ROUTE, unix.RTNLGRP_IPV6_ROUTE) if err != nil { return err } if rcvTimeout != nil { if err := s.SetReceiveTimeout(rcvTimeout); err != nil { return err } } if rcvbuf != 0 { err = s.SetReceiveBufferSize(rcvbuf, rcvbufForce) if err != nil { return err } } if done != nil { go func() { <-done s.Close() }() } if listExisting { req := pkgHandle.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_DUMP) infmsg := nl.NewIfInfomsg(unix.AF_UNSPEC) req.AddData(infmsg) if err := s.Send(req); err != nil { return err } } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { if cberr != nil { cberr(fmt.Errorf("Receive failed: %v", err)) } return } if from.Pid != nl.PidKernel { if cberr != nil { cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)) } continue } for _, m := range msgs { if m.Header.Type == unix.NLMSG_DONE { continue } if m.Header.Type == unix.NLMSG_ERROR { error := int32(native.Uint32(m.Data[0:4])) if error == 0 { continue } if cberr != nil { cberr(fmt.Errorf("error message: %v", syscall.Errno(-error))) } continue } route, err := deserializeRoute(m.Data) if err != nil { if cberr != nil { cberr(err) } continue } ch <- RouteUpdate{ Type: m.Header.Type, NlFlags: m.Header.Flags & (unix.NLM_F_REPLACE | unix.NLM_F_EXCL | unix.NLM_F_CREATE | unix.NLM_F_APPEND), Route: route, } } } }() return nil } func (p RouteProtocol) String() string { switch int(p) { case unix.RTPROT_BABEL: return "babel" case unix.RTPROT_BGP: return "bgp" case unix.RTPROT_BIRD: return "bird" case unix.RTPROT_BOOT: return "boot" case unix.RTPROT_DHCP: return "dhcp" case unix.RTPROT_DNROUTED: return "dnrouted" case unix.RTPROT_EIGRP: return "eigrp" case unix.RTPROT_GATED: return "gated" case unix.RTPROT_ISIS: return "isis" // case unix.RTPROT_KEEPALIVED: // return "keepalived" case unix.RTPROT_KERNEL: return "kernel" case unix.RTPROT_MROUTED: return "mrouted" case unix.RTPROT_MRT: return "mrt" case unix.RTPROT_NTK: return "ntk" case unix.RTPROT_OSPF: return "ospf" case unix.RTPROT_RA: return "ra" case unix.RTPROT_REDIRECT: return "redirect" case unix.RTPROT_RIP: return "rip" case unix.RTPROT_STATIC: return "static" case unix.RTPROT_UNSPEC: return "unspec" case unix.RTPROT_XORP: return "xorp" case unix.RTPROT_ZEBRA: return "zebra" default: return strconv.Itoa(int(p)) } } // genZeroIPNet returns 0.0.0.0/0 or ::/0 for IPv4 or IPv6, otherwise nil func genZeroIPNet(family int) *net.IPNet { var addLen int var ip net.IP switch family { case FAMILY_V4: addLen = net.IPv4len ip = net.IPv4zero case FAMILY_V6: addLen = net.IPv6len ip = net.IPv6zero } if addLen != 0 { return &net.IPNet{ IP: ip, Mask: net.CIDRMask(0, 8*addLen), } } return nil } netlink-1.3.0/route_test.go000066400000000000000000001617621466216277000157330ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "net" "os" "runtime" "strconv" "testing" "time" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) func TestRouteAddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } routes, err = RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not listed properly") } dstIP := net.IPv4(192, 168, 0, 42) routeToDstIP, err := RouteGet(dstIP) if err != nil { t.Fatal(err) } if len(routeToDstIP) == 0 { t.Fatal("Default route not present") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } // add default route test // equiv: default dev lo _, defaultDst, _ := net.ParseCIDR("0.0.0.0/0") route = Route{Dst: defaultDst, LinkIndex: link.Attrs().Index} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Dev default route not listed properly") } if err := RouteDel(&routes[0]); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Dev default route not removed properly") } // equiv: blackhole default route = Route{Dst: defaultDst, Type: unix.RTN_BLACKHOLE, Family: FAMILY_V4} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } t.Logf("%+v", routes) if len(routes) != 1 { t.Fatal("Blackhole default route not listed properly") } if err := RouteDel(&routes[0]); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Blackhole default route not removed properly") } // equiv: prohibit default route = Route{Dst: defaultDst, Type: unix.RTN_PROHIBIT} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Prohibit default route not listed properly") } if err := RouteDel(&routes[0]); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Prohibit default route not removed properly") } } func TestRoute6AddDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // create dummy interface // IPv6 route added to loopback interface will be unreachable la := NewLinkAttrs() la.Name = "dummy_route6" la.TxQLen = 1500 dummy := &Dummy{LinkAttrs: la} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } // get dummy interface link, err := LinkByName("dummy_route6") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // remember number of routes before adding // typically one route (fe80::/64) will be created when dummy_route6 is created routes, err := RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } nroutes := len(routes) // add a gateway route dst := &net.IPNet{ IP: net.ParseIP("2001:db8::0"), Mask: net.CIDRMask(64, 128), } route := Route{LinkIndex: link.Attrs().Index, Dst: dst} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes+1 { t.Fatal("Route not added properly") } dstIP := net.ParseIP("2001:db8::1") routeToDstIP, err := RouteGet(dstIP) if err != nil { t.Fatal(err) } // cleanup route if len(routeToDstIP) == 0 { t.Fatal("Route not present") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes { t.Fatal("Route not removed properly") } // add a default link route _, defaultDst, _ := net.ParseCIDR("::/0") route = Route{LinkIndex: link.Attrs().Index, Dst: defaultDst} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes+1 { t.Fatal("Default route not added properly") } // add a default link route for _, route := range routes { if route.Dst.String() == defaultDst.String() { if err := RouteDel(&route); err != nil { t.Fatal(err) } } } routes, err = RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes { t.Fatal("Default route not removed properly") } // add blackhole default link route routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } nroutes = len(routes) route = Route{Type: unix.RTN_BLACKHOLE, Dst: defaultDst} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes+1 { t.Fatal("Blackhole default route not added properly") } // add blackhole default link route for _, route := range routes { if ipNetEqual(route.Dst, defaultDst) { if err := RouteDel(&route); err != nil { t.Fatal(err) } } } routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes { t.Fatal("Blackhole default route not removed properly") } // add prohibit default link route routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } nroutes = len(routes) route = Route{Type: unix.RTN_BLACKHOLE, Dst: defaultDst} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes+1 { t.Fatal("Prohibit default route not added properly") } // add prohibit default link route for _, route := range routes { if ipNetEqual(route.Dst, defaultDst) { if err := RouteDel(&route); err != nil { t.Fatal(err) } } } routes, err = RouteList(nil, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != nroutes { t.Fatal("Prohibit default route not removed properly") } // cleanup dummy interface created for the test if err := LinkDel(link); err != nil { t.Fatal(err) } } func TestRouteChange(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteChange(&route); err == nil { t.Fatal("Route added while it should fail") } if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } ip = net.IPv4(127, 1, 1, 2) route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteChange(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 || !routes[0].Src.Equal(ip) { t.Fatal("Route not changed properly") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteReplace(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } ip = net.IPv4(127, 1, 1, 2) route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteReplace(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 || !routes[0].Src.Equal(ip) { t.Fatal("Route not replaced properly") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteAppend(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } ip = net.IPv4(127, 1, 1, 2) route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAppend(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 2 || !routes[1].Src.Equal(ip) { t.Fatal("Route not append properly") } if err := RouteDel(&routes[0]); err != nil { t.Fatal(err) } if err := RouteDel(&routes[1]); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteAddIncomplete(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } route := Route{LinkIndex: link.Attrs().Index} if err := RouteAdd(&route); err == nil { t.Fatal("Adding incomplete route should fail") } } // expectRouteUpdate returns whether the expected updated is received within one minute. func expectRouteUpdate(ch <-chan RouteUpdate, t, f uint16, dst net.IP) bool { for { timeout := time.After(time.Minute) select { case update := <-ch: if update.Type == t && update.NlFlags == f && update.Route.Dst != nil && update.Route.Dst.IP.Equal(dst) { return true } case <-timeout: return false } } } func TestRouteSubscribe(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan RouteUpdate) done := make(chan struct{}) defer close(done) if err := RouteSubscribe(ch, done); err != nil { t.Fatal(err) } // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAdd(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) { t.Fatal("Add update not received as expected") } if err := RouteDel(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) { t.Fatal("Del update not received as expected") } } func TestRouteSubscribeWithOptions(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() ch := make(chan RouteUpdate) done := make(chan struct{}) defer close(done) var lastError error defer func() { if lastError != nil { t.Fatalf("Fatal error received during subscription: %v", lastError) } }() if err := RouteSubscribeWithOptions(ch, done, RouteSubscribeOptions{ ErrorCallback: func(err error) { lastError = err }, }); err != nil { t.Fatal(err) } // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 1, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := RouteAdd(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) { t.Fatal("Add update not received as expected") } } func TestRouteSubscribeAt(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() // Subscribe for Route events on the custom netns ch := make(chan RouteUpdate) done := make(chan struct{}) defer close(done) if err := RouteSubscribeAt(newNs, ch, done); err != nil { t.Fatal(err) } // get loopback interface link, err := nh.LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = nh.LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 169, 0, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 100, 1, 1) route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := nh.RouteAdd(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) { t.Fatal("Add update not received as expected") } if err := nh.RouteDel(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) { t.Fatal("Del update not received as expected") } } func TestRouteSubscribeListExisting(t *testing.T) { skipUnlessRoot(t) // Create an handle on a custom netns newNs, err := netns.New() if err != nil { t.Fatal(err) } defer newNs.Close() nh, err := NewHandleAt(newNs) if err != nil { t.Fatal(err) } defer nh.Close() // get loopback interface link, err := nh.LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = nh.LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route before subscribing dst10 := &net.IPNet{ IP: net.IPv4(10, 10, 10, 0), Mask: net.CIDRMask(24, 32), } ip := net.IPv4(127, 100, 1, 1) route10 := Route{LinkIndex: link.Attrs().Index, Dst: dst10, Src: ip} if err := nh.RouteAdd(&route10); err != nil { t.Fatal(err) } // Subscribe for Route events including existing routes ch := make(chan RouteUpdate) done := make(chan struct{}) defer close(done) if err := RouteSubscribeWithOptions(ch, done, RouteSubscribeOptions{ Namespace: &newNs, ListExisting: true}, ); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, 0, dst10.IP) { t.Fatal("Existing add update not received as expected") } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 169, 0, 0), Mask: net.CIDRMask(24, 32), } route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip} if err := nh.RouteAdd(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) { t.Fatal("Add update not received as expected") } if err := nh.RouteDel(&route); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) { t.Fatal("Del update not received as expected") } if err := nh.RouteDel(&route10); err != nil { t.Fatal(err) } if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst10.IP) { t.Fatal("Del update not received as expected") } } func TestRouteFilterAllTables(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), } tables := []int{1000, 1001, 1002} src := net.IPv4(127, 3, 3, 3) for _, table := range tables { route := Route{ LinkIndex: link.Attrs().Index, Dst: dst, Src: src, Scope: unix.RT_SCOPE_LINK, Priority: 13, Table: table, Type: unix.RTN_UNICAST, Tos: 12, Hoplimit: 100, Realm: 328, } if err := RouteAdd(&route); err != nil { t.Fatal(err) } } routes, err := RouteListFiltered(FAMILY_V4, &Route{ Dst: dst, Src: src, Scope: unix.RT_SCOPE_LINK, Table: unix.RT_TABLE_UNSPEC, Type: unix.RTN_UNICAST, Tos: 12, Hoplimit: 100, Realm: 328, }, RT_FILTER_DST|RT_FILTER_SRC|RT_FILTER_SCOPE|RT_FILTER_TABLE|RT_FILTER_TYPE|RT_FILTER_TOS|RT_FILTER_HOPLIMIT|RT_FILTER_REALM) if err != nil { t.Fatal(err) } if len(routes) != 3 { t.Fatal("Routes not added properly") } for _, route := range routes { if route.Scope != unix.RT_SCOPE_LINK { t.Fatal("Invalid Scope. Route not added properly") } if route.Priority != 13 { t.Fatal("Invalid Priority. Route not added properly") } if !tableIDIn(tables, route.Table) { t.Fatalf("Invalid Table %d. Route not added properly", route.Table) } if route.Type != unix.RTN_UNICAST { t.Fatal("Invalid Type. Route not added properly") } if route.Tos != 12 { t.Fatal("Invalid Tos. Route not added properly") } if route.Hoplimit != 100 { t.Fatal("Invalid Hoplimit. Route not added properly") } if route.Realm != 328 { t.Fatal("Invalid Realm. Route not added properly") } } } func TestRouteFilterByFamily(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() const table int = 999 // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a IPv4 gateway route dst4 := &net.IPNet{ IP: net.IPv4(2, 2, 0, 0), Mask: net.CIDRMask(24, 32), } route4 := Route{LinkIndex: link.Attrs().Index, Dst: dst4, Table: table} if err := RouteAdd(&route4); err != nil { t.Fatal(err) } // add a IPv6 gateway route dst6 := &net.IPNet{ IP: net.ParseIP("2001:db9::0"), Mask: net.CIDRMask(64, 128), } route6 := Route{LinkIndex: link.Attrs().Index, Dst: dst6, Table: table} if err := RouteAdd(&route6); err != nil { t.Fatal(err) } // Get routes for both families routes_all, err := RouteListFiltered(FAMILY_ALL, &Route{Table: table}, RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes_all) != 2 { t.Fatal("Filtering by FAMILY_ALL doesn't find two routes") } // Get IPv4 route routes_v4, err := RouteListFiltered(FAMILY_V4, &Route{Table: table}, RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes_v4) != 1 { t.Fatal("Filtering by FAMILY_V4 doesn't find one route") } // Get IPv6 route routes_v6, err := RouteListFiltered(FAMILY_V6, &Route{Table: table}, RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes_v6) != 1 { t.Fatal("Filtering by FAMILY_V6 doesn't find one route") } // Get non-existent routes routes_non_existent, err := RouteListFiltered(99, &Route{Table: table}, RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes_non_existent) != 0 { t.Fatal("Filtering by non-existent family find some route") } } func TestRouteFilterIterCanStop(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), } for i := 0; i < 3; i++ { route := Route{ LinkIndex: link.Attrs().Index, Dst: dst, Scope: unix.RT_SCOPE_LINK, Priority: 1 + i, Table: 1000, Type: unix.RTN_UNICAST, } if err := RouteAdd(&route); err != nil { t.Fatal(err) } } var routes []Route err = RouteListFilteredIter(FAMILY_V4, &Route{ Dst: dst, Scope: unix.RT_SCOPE_LINK, Table: 1000, Type: unix.RTN_UNICAST, }, RT_FILTER_TABLE, func(route Route) (cont bool) { routes = append(routes, route) return len(routes) < 2 }) if err != nil { t.Fatal(err) } if len(routes) != 2 { t.Fatal("Unexpected number of iterations") } for _, route := range routes { if route.Scope != unix.RT_SCOPE_LINK { t.Fatal("Invalid Scope. Route not added properly") } if route.Priority < 1 || route.Priority > 3 { t.Fatal("Priority outside expected range. Route not added properly") } if route.Table != 1000 { t.Fatalf("Invalid Table %d. Route not added properly", route.Table) } if route.Type != unix.RTN_UNICAST { t.Fatal("Invalid Type. Route not added properly") } } } func BenchmarkRouteListFilteredNew(b *testing.B) { tearDown := setUpNetlinkTest(b) defer tearDown() link, err := setUpRoutesBench(b) b.ResetTimer() b.ReportAllocs() var routes []Route for i := 0; i < b.N; i++ { routes, err = pkgHandle.RouteListFiltered(FAMILY_V4, &Route{ LinkIndex: link.Attrs().Index, }, RT_FILTER_OIF) if err != nil { b.Fatal(err) } if len(routes) != 65535 { b.Fatal("Incorrect number of routes.", len(routes)) } } runtime.KeepAlive(routes) } func BenchmarkRouteListIter(b *testing.B) { tearDown := setUpNetlinkTest(b) defer tearDown() link, err := setUpRoutesBench(b) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { var routes int err = RouteListFilteredIter(FAMILY_V4, &Route{ LinkIndex: link.Attrs().Index, }, RT_FILTER_OIF, func(route Route) (cont bool) { routes++ return true }) if err != nil { b.Fatal(err) } if routes != 65535 { b.Fatal("Incorrect number of routes.", routes) } } } func setUpRoutesBench(b *testing.B) (Link, error) { // get loopback interface link, err := LinkByName("lo") if err != nil { b.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { b.Fatal(err) } // add a gateway route for i := 0; i < 65535; i++ { dst := &net.IPNet{ IP: net.IPv4(1, 1, byte(i>>8), byte(i&0xff)), Mask: net.CIDRMask(32, 32), } route := Route{ LinkIndex: link.Attrs().Index, Dst: dst, Scope: unix.RT_SCOPE_LINK, Priority: 10, Type: unix.RTN_UNICAST, } if err := RouteAdd(&route); err != nil { b.Fatal(err) } } return link, err } func tableIDIn(ids []int, id int) bool { for _, v := range ids { if v == id { return true } } return false } func TestRouteExtraFields(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), } src := net.IPv4(127, 3, 3, 3) route := Route{ LinkIndex: link.Attrs().Index, Dst: dst, Src: src, Scope: unix.RT_SCOPE_LINK, Priority: 13, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Tos: 12, Hoplimit: 100, Realm: 239, } if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteListFiltered(FAMILY_V4, &Route{ Dst: dst, Src: src, Scope: unix.RT_SCOPE_LINK, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Tos: 12, Hoplimit: 100, Realm: 239, }, RT_FILTER_DST|RT_FILTER_SRC|RT_FILTER_SCOPE|RT_FILTER_TABLE|RT_FILTER_TYPE|RT_FILTER_TOS|RT_FILTER_HOPLIMIT|RT_FILTER_REALM) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } if routes[0].Scope != unix.RT_SCOPE_LINK { t.Fatal("Invalid Scope. Route not added properly") } if routes[0].Priority != 13 { t.Fatal("Invalid Priority. Route not added properly") } if routes[0].Table != unix.RT_TABLE_MAIN { t.Fatal("Invalid Scope. Route not added properly") } if routes[0].Type != unix.RTN_UNICAST { t.Fatal("Invalid Type. Route not added properly") } if routes[0].Tos != 12 { t.Fatal("Invalid Tos. Route not added properly") } if routes[0].Hoplimit != 100 { t.Fatal("Invalid Hoplimit. Route not added properly") } if routes[0].Realm != 239 { t.Fatal("Invalid Realm. Route not added properly") } } func TestRouteMultiPath(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } idx := link.Attrs().Index route := Route{Dst: dst, MultiPath: []*NexthopInfo{{LinkIndex: idx}, {LinkIndex: idx}}} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("MultiPath Route not added properly") } if len(routes[0].MultiPath) != 2 { t.Fatal("MultiPath Route not added properly") } } func TestRouteIifOption(t *testing.T) { skipUnlessRoot(t) runtime.LockOSThread() t.Cleanup(runtime.UnlockOSThread) rootNs, err := netns.GetFromPid(1) if err != nil { t.Fatalf("could not get root ns: %s", err) } t.Cleanup(func() { rootNs.Close() }) rootHdl, err := NewHandleAt(rootNs) if err != nil { t.Fatalf("could not create handle for root ns: %s", err) } t.Cleanup(func() { rootHdl.Close() }) // setup a veth pair across two namespaces // veth1 (2.2.2.3/24) <-> veth2 (2.2.2.4/24) // peer ns for veth pair ns, err := netns.New() if err != nil { t.Fatalf("could not create new ns: %s", err) } t.Cleanup(func() { ns.Close() }) l := &Veth{ LinkAttrs: LinkAttrs{Name: "veth1"}, PeerName: "veth2", PeerNamespace: NsFd(ns), } if err = rootHdl.LinkAdd(l); err != nil { t.Fatalf("could not add veth interface: %s", err) } t.Cleanup(func() { rootHdl.LinkDel(l) }) ve1, err := rootHdl.LinkByName("veth1") if err != nil { t.Fatalf("could not get link veth1: %s", err) } err = rootHdl.AddrAdd(ve1, &Addr{IPNet: &net.IPNet{IP: net.ParseIP("2.2.2.3"), Mask: net.CIDRMask(24, 32)}}) if err != nil { t.Fatalf("could not set address for veth1: %s", err) } nh, err := NewHandleAt(ns) if err != nil { t.Fatalf("could not get handle for ns %+v: %s", ns, err) } t.Cleanup(func() { nh.Close() }) ve2, err := nh.LinkByName("veth2") if err != nil { t.Fatalf("could not get link veth2: %s", err) } err = nh.AddrAdd(ve2, &Addr{IPNet: &net.IPNet{IP: net.ParseIP("2.2.2.4"), Mask: net.CIDRMask(24, 32)}}) if err != nil { t.Fatalf("could set address for veth2: %s", err) } if err = rootHdl.LinkSetUp(ve1); err != nil { t.Fatalf("could not set veth1 up: %s", err) } if err = nh.LinkSetUp(ve2); err != nil { t.Fatalf("could not set veth2 up: %s", err) } err = nh.RouteAdd(&Route{ Dst: &net.IPNet{ IP: net.IPv4zero, Mask: net.CIDRMask(0, 32), }, Gw: net.ParseIP("2.2.2.3"), }) if err != nil { t.Fatalf("could not add default route to ns: %s", err) } // setup finished, now do the actual test _, err = rootHdl.RouteGetWithOptions(net.ParseIP("8.8.8.8"), &RouteGetOptions{ SrcAddr: net.ParseIP("2.2.2.4"), }) if err == nil { t.Fatal("route get should have resulted in error but did not") } testWithOptions := func(opts *RouteGetOptions) { routes, err := rootHdl.RouteGetWithOptions(net.ParseIP("8.8.8.8"), opts) if err != nil { t.Fatalf("could not get route: %s", err) } if len(routes) != 1 { t.Fatalf("did not get exactly one route, routes: %+v", routes) } // should be the default route r, err := rootHdl.RouteGet(net.ParseIP("8.8.8.8")) if err != nil { t.Fatalf("could not get default route for 8.8.8.8: %s", err) } if len(r) != 1 { t.Fatalf("did not get exactly one route, routes: %+v", routes) } if !routes[0].Gw.Equal(r[0].Gw) { t.Fatalf("wrong gateway in route: expected: %s, got: %s", r[0].Gw, routes[0].Gw) } if routes[0].LinkIndex != r[0].LinkIndex { t.Fatalf("wrong link in route: expected: %d, got: %d", r[0].LinkIndex, routes[0].LinkIndex) } } t.Run("with iif", func(t *testing.T) { testWithOptions(&RouteGetOptions{ SrcAddr: net.ParseIP("2.2.2.4"), Iif: "veth1", }) }) t.Run("with iifIndex", func(t *testing.T) { testWithOptions(&RouteGetOptions{ SrcAddr: net.ParseIP("2.2.2.4"), IifIndex: ve1.Attrs().Index, }) }) t.Run("with iif and iifIndex", func(t *testing.T) { testWithOptions(&RouteGetOptions{ SrcAddr: net.ParseIP("2.2.2.4"), Iif: "veth1", IifIndex: ve2.Attrs().Index, // Iif will supersede here }) }) } func TestRouteOifOption(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // setup two interfaces: eth0, eth1 err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}}) if err != nil { t.Fatal(err) } link1, err := LinkByName("eth0") if err != nil { t.Fatal(err) } if err = LinkSetUp(link1); err != nil { t.Fatal(err) } if err = LinkAdd(&Dummy{LinkAttrs{Name: "eth1"}}); err != nil { t.Fatal(err) } link2, err := LinkByName("eth1") if err != nil { t.Fatal(err) } if err = LinkSetUp(link2); err != nil { t.Fatal(err) } // config ip addresses on interfaces addr1 := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 1, 1), Mask: net.CIDRMask(24, 32), }, } if err = AddrAdd(link1, addr1); err != nil { t.Fatal(err) } addr2 := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 2, 1), Mask: net.CIDRMask(24, 32), }, } if err = AddrAdd(link2, addr2); err != nil { t.Fatal(err) } // add default multipath route dst := &net.IPNet{ IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(0, 32), } gw1 := net.IPv4(192, 168, 1, 254) gw2 := net.IPv4(192, 168, 2, 254) route := Route{Dst: dst, MultiPath: []*NexthopInfo{{LinkIndex: link1.Attrs().Index, Gw: gw1}, {LinkIndex: link2.Attrs().Index, Gw: gw2}}} if err := RouteAdd(&route); err != nil { t.Fatal(err) } // check getting route from specified Oif dstIP := net.IPv4(10, 1, 1, 1) routes, err := RouteGetWithOptions(dstIP, &RouteGetOptions{Oif: "eth0"}) if err != nil { t.Fatal(err) } if len(routes) != 1 || routes[0].LinkIndex != link1.Attrs().Index || !routes[0].Gw.Equal(gw1) { t.Fatal("Get route from unmatched interface") } routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Oif: "eth1"}) if err != nil { t.Fatal(err) } if len(routes) != 1 || routes[0].LinkIndex != link2.Attrs().Index || !routes[0].Gw.Equal(gw2) { t.Fatal("Get route from unmatched interface") } } func TestFilterDefaultRoute(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err = LinkSetUp(link); err != nil { t.Fatal(err) } address := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(127, 0, 0, 2), Mask: net.CIDRMask(24, 32), }, } if err = AddrAdd(link, address); err != nil { t.Fatal(err) } // Add default route gw := net.IPv4(127, 0, 0, 2) defaultRoute := Route{ Dst: nil, Gw: gw, } if err := RouteAdd(&defaultRoute); err != nil { t.Fatal(err) } // add an extra route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } extraRoute := Route{ Dst: dst, Gw: gw, } if err := RouteAdd(&extraRoute); err != nil { t.Fatal(err) } var filterTests = []struct { filter *Route mask uint64 expected net.IP }{ { &Route{Dst: nil}, RT_FILTER_DST, gw, }, { &Route{Dst: dst}, RT_FILTER_DST, gw, }, } for _, f := range filterTests { routes, err := RouteListFiltered(FAMILY_V4, f.filter, f.mask) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not filtered properly") } if !routes[0].Gw.Equal(gw) { t.Fatal("Unexpected Gateway") } } } func TestMPLSRouteAddDel(t *testing.T) { tearDown := setUpMPLSNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } mplsDst := 100 route := Route{ LinkIndex: link.Attrs().Index, MPLSDst: &mplsDst, NewDst: &MPLSDestination{ Labels: []int{200, 300}, }, } if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_MPLS) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_MPLS) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestIP6tnlRouteAddDel(t *testing.T) { _, err := RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } _, dst, err := net.ParseCIDR("192.168.99.0/24") if err != nil { t.Fatalf("cannot parse destination prefix: %v", err) } encap := IP6tnlEncap{ Dst: net.ParseIP("2001:db8::"), Src: net.ParseIP("::"), } route := &Route{ LinkIndex: link.Attrs().Index, Dst: dst, Encap: &encap, } if err := RouteAdd(route); err != nil { t.Fatalf("Cannot add route: %v", err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } if err := RouteDel(route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteEqual(t *testing.T) { mplsDst := 100 seg6encap := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_ENCAP} seg6encap.Segments = []net.IP{net.ParseIP("fc00:a000::11")} cases := []Route{ { Dst: nil, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Gw: net.IPv4(1, 1, 1, 1), }, { ILinkIndex: 21, LinkIndex: 20, Dst: nil, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Protocol: 20, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Priority: 20, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Type: 20, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Table: 200, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Tos: 1, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Hoplimit: 1, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Realm: 29, Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 20, Dst: nil, Flags: int(FLAG_ONLINK), Gw: net.IPv4(1, 1, 1, 1), }, { LinkIndex: 10, Dst: &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), }, Src: net.IPv4(127, 1, 1, 1), }, { LinkIndex: 10, Scope: unix.RT_SCOPE_LINK, Dst: &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), }, Src: net.IPv4(127, 1, 1, 1), }, { LinkIndex: 3, Dst: &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), }, Src: net.IPv4(127, 3, 3, 3), Scope: unix.RT_SCOPE_LINK, Priority: 13, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Tos: 12, }, { LinkIndex: 3, Dst: &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), }, Src: net.IPv4(127, 3, 3, 3), Scope: unix.RT_SCOPE_LINK, Priority: 13, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Hoplimit: 100, }, { LinkIndex: 3, Dst: &net.IPNet{ IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(32, 32), }, Src: net.IPv4(127, 3, 3, 3), Scope: unix.RT_SCOPE_LINK, Priority: 13, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Realm: 129, }, { LinkIndex: 10, MPLSDst: &mplsDst, NewDst: &MPLSDestination{ Labels: []int{200, 300}, }, }, { Dst: nil, Gw: net.IPv4(1, 1, 1, 1), Encap: &MPLSEncap{ Labels: []int{100}, }, }, { LinkIndex: 10, Dst: &net.IPNet{ IP: net.IPv4(10, 0, 0, 102), Mask: net.CIDRMask(32, 32), }, Encap: seg6encap, }, { Dst: nil, MultiPath: []*NexthopInfo{{LinkIndex: 10}, {LinkIndex: 20}}, }, { Dst: nil, MultiPath: []*NexthopInfo{{ LinkIndex: 10, Gw: net.IPv4(1, 1, 1, 1), }, {LinkIndex: 20}}, }, { Dst: nil, MultiPath: []*NexthopInfo{{ LinkIndex: 10, Gw: net.IPv4(1, 1, 1, 1), Encap: &MPLSEncap{ Labels: []int{100}, }, }, {LinkIndex: 20}}, }, { Dst: nil, MultiPath: []*NexthopInfo{{ LinkIndex: 10, NewDst: &MPLSDestination{ Labels: []int{200, 300}, }, }, {LinkIndex: 20}}, }, { Dst: nil, MultiPath: []*NexthopInfo{{ LinkIndex: 10, Encap: seg6encap, }, {LinkIndex: 20}}, }, } for i1 := range cases { for i2 := range cases { got := cases[i1].Equal(cases[i2]) expected := i1 == i2 if got != expected { t.Errorf("Equal(%q,%q) == %s but expected %s", cases[i1], cases[i2], strconv.FormatBool(got), strconv.FormatBool(expected)) } } } } func TestIPNetEqual(t *testing.T) { cases := []string{ "1.1.1.1/24", "1.1.1.0/24", "1.1.1.1/32", "0.0.0.0/0", "0.0.0.0/14", "2001:db8::/32", "2001:db8::/128", "2001:db8::caff/32", "2001:db8::caff/128", "", } for _, c1 := range cases { var n1 *net.IPNet if c1 != "" { var i1 net.IP var err1 error i1, n1, err1 = net.ParseCIDR(c1) if err1 != nil { panic(err1) } n1.IP = i1 } for _, c2 := range cases { var n2 *net.IPNet if c2 != "" { var i2 net.IP var err2 error i2, n2, err2 = net.ParseCIDR(c2) if err2 != nil { panic(err2) } n2.IP = i2 } got := ipNetEqual(n1, n2) expected := c1 == c2 if got != expected { t.Errorf("IPNetEqual(%q,%q) == %s but expected %s", c1, c2, strconv.FormatBool(got), strconv.FormatBool(expected)) } } } } func TestSEG6LocalEqual(t *testing.T) { // Different attributes exists in different Actions. For example, Action // SEG6_LOCAL_ACTION_END_X has In6Addr, SEG6_LOCAL_ACTION_END_T has Table etc. segs := []net.IP{net.ParseIP("fc00:a000::11")} // set flags for each actions. var flags_end [nl.SEG6_LOCAL_MAX]bool flags_end[nl.SEG6_LOCAL_ACTION] = true var flags_end_x [nl.SEG6_LOCAL_MAX]bool flags_end_x[nl.SEG6_LOCAL_ACTION] = true flags_end_x[nl.SEG6_LOCAL_NH6] = true var flags_end_t [nl.SEG6_LOCAL_MAX]bool flags_end_t[nl.SEG6_LOCAL_ACTION] = true flags_end_t[nl.SEG6_LOCAL_TABLE] = true var flags_end_dx2 [nl.SEG6_LOCAL_MAX]bool flags_end_dx2[nl.SEG6_LOCAL_ACTION] = true flags_end_dx2[nl.SEG6_LOCAL_OIF] = true var flags_end_dx6 [nl.SEG6_LOCAL_MAX]bool flags_end_dx6[nl.SEG6_LOCAL_ACTION] = true flags_end_dx6[nl.SEG6_LOCAL_NH6] = true var flags_end_dx4 [nl.SEG6_LOCAL_MAX]bool flags_end_dx4[nl.SEG6_LOCAL_ACTION] = true flags_end_dx4[nl.SEG6_LOCAL_NH4] = true var flags_end_dt6 [nl.SEG6_LOCAL_MAX]bool flags_end_dt6[nl.SEG6_LOCAL_ACTION] = true flags_end_dt6[nl.SEG6_LOCAL_TABLE] = true var flags_end_dt4 [nl.SEG6_LOCAL_MAX]bool flags_end_dt4[nl.SEG6_LOCAL_ACTION] = true flags_end_dt4[nl.SEG6_LOCAL_TABLE] = true var flags_end_b6 [nl.SEG6_LOCAL_MAX]bool flags_end_b6[nl.SEG6_LOCAL_ACTION] = true flags_end_b6[nl.SEG6_LOCAL_SRH] = true var flags_end_b6_encaps [nl.SEG6_LOCAL_MAX]bool flags_end_b6_encaps[nl.SEG6_LOCAL_ACTION] = true flags_end_b6_encaps[nl.SEG6_LOCAL_SRH] = true var flags_end_bpf [nl.SEG6_LOCAL_MAX]bool flags_end_bpf[nl.SEG6_LOCAL_ACTION] = true flags_end_bpf[nl.SEG6_LOCAL_BPF] = true cases := []SEG6LocalEncap{ { Flags: flags_end, Action: nl.SEG6_LOCAL_ACTION_END, }, { Flags: flags_end_x, Action: nl.SEG6_LOCAL_ACTION_END_X, In6Addr: net.ParseIP("2001:db8::1"), }, { Flags: flags_end_t, Action: nl.SEG6_LOCAL_ACTION_END_T, Table: 10, }, { Flags: flags_end_dx2, Action: nl.SEG6_LOCAL_ACTION_END_DX2, Oif: 20, }, { Flags: flags_end_dx6, Action: nl.SEG6_LOCAL_ACTION_END_DX6, In6Addr: net.ParseIP("2001:db8::1"), }, { Flags: flags_end_dx4, Action: nl.SEG6_LOCAL_ACTION_END_DX4, InAddr: net.IPv4(192, 168, 10, 10), }, { Flags: flags_end_dt6, Action: nl.SEG6_LOCAL_ACTION_END_DT6, Table: 30, }, { Flags: flags_end_dt4, Action: nl.SEG6_LOCAL_ACTION_END_DT4, Table: 40, }, { Flags: flags_end_b6, Action: nl.SEG6_LOCAL_ACTION_END_B6, Segments: segs, }, { Flags: flags_end_b6_encaps, Action: nl.SEG6_LOCAL_ACTION_END_B6_ENCAPS, Segments: segs, }, } // SEG6_LOCAL_ACTION_END_BPF endBpf := SEG6LocalEncap{ Flags: flags_end_bpf, Action: nl.SEG6_LOCAL_ACTION_END_BPF, } _ = endBpf.SetProg(1, "firewall") cases = append(cases, endBpf) for i1 := range cases { for i2 := range cases { got := cases[i1].Equal(&cases[i2]) expected := i1 == i2 if got != expected { t.Errorf("Equal(%v,%v) == %s but expected %s", cases[i1], cases[i2], strconv.FormatBool(got), strconv.FormatBool(expected)) } } } } func TestSEG6RouteAddDel(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Fails in CI with: route_test.go:*: Invalid Type. SEG6_IPTUN_MODE_INLINE routes not added properly") } // add/del routes with LWTUNNEL_SEG6 to/from loopback interface. // Test both seg6 modes: encap (IPv4) & inline (IPv6). tearDown := setUpSEG6NetlinkTest(t) defer tearDown() // get loopback interface and bring it up link, err := LinkByName("lo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } dst1 := &net.IPNet{ // INLINE mode must be IPv6 route IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(128, 128), } dst2 := &net.IPNet{ IP: net.IPv4(10, 0, 0, 102), Mask: net.CIDRMask(32, 32), } var s1, s2 []net.IP s1 = append(s1, net.ParseIP("::")) // inline requires "::" s1 = append(s1, net.ParseIP("fc00:a000::12")) s1 = append(s1, net.ParseIP("fc00:a000::11")) s2 = append(s2, net.ParseIP("fc00:a000::22")) s2 = append(s2, net.ParseIP("fc00:a000::21")) e1 := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_INLINE} e2 := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_ENCAP} e1.Segments = s1 e2.Segments = s2 route1 := Route{LinkIndex: link.Attrs().Index, Dst: dst1, Encap: e1} route2 := Route{LinkIndex: link.Attrs().Index, Dst: dst2, Encap: e2} // Add SEG6 routes if err := RouteAdd(&route1); err != nil { t.Fatal(err) } if err := RouteAdd(&route2); err != nil { t.Fatal(err) } // SEG6_IPTUN_MODE_INLINE routes, err := RouteList(link, FAMILY_V6) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("SEG6 routes not added properly") } for _, route := range routes { if route.Encap == nil || route.Encap.Type() != nl.LWTUNNEL_ENCAP_SEG6 { t.Fatal("Invalid Type. SEG6_IPTUN_MODE_INLINE routes not added properly") } } // SEG6_IPTUN_MODE_ENCAP routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("SEG6 routes not added properly") } for _, route := range routes { if route.Encap.Type() != nl.LWTUNNEL_ENCAP_SEG6 { t.Fatal("Invalid Type. SEG6_IPTUN_MODE_ENCAP routes not added properly") } } // Del (remove) SEG6 routes if err := RouteDel(&route1); err != nil { t.Fatal(err) } if err := RouteDel(&route2); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("SEG6 routes not removed properly") } } // add/del routes with LWTUNNEL_ENCAP_SEG6_LOCAL to/from dummy interface. func TestSEG6LocalRoute6AddDel(t *testing.T) { minKernelRequired(t, 4, 14) tearDown := setUpSEG6NetlinkTest(t) defer tearDown() // create dummy interface // IPv6 route added to loopback interface will be unreachable la := NewLinkAttrs() la.Name = "dummy_route6" la.TxQLen = 1500 dummy := &Dummy{LinkAttrs: la} if err := LinkAdd(dummy); err != nil { t.Fatal(err) } // get dummy interface and bring it up link, err := LinkByName("dummy_route6") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } dst1 := &net.IPNet{ IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(128, 128), } // Create Route including Action SEG6_LOCAL_ACTION_END_B6. // Could be any Action but thought better to have seg list. var s1 []net.IP s1 = append(s1, net.ParseIP("fc00:a000::12")) s1 = append(s1, net.ParseIP("fc00:a000::11")) var flags_end_b6_encaps [nl.SEG6_LOCAL_MAX]bool flags_end_b6_encaps[nl.SEG6_LOCAL_ACTION] = true flags_end_b6_encaps[nl.SEG6_LOCAL_SRH] = true e1 := &SEG6LocalEncap{ Flags: flags_end_b6_encaps, Action: nl.SEG6_LOCAL_ACTION_END_B6, Segments: s1, } route1 := Route{LinkIndex: link.Attrs().Index, Dst: dst1, Encap: e1} // Add SEG6Local routes if err := RouteAdd(&route1); err != nil { t.Fatal(err) } // typically one route (fe80::/64) will be created when dummy_route6 is created. // Thus you cannot use RouteList() to find the route entry just added. // Lookup route and confirm it's SEG6Local route just added. routesFound, err := RouteGet(dst1.IP) if err != nil { t.Fatal(err) } if len(routesFound) != 1 { // should only find 1 route entry t.Fatal("SEG6Local route not added correctly") } if !e1.Equal(routesFound[0].Encap) { t.Fatal("Encap does not match the original SEG6LocalEncap") } // Del SEG6Local routes if err := RouteDel(&route1); err != nil { t.Fatal(err) } // Confirm route is deleted. if _, err = RouteGet(dst1.IP); err == nil { t.Fatal("SEG6Local route still exists.") } // cleanup dummy interface created for the test if err := LinkDel(link); err != nil { t.Fatal(err) } } func TestBpfEncap(t *testing.T) { tCase := &BpfEncap{} if err := tCase.SetProg(nl.LWT_BPF_IN, 0, "test_in"); err == nil { t.Fatal("BpfEncap: inserting invalid FD did not return error") } if err := tCase.SetProg(nl.LWT_BPF_XMIT_HEADROOM, 23, "test_nout"); err == nil { t.Fatal("BpfEncap: inserting invalid mode did not return error") } if err := tCase.SetProg(nl.LWT_BPF_XMIT, 12, "test_xmit"); err != nil { t.Fatal("BpfEncap: inserting valid program option returned error") } if err := tCase.SetXmitHeadroom(12); err != nil { t.Fatal("BpfEncap: inserting valid headroom returned error") } if err := tCase.SetXmitHeadroom(nl.LWT_BPF_MAX_HEADROOM + 1); err == nil { t.Fatal("BpfEncap: inserting invalid headroom did not return error") } tCase = &BpfEncap{} expected := &BpfEncap{ progs: [nl.LWT_BPF_MAX]bpfObj{ 1: { progName: "test_in[fd:10]", progFd: 10, }, 2: { progName: "test_out[fd:11]", progFd: 11, }, 3: { progName: "test_xmit[fd:21]", progFd: 21, }, }, headroom: 128, } _ = tCase.SetProg(1, 10, "test_in") _ = tCase.SetProg(2, 11, "test_out") _ = tCase.SetProg(3, 21, "test_xmit") _ = tCase.SetXmitHeadroom(128) if !tCase.Equal(expected) { t.Fatal("BpfEncap: equal comparison failed") } _ = tCase.SetProg(3, 21, "test2_xmit") if tCase.Equal(expected) { t.Fatal("BpfEncap: equal comparison succeeded when attributes differ") } } func TestMTURouteAddDel(t *testing.T) { _, err := RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } tearDown := setUpNetlinkTest(t) defer tearDown() // get loopback interface link, err := LinkByName("lo") if err != nil { t.Fatal(err) } // bring the interface up if err := LinkSetUp(link); err != nil { t.Fatal(err) } // add a gateway route dst := &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), } route := Route{LinkIndex: link.Attrs().Index, Dst: dst, MTU: 500} if err := RouteAdd(&route); err != nil { t.Fatal(err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } if route.MTU != routes[0].MTU { t.Fatal("Route mtu not set properly") } if err := RouteDel(&route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteViaAddDel(t *testing.T) { minKernelRequired(t, 5, 4) tearDown := setUpNetlinkTest(t) defer tearDown() _, err := RouteList(nil, FAMILY_V4) if err != nil { t.Fatal(err) } link, err := LinkByName("lo") if err != nil { t.Fatal(err) } if err := LinkSetUp(link); err != nil { t.Fatal(err) } route := &Route{ LinkIndex: link.Attrs().Index, Dst: &net.IPNet{ IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(24, 32), }, MultiPath: []*NexthopInfo{ { LinkIndex: link.Attrs().Index, Via: &Via{ AddrFamily: FAMILY_V6, Addr: net.ParseIP("2001::1"), }, }, }, } if err := RouteAdd(route); err != nil { t.Fatalf("route: %v, err: %v", route, err) } routes, err := RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatal("Route not added properly") } got := routes[0].Via want := route.MultiPath[0].Via if !want.Equal(got) { t.Fatalf("Route Via attribute does not match; got: %s, want: %s", got, want) } if err := RouteDel(route); err != nil { t.Fatal(err) } routes, err = RouteList(link, FAMILY_V4) if err != nil { t.Fatal(err) } if len(routes) != 0 { t.Fatal("Route not removed properly") } } func TestRouteUIDOption(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // setup eth0 so that network is reachable err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}}) if err != nil { t.Fatal(err) } link, err := LinkByName("eth0") if err != nil { t.Fatal(err) } if err = LinkSetUp(link); err != nil { t.Fatal(err) } addr := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 1, 1), Mask: net.CIDRMask(16, 32), }, } if err = AddrAdd(link, addr); err != nil { t.Fatal(err) } // a table different than unix.RT_TABLE_MAIN testtable := 1000 gw1 := net.IPv4(192, 168, 1, 254) gw2 := net.IPv4(192, 168, 2, 254) // add default route via gw1 (in main route table by default) defaultRouteMain := Route{ Dst: nil, Gw: gw1, } if err := RouteAdd(&defaultRouteMain); err != nil { t.Fatal(err) } // add default route via gw2 in test route table defaultRouteTest := Route{ Dst: nil, Gw: gw2, Table: testtable, } if err := RouteAdd(&defaultRouteTest); err != nil { t.Fatal(err) } // check the routes are in different tables routes, err := RouteListFiltered(FAMILY_V4, &Route{ Dst: nil, Table: unix.RT_TABLE_UNSPEC, }, RT_FILTER_DST|RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes) != 2 || routes[0].Table == routes[1].Table { t.Fatal("Routes not added properly") } // add a rule that uidrange match should result in route lookup of test table for uid other than current // current uid is 0 due to skipUnlessRoot() var uid uint32 = 1000 rule := NewRule() rule.UIDRange = NewRuleUIDRange(uid, uid) rule.Table = testtable if err := RuleAdd(rule); err != nil { t.Fatal(err) } dstIP := net.IPv4(10, 1, 1, 1) // check getting route without UID option routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{UID: nil}) if err != nil { t.Fatal(err) } // current uid is outside uidrange; rule does not apply; lookup main table if len(routes) != 1 || !routes[0].Gw.Equal(gw1) { t.Fatal(routes) } // check getting route with UID option routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{UID: &uid}) if err != nil { t.Fatal(err) } // option uid is within uidrange; rule applies; lookup test table if len(routes) != 1 || !routes[0].Gw.Equal(gw2) { t.Fatal(routes) } } func TestRouteFWMarkOption(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() // setup eth0 so that network is reachable err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}}) if err != nil { t.Fatal(err) } link, err := LinkByName("eth0") if err != nil { t.Fatal(err) } if err = LinkSetUp(link); err != nil { t.Fatal(err) } addr := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 1, 1), Mask: net.CIDRMask(16, 32), }, } if err = AddrAdd(link, addr); err != nil { t.Fatal(err) } // a table different than unix.RT_TABLE_MAIN testTable0 := 254 testTable1 := 1000 testTable2 := 1001 gw0 := net.IPv4(192, 168, 1, 254) gw1 := net.IPv4(192, 168, 2, 254) gw2 := net.IPv4(192, 168, 3, 254) // add default route via gw0 (in main route table by default) defaultRouteMain := Route{ Dst: nil, Gw: gw0, Table: testTable0, } if err := RouteAdd(&defaultRouteMain); err != nil { t.Fatal(err) } // add default route via gw1 in test route table defaultRouteTest1 := Route{ Dst: nil, Gw: gw1, Table: testTable1, } if err := RouteAdd(&defaultRouteTest1); err != nil { t.Fatal(err) } // add default route via gw2 in test route table defaultRouteTest2 := Route{ Dst: nil, Gw: gw2, Table: testTable2, } if err := RouteAdd(&defaultRouteTest2); err != nil { t.Fatal(err) } // check the routes are in different tables routes, err := RouteListFiltered(FAMILY_V4, &Route{ Dst: nil, Table: unix.RT_TABLE_UNSPEC, }, RT_FILTER_DST|RT_FILTER_TABLE) if err != nil { t.Fatal(err) } if len(routes) != 3 || routes[0].Table == routes[1].Table || routes[1].Table == routes[2].Table || routes[0].Table == routes[2].Table { t.Fatal("Routes not added properly") } // add a rule that fwmark match should result in route lookup of test table fwmark1 := uint32(0xAFFFFFFF) fwmark2 := uint32(0xBFFFFFFF) rule := NewRule() rule.Mark = fwmark1 rule.Mask = &[]uint32{0xFFFFFFFF}[0] rule.Table = testTable1 if err := RuleAdd(rule); err != nil { t.Fatal(err) } rule = NewRule() rule.Mark = fwmark2 rule.Mask = &[]uint32{0xFFFFFFFF}[0] rule.Table = testTable2 if err := RuleAdd(rule); err != nil { t.Fatal(err) } rules, err := RuleListFiltered(FAMILY_V4, &Rule{Mark: fwmark1}, RT_FILTER_MARK) if err != nil { t.Fatal(err) } if len(rules) != 1 || rules[0].Table != testTable1 || rules[0].Mark != fwmark1 { t.Fatal("Rules not added properly") } rules, err = RuleListFiltered(FAMILY_V4, &Rule{Mark: fwmark2}, RT_FILTER_MARK) if err != nil { t.Fatal(err) } if len(rules) != 1 || rules[0].Table != testTable2 || rules[0].Mark != fwmark2 { t.Fatal("Rules not added properly") } dstIP := net.IPv4(10, 1, 1, 1) // check getting route without FWMark option routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{}) if err != nil { t.Fatal(err) } if len(routes) != 1 || !routes[0].Gw.Equal(gw0) { t.Fatal(routes) } // check getting route with FWMark option routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Mark: fwmark1}) if err != nil { t.Fatal(err) } if len(routes) != 1 || !routes[0].Gw.Equal(gw1) { t.Fatal(routes) } // check getting route with FWMark option routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Mark: fwmark2}) if err != nil { t.Fatal(err) } if len(routes) != 1 || !routes[0].Gw.Equal(gw2) { t.Fatal(routes) } } func TestRouteGetFIBMatchOption(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}}) if err != nil { t.Fatal(err) } link, err := LinkByName("eth0") if err != nil { t.Fatal(err) } if err = LinkSetUp(link); err != nil { t.Fatal(err) } addr := &Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 0, 2), Mask: net.CIDRMask(24, 32), }, } if err = AddrAdd(link, addr); err != nil { t.Fatal(err) } route := &Route{ LinkIndex: link.Attrs().Index, Gw: net.IPv4(192, 168, 1, 1), Dst: &net.IPNet{ IP: net.IPv4(192, 168, 2, 0), Mask: net.CIDRMask(24, 32), }, Flags: int(FLAG_ONLINK), } err = RouteAdd(route) if err != nil { t.Fatal(err) } routes, err := RouteGetWithOptions(net.IPv4(192, 168, 2, 1), &RouteGetOptions{FIBMatch: true}) if err != nil { t.Fatal(err) } if len(routes) != 1 { t.Fatalf("More than one route matched %v", routes) } if len(routes[0].ListFlags()) != 1 { t.Fatalf("More than one route flag returned %v", routes[0].ListFlags()) } flag := routes[0].ListFlags()[0] if flag != "onlink" { t.Fatalf("Unexpected flag %s returned", flag) } } netlink-1.3.0/route_unspecified.go000066400000000000000000000004631466216277000172400ustar00rootroot00000000000000// +build !linux package netlink import "strconv" func (r *Route) ListFlags() []string { return []string{} } func (n *NexthopInfo) ListFlags() []string { return []string{} } func (s Scope) String() string { return "unknown" } func (p RouteProtocol) String() string { return strconv.Itoa(int(p)) } netlink-1.3.0/rule.go000066400000000000000000000033441466216277000144740ustar00rootroot00000000000000package netlink import ( "fmt" "net" ) // Rule represents a netlink rule. type Rule struct { Priority int Family int Table int Mark uint32 Mask *uint32 Tos uint TunID uint Goto int Src *net.IPNet Dst *net.IPNet Flow int IifName string OifName string SuppressIfgroup int SuppressPrefixlen int Invert bool Dport *RulePortRange Sport *RulePortRange IPProto int UIDRange *RuleUIDRange Protocol uint8 Type uint8 } func (r Rule) String() string { from := "all" if r.Src != nil && r.Src.String() != "" { from = r.Src.String() } to := "all" if r.Dst != nil && r.Dst.String() != "" { to = r.Dst.String() } return fmt.Sprintf("ip rule %d: from %s to %s table %d %s", r.Priority, from, to, r.Table, r.typeString()) } // NewRule return empty rules. func NewRule() *Rule { return &Rule{ SuppressIfgroup: -1, SuppressPrefixlen: -1, Priority: -1, Mark: 0, Mask: nil, Goto: -1, Flow: -1, } } // NewRulePortRange creates rule sport/dport range. func NewRulePortRange(start, end uint16) *RulePortRange { return &RulePortRange{Start: start, End: end} } // RulePortRange represents rule sport/dport range. type RulePortRange struct { Start uint16 End uint16 } // NewRuleUIDRange creates rule uid range. func NewRuleUIDRange(start, end uint32) *RuleUIDRange { return &RuleUIDRange{Start: start, End: end} } // RuleUIDRange represents rule uid range. type RuleUIDRange struct { Start uint32 End uint32 } netlink-1.3.0/rule_linux.go000066400000000000000000000234071466216277000157150ustar00rootroot00000000000000package netlink import ( "bytes" "fmt" "net" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) const FibRuleInvert = 0x2 // RuleAdd adds a rule to the system. // Equivalent to: ip rule add func RuleAdd(rule *Rule) error { return pkgHandle.RuleAdd(rule) } // RuleAdd adds a rule to the system. // Equivalent to: ip rule add func (h *Handle) RuleAdd(rule *Rule) error { req := h.newNetlinkRequest(unix.RTM_NEWRULE, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) return ruleHandle(rule, req) } // RuleDel deletes a rule from the system. // Equivalent to: ip rule del func RuleDel(rule *Rule) error { return pkgHandle.RuleDel(rule) } // RuleDel deletes a rule from the system. // Equivalent to: ip rule del func (h *Handle) RuleDel(rule *Rule) error { req := h.newNetlinkRequest(unix.RTM_DELRULE, unix.NLM_F_ACK) return ruleHandle(rule, req) } func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error { msg := nl.NewRtMsg() msg.Family = unix.AF_INET msg.Protocol = unix.RTPROT_BOOT msg.Scope = unix.RT_SCOPE_UNIVERSE msg.Table = unix.RT_TABLE_UNSPEC msg.Type = rule.Type // usually 0, same as unix.RTN_UNSPEC if msg.Type == 0 && req.NlMsghdr.Flags&unix.NLM_F_CREATE > 0 { msg.Type = unix.RTN_UNICAST } if rule.Invert { msg.Flags |= FibRuleInvert } if rule.Family != 0 { msg.Family = uint8(rule.Family) } if rule.Table >= 0 && rule.Table < 256 { msg.Table = uint8(rule.Table) } if rule.Tos != 0 { msg.Tos = uint8(rule.Tos) } var dstFamily uint8 var rtAttrs []*nl.RtAttr if rule.Dst != nil && rule.Dst.IP != nil { dstLen, _ := rule.Dst.Mask.Size() msg.Dst_len = uint8(dstLen) msg.Family = uint8(nl.GetIPFamily(rule.Dst.IP)) dstFamily = msg.Family var dstData []byte if msg.Family == unix.AF_INET { dstData = rule.Dst.IP.To4() } else { dstData = rule.Dst.IP.To16() } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_DST, dstData)) } if rule.Src != nil && rule.Src.IP != nil { msg.Family = uint8(nl.GetIPFamily(rule.Src.IP)) if dstFamily != 0 && dstFamily != msg.Family { return fmt.Errorf("source and destination ip are not the same IP family") } srcLen, _ := rule.Src.Mask.Size() msg.Src_len = uint8(srcLen) var srcData []byte if msg.Family == unix.AF_INET { srcData = rule.Src.IP.To4() } else { srcData = rule.Src.IP.To16() } rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_SRC, srcData)) } req.AddData(msg) for i := range rtAttrs { req.AddData(rtAttrs[i]) } if rule.Priority >= 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.Priority)) req.AddData(nl.NewRtAttr(nl.FRA_PRIORITY, b)) } if rule.Mark != 0 || rule.Mask != nil { b := make([]byte, 4) native.PutUint32(b, rule.Mark) req.AddData(nl.NewRtAttr(nl.FRA_FWMARK, b)) } if rule.Mask != nil { b := make([]byte, 4) native.PutUint32(b, *rule.Mask) req.AddData(nl.NewRtAttr(nl.FRA_FWMASK, b)) } if rule.Flow >= 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.Flow)) req.AddData(nl.NewRtAttr(nl.FRA_FLOW, b)) } if rule.TunID > 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.TunID)) req.AddData(nl.NewRtAttr(nl.FRA_TUN_ID, b)) } if rule.Table >= 256 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.Table)) req.AddData(nl.NewRtAttr(nl.FRA_TABLE, b)) } if msg.Table > 0 { if rule.SuppressPrefixlen >= 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.SuppressPrefixlen)) req.AddData(nl.NewRtAttr(nl.FRA_SUPPRESS_PREFIXLEN, b)) } if rule.SuppressIfgroup >= 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.SuppressIfgroup)) req.AddData(nl.NewRtAttr(nl.FRA_SUPPRESS_IFGROUP, b)) } } if rule.IifName != "" { req.AddData(nl.NewRtAttr(nl.FRA_IIFNAME, []byte(rule.IifName+"\x00"))) } if rule.OifName != "" { req.AddData(nl.NewRtAttr(nl.FRA_OIFNAME, []byte(rule.OifName+"\x00"))) } if rule.Goto >= 0 { msg.Type = nl.FR_ACT_GOTO b := make([]byte, 4) native.PutUint32(b, uint32(rule.Goto)) req.AddData(nl.NewRtAttr(nl.FRA_GOTO, b)) } if rule.IPProto > 0 { b := make([]byte, 4) native.PutUint32(b, uint32(rule.IPProto)) req.AddData(nl.NewRtAttr(nl.FRA_IP_PROTO, b)) } if rule.Dport != nil { b := rule.Dport.toRtAttrData() req.AddData(nl.NewRtAttr(nl.FRA_DPORT_RANGE, b)) } if rule.Sport != nil { b := rule.Sport.toRtAttrData() req.AddData(nl.NewRtAttr(nl.FRA_SPORT_RANGE, b)) } if rule.UIDRange != nil { b := rule.UIDRange.toRtAttrData() req.AddData(nl.NewRtAttr(nl.FRA_UID_RANGE, b)) } if rule.Protocol > 0 { req.AddData(nl.NewRtAttr(nl.FRA_PROTOCOL, nl.Uint8Attr(rule.Protocol))) } _, err := req.Execute(unix.NETLINK_ROUTE, 0) return err } // RuleList lists rules in the system. // Equivalent to: ip rule list func RuleList(family int) ([]Rule, error) { return pkgHandle.RuleList(family) } // RuleList lists rules in the system. // Equivalent to: ip rule list func (h *Handle) RuleList(family int) ([]Rule, error) { return h.RuleListFiltered(family, nil, 0) } // RuleListFiltered gets a list of rules in the system filtered by the // specified rule template `filter`. // Equivalent to: ip rule list func RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { return pkgHandle.RuleListFiltered(family, filter, filterMask) } // RuleListFiltered lists rules in the system. // Equivalent to: ip rule list func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { req := h.newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP|unix.NLM_F_REQUEST) msg := nl.NewIfInfomsg(family) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWRULE) if err != nil { return nil, err } var res = make([]Rule, 0) for i := range msgs { msg := nl.DeserializeRtMsg(msgs[i]) attrs, err := nl.ParseRouteAttr(msgs[i][msg.Len():]) if err != nil { return nil, err } rule := NewRule() rule.Priority = 0 // The default priority from kernel rule.Invert = msg.Flags&FibRuleInvert > 0 rule.Family = int(msg.Family) rule.Tos = uint(msg.Tos) for j := range attrs { switch attrs[j].Attr.Type { case unix.RTA_TABLE: rule.Table = int(native.Uint32(attrs[j].Value[0:4])) case nl.FRA_SRC: rule.Src = &net.IPNet{ IP: attrs[j].Value, Mask: net.CIDRMask(int(msg.Src_len), 8*len(attrs[j].Value)), } case nl.FRA_DST: rule.Dst = &net.IPNet{ IP: attrs[j].Value, Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attrs[j].Value)), } case nl.FRA_FWMARK: rule.Mark = native.Uint32(attrs[j].Value[0:4]) case nl.FRA_FWMASK: mask := native.Uint32(attrs[j].Value[0:4]) rule.Mask = &mask case nl.FRA_TUN_ID: rule.TunID = uint(native.Uint64(attrs[j].Value[0:8])) case nl.FRA_IIFNAME: rule.IifName = string(attrs[j].Value[:len(attrs[j].Value)-1]) case nl.FRA_OIFNAME: rule.OifName = string(attrs[j].Value[:len(attrs[j].Value)-1]) case nl.FRA_SUPPRESS_PREFIXLEN: i := native.Uint32(attrs[j].Value[0:4]) if i != 0xffffffff { rule.SuppressPrefixlen = int(i) } case nl.FRA_SUPPRESS_IFGROUP: i := native.Uint32(attrs[j].Value[0:4]) if i != 0xffffffff { rule.SuppressIfgroup = int(i) } case nl.FRA_FLOW: rule.Flow = int(native.Uint32(attrs[j].Value[0:4])) case nl.FRA_GOTO: rule.Goto = int(native.Uint32(attrs[j].Value[0:4])) case nl.FRA_PRIORITY: rule.Priority = int(native.Uint32(attrs[j].Value[0:4])) case nl.FRA_IP_PROTO: rule.IPProto = int(native.Uint32(attrs[j].Value[0:4])) case nl.FRA_DPORT_RANGE: rule.Dport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4])) case nl.FRA_SPORT_RANGE: rule.Sport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4])) case nl.FRA_UID_RANGE: rule.UIDRange = NewRuleUIDRange(native.Uint32(attrs[j].Value[0:4]), native.Uint32(attrs[j].Value[4:8])) case nl.FRA_PROTOCOL: rule.Protocol = uint8(attrs[j].Value[0]) } } if filter != nil { switch { case filterMask&RT_FILTER_SRC != 0 && (rule.Src == nil || rule.Src.String() != filter.Src.String()): continue case filterMask&RT_FILTER_DST != 0 && (rule.Dst == nil || rule.Dst.String() != filter.Dst.String()): continue case filterMask&RT_FILTER_TABLE != 0 && filter.Table != unix.RT_TABLE_UNSPEC && rule.Table != filter.Table: continue case filterMask&RT_FILTER_TOS != 0 && rule.Tos != filter.Tos: continue case filterMask&RT_FILTER_PRIORITY != 0 && rule.Priority != filter.Priority: continue case filterMask&RT_FILTER_MARK != 0 && rule.Mark != filter.Mark: continue case filterMask&RT_FILTER_MASK != 0 && !ptrEqual(rule.Mask, filter.Mask): continue } } res = append(res, *rule) } return res, nil } func (pr *RulePortRange) toRtAttrData() []byte { b := [][]byte{make([]byte, 2), make([]byte, 2)} native.PutUint16(b[0], pr.Start) native.PutUint16(b[1], pr.End) return bytes.Join(b, []byte{}) } func (pr *RuleUIDRange) toRtAttrData() []byte { b := [][]byte{make([]byte, 4), make([]byte, 4)} native.PutUint32(b[0], pr.Start) native.PutUint32(b[1], pr.End) return bytes.Join(b, []byte{}) } func ptrEqual(a, b *uint32) bool { if a == b { return true } if (a == nil) || (b == nil) { return false } return *a == *b } func (r Rule) typeString() string { switch r.Type { case unix.RTN_UNSPEC: // zero return "" case unix.RTN_UNICAST: return "" case unix.RTN_LOCAL: return "local" case unix.RTN_BROADCAST: return "broadcast" case unix.RTN_ANYCAST: return "anycast" case unix.RTN_MULTICAST: return "multicast" case unix.RTN_BLACKHOLE: return "blackhole" case unix.RTN_UNREACHABLE: return "unreachable" case unix.RTN_PROHIBIT: return "prohibit" case unix.RTN_THROW: return "throw" case unix.RTN_NAT: return "nat" case unix.RTN_XRESOLVE: return "xresolve" default: return fmt.Sprintf("type(0x%x)", r.Type) } } netlink-1.3.0/rule_nonlinux.go000066400000000000000000000001461466216277000164230ustar00rootroot00000000000000//go:build !linux // +build !linux package netlink func (r Rule) typeString() string { return "" } netlink-1.3.0/rule_test.go000066400000000000000000000407661466216277000155440ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "net" "testing" "golang.org/x/sys/unix" ) func TestRuleAddDel(t *testing.T) { skipUnlessRoot(t) defer setUpNetlinkTest(t)() srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)} dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)} rulesBegin, err := RuleList(FAMILY_V4) if err != nil { t.Fatal(err) } rule := NewRule() rule.Family = FAMILY_V4 rule.Table = unix.RT_TABLE_MAIN rule.Src = srcNet rule.Dst = dstNet rule.Priority = 5 rule.OifName = "lo" rule.IifName = "lo" rule.Invert = true rule.Tos = 0x10 rule.Dport = NewRulePortRange(80, 80) rule.Sport = NewRulePortRange(1000, 1024) rule.IPProto = unix.IPPROTO_UDP rule.UIDRange = NewRuleUIDRange(100, 100) rule.Protocol = unix.RTPROT_KERNEL if err := RuleAdd(rule); err != nil { t.Fatal(err) } rules, err := RuleList(FAMILY_V4) if err != nil { t.Fatal(err) } if len(rules) != len(rulesBegin)+1 { t.Fatal("Rule not added properly") } // find this rule found := ruleExists(rules, *rule) if !found { t.Fatal("Rule has diffrent options than one added") } if err := RuleDel(rule); err != nil { t.Fatal(err) } rulesEnd, err := RuleList(FAMILY_V4) if err != nil { t.Fatal(err) } if len(rulesEnd) != len(rulesBegin) { t.Fatal("Rule not removed properly") } } func TestRuleListFiltered(t *testing.T) { skipUnlessRoot(t) defer setUpNetlinkTest(t)() t.Run("IPv4", testRuleListFilteredIPv4) t.Run("IPv6", testRuleListFilteredIPv6) } func testRuleListFilteredIPv4(t *testing.T) { srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)} dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)} runRuleListFiltered(t, FAMILY_V4, srcNet, dstNet) } func testRuleListFilteredIPv6(t *testing.T) { ip1 := net.ParseIP("fd56:6b58:db28:2913::") ip2 := net.ParseIP("fde9:379f:3b35:6635::") srcNet := &net.IPNet{IP: ip1, Mask: net.CIDRMask(64, 128)} dstNet := &net.IPNet{IP: ip2, Mask: net.CIDRMask(96, 128)} runRuleListFiltered(t, FAMILY_V6, srcNet, dstNet) } func runRuleListFiltered(t *testing.T, family int, srcNet, dstNet *net.IPNet) { defaultRules, _ := RuleList(family) tests := []struct { name string ruleFilter *Rule filterMask uint64 preRun func() *Rule // Creates sample rule harness postRun func(*Rule) // Deletes sample rule harness setupWant func(*Rule) ([]Rule, bool) }{ { name: "returns all rules", ruleFilter: nil, filterMask: 0, preRun: func() *Rule { return nil }, postRun: func(r *Rule) {}, setupWant: func(_ *Rule) ([]Rule, bool) { return defaultRules, false }, }, { name: "returns one rule filtered by Src", ruleFilter: &Rule{Src: srcNet}, filterMask: RT_FILTER_SRC, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 // Must add priority and table otherwise it's auto-assigned r.Family = family r.Table = 1 RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns one rule filtered by Dst", ruleFilter: &Rule{Dst: dstNet}, filterMask: RT_FILTER_DST, preRun: func() *Rule { r := NewRule() r.Dst = dstNet r.Priority = 1 // Must add priority and table otherwise it's auto-assigned r.Family = family r.Table = 1 RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns two rules filtered by Dst", ruleFilter: &Rule{Dst: dstNet}, filterMask: RT_FILTER_DST, preRun: func() *Rule { r := NewRule() r.Dst = dstNet r.Priority = 1 // Must add priority and table otherwise it's auto-assigned r.Family = family r.Table = 1 RuleAdd(r) rc := *r // Create almost identical copy rc.Src = srcNet RuleAdd(&rc) return r }, postRun: func(r *Rule) { RuleDel(r) rc := *r // Delete the almost identical copy rc.Src = srcNet RuleDel(&rc) }, setupWant: func(r *Rule) ([]Rule, bool) { rs := []Rule{} rs = append(rs, *r) rc := *r // Append the almost identical copy rc.Src = srcNet rs = append(rs, rc) return rs, false }, }, { name: "returns one rule filtered by Src when two rules exist", ruleFilter: &Rule{Src: srcNet}, filterMask: RT_FILTER_SRC, preRun: func() *Rule { r := NewRule() r.Dst = dstNet r.Priority = 1 // Must add priority and table otherwise it's auto-assigned r.Family = family r.Table = 1 RuleAdd(r) rc := *r // Create almost identical copy rc.Src = srcNet RuleAdd(&rc) return r }, postRun: func(r *Rule) { RuleDel(r) rc := *r // Delete the almost identical copy rc.Src = srcNet RuleDel(&rc) }, setupWant: func(r *Rule) ([]Rule, bool) { rs := []Rule{} // Do not append `r` rc := *r // Append the almost identical copy rc.Src = srcNet rs = append(rs, rc) return rs, false }, }, { name: "returns one rule filtered by Priority(0) and Table", ruleFilter: &Rule{Priority: 0, Table: 1}, filterMask: RT_FILTER_PRIORITY | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 0 r.Family = family r.Table = 1 RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns one rule filtered by Priority preceding main-table rule", ruleFilter: &Rule{Priority: 32765}, filterMask: RT_FILTER_PRIORITY, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Family = family r.Table = 1 RuleAdd(r) r.Priority = 32765 // Set priority for assertion return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules with specific priority", ruleFilter: &Rule{Priority: 5}, filterMask: RT_FILTER_PRIORITY, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 5 r.Family = family r.Table = 1 RuleAdd(r) for i := 2; i < 5; i++ { rc := *r // Create almost identical copy rc.Table = i RuleAdd(&rc) } return r }, postRun: func(r *Rule) { RuleDel(r) for i := 2; i < 5; i++ { rc := *r // Delete the almost identical copy rc.Table = -1 RuleDel(&rc) } }, setupWant: func(r *Rule) ([]Rule, bool) { rs := []Rule{} rs = append(rs, *r) for i := 2; i < 5; i++ { rc := *r // Append the almost identical copy rc.Table = i rs = append(rs, rc) } return rs, false }, }, { name: "returns rules filtered by Table", ruleFilter: &Rule{Table: 199}, filterMask: RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 // Must add priority otherwise it's auto-assigned r.Family = family r.Table = 199 RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by Mask", ruleFilter: &Rule{Mask: &[]uint32{0x5}[0]}, filterMask: RT_FILTER_MASK, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 // Must add priority and table otherwise it's auto-assigned r.Family = family r.Table = 1 r.Mask = &[]uint32{0x5}[0] RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by Mark", ruleFilter: &Rule{Mark: 0xbb}, filterMask: RT_FILTER_MARK, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned r.Family = family r.Table = 1 r.Mask = &[]uint32{0xff}[0] r.Mark = 0xbb RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0", ruleFilter: &Rule{Mark: 0, Mask: nil, Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0 r.Mask = nil if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0/0xFFFFFFFF", ruleFilter: &Rule{Mark: 0, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0 r.Mask = &[]uint32{0xFFFFFFFF}[0] if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0x1234/0", ruleFilter: &Rule{Mark: 0x1234, Mask: &[]uint32{0}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0x1234 r.Mask = &[]uint32{0}[0] if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0/0xFFFFFFFF", ruleFilter: &Rule{Mark: 0, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0 r.Mask = &[]uint32{0xFFFFFFFF}[0] if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0xFFFFFFFF", ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0xFFFFFFFF r.Mask = nil if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0x1234", ruleFilter: &Rule{Mark: 0x1234, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0x1234 r.Mask = nil if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0x12345678", ruleFilter: &Rule{Mark: 0x12345678, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0x12345678 r.Mask = nil if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0xFFFFFFFF/0", ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0xFFFFFFFF r.Mask = &[]uint32{0}[0] if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by fwmark 0xFFFFFFFF/0xFFFFFFFF", ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100}, filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 r.Family = family r.Table = 100 r.Mark = 0xFFFFFFFF r.Mask = &[]uint32{0xFFFFFFFF}[0] if err := RuleAdd(r); err != nil { t.Fatal(err) } return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, { name: "returns rules filtered by Tos", ruleFilter: &Rule{Tos: 12}, filterMask: RT_FILTER_TOS, preRun: func() *Rule { r := NewRule() r.Src = srcNet r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned r.Family = family r.Table = 12 r.Tos = 12 // Tos must equal table RuleAdd(r) return r }, postRun: func(r *Rule) { RuleDel(r) }, setupWant: func(r *Rule) ([]Rule, bool) { return []Rule{*r}, false }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rule := tt.preRun() rules, err := RuleListFiltered(family, tt.ruleFilter, tt.filterMask) tt.postRun(rule) wantRules, wantErr := tt.setupWant(rule) if len(wantRules) != len(rules) { t.Errorf("Expected len: %d, got: %d", len(wantRules), len(rules)) } else { for i := range wantRules { if !ruleEquals(wantRules[i], rules[i]) { t.Errorf("Rules mismatch, want %v, got %v", wantRules[i], rules[i]) } } } if (err != nil) != wantErr { t.Errorf("Error expectation not met, want %v, got %v", (err != nil), wantErr) } }) } } func TestRuleString(t *testing.T) { t.Parallel() testCases := map[string]struct { r Rule s string }{ "empty rule": { s: "ip rule 0: from all to all table 0 ", }, "rule with src and dst equivalent to ": { r: Rule{ Priority: 100, Src: &net.IPNet{IP: net.IPv4(10, 0, 0, 0)}, Dst: &net.IPNet{IP: net.IPv4(20, 0, 0, 0)}, Table: 99, }, s: "ip rule 100: from all to all table 99 ", }, "rule with src and dst": { r: Rule{ Priority: 100, Src: &net.IPNet{IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, Dst: &net.IPNet{IP: net.IPv4(20, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, Table: 99, }, s: "ip rule 100: from 10.0.0.0/24 to 20.0.0.0/24 table 99 ", }, "rule with type": { r: Rule{ Priority: 101, Type: unix.RTN_UNREACHABLE, }, s: "ip rule 101: from all to all table 0 unreachable", }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() s := testCase.r.String() if s != testCase.s { t.Errorf("expected %q but got %q", testCase.s, s) } }) } } func ruleExists(rules []Rule, rule Rule) bool { for i := range rules { if ruleEquals(rules[i], rule) { return true } } return false } func ruleEquals(a, b Rule) bool { return a.Table == b.Table && ((a.Src == nil && b.Src == nil) || (a.Src != nil && b.Src != nil && a.Src.String() == b.Src.String())) && ((a.Dst == nil && b.Dst == nil) || (a.Dst != nil && b.Dst != nil && a.Dst.String() == b.Dst.String())) && a.OifName == b.OifName && a.Priority == b.Priority && a.Family == b.Family && a.IifName == b.IifName && a.Invert == b.Invert && a.Tos == b.Tos && a.Type == b.Type && a.IPProto == b.IPProto && a.Protocol == b.Protocol && a.Mark == b.Mark && (ptrEqual(a.Mask, b.Mask) || (a.Mark != 0 && (a.Mask == nil && *b.Mask == 0xFFFFFFFF || b.Mask == nil && *a.Mask == 0xFFFFFFFF))) } netlink-1.3.0/socket.go000066400000000000000000000042701466216277000150140ustar00rootroot00000000000000package netlink import "net" // SocketID identifies a single socket. type SocketID struct { SourcePort uint16 DestinationPort uint16 Source net.IP Destination net.IP Interface uint32 Cookie [2]uint32 } // Socket represents a netlink socket. type Socket struct { Family uint8 State uint8 Timer uint8 Retrans uint8 ID SocketID Expires uint32 RQueue uint32 WQueue uint32 UID uint32 INode uint32 } // UnixSocket represents a netlink unix socket. type UnixSocket struct { Type uint8 Family uint8 State uint8 pad uint8 INode uint32 Cookie [2]uint32 } // XDPSocket represents an XDP socket (and the common diagnosis part in // particular). Please note that in contrast to [UnixSocket] the XDPSocket type // does not feature “State†information. type XDPSocket struct { // xdp_diag_msg // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21 Family uint8 Type uint8 pad uint16 Ino uint32 Cookie [2]uint32 } type XDPInfo struct { // XDP_DIAG_INFO/xdp_diag_info // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L51 Ifindex uint32 QueueID uint32 // XDP_DIAG_UID UID uint32 // XDP_RX_RING // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L56 RxRingEntries uint32 TxRingEntries uint32 UmemFillRingEntries uint32 UmemCompletionRingEntries uint32 // XDR_DIAG_UMEM Umem *XDPDiagUmem // XDR_DIAG_STATS Stats *XDPDiagStats } const ( XDP_DU_F_ZEROCOPY = 1 << iota ) // XDPDiagUmem describes the umem attached to an XDP socket. // // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L62 type XDPDiagUmem struct { Size uint64 ID uint32 NumPages uint32 ChunkSize uint32 Headroom uint32 Ifindex uint32 QueueID uint32 Flags uint32 Refs uint32 } // XDPDiagStats contains ring statistics for an XDP socket. // // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L74 type XDPDiagStats struct { RxDropped uint64 RxInvalid uint64 RxFull uint64 FillRingEmpty uint64 TxInvalid uint64 TxRingEmpty uint64 } netlink-1.3.0/socket_linux.go000066400000000000000000000370421466216277000162360ustar00rootroot00000000000000package netlink import ( "errors" "fmt" "net" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) const ( sizeofSocketID = 0x30 sizeofSocketRequest = sizeofSocketID + 0x8 sizeofSocket = sizeofSocketID + 0x18 sizeofUnixSocketRequest = 0x18 // 24 byte sizeofUnixSocket = 0x10 // 16 byte ) type socketRequest struct { Family uint8 Protocol uint8 Ext uint8 pad uint8 States uint32 ID SocketID } type writeBuffer struct { Bytes []byte pos int } func (b *writeBuffer) Write(c byte) { b.Bytes[b.pos] = c b.pos++ } func (b *writeBuffer) Next(n int) []byte { s := b.Bytes[b.pos : b.pos+n] b.pos += n return s } func (r *socketRequest) Serialize() []byte { b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)} b.Write(r.Family) b.Write(r.Protocol) b.Write(r.Ext) b.Write(r.pad) native.PutUint32(b.Next(4), r.States) networkOrder.PutUint16(b.Next(2), r.ID.SourcePort) networkOrder.PutUint16(b.Next(2), r.ID.DestinationPort) if r.Family == unix.AF_INET6 { copy(b.Next(16), r.ID.Source) copy(b.Next(16), r.ID.Destination) } else { copy(b.Next(16), r.ID.Source.To4()) copy(b.Next(16), r.ID.Destination.To4()) } native.PutUint32(b.Next(4), r.ID.Interface) native.PutUint32(b.Next(4), r.ID.Cookie[0]) native.PutUint32(b.Next(4), r.ID.Cookie[1]) return b.Bytes } func (r *socketRequest) Len() int { return sizeofSocketRequest } // According to linux/include/uapi/linux/unix_diag.h type unixSocketRequest struct { Family uint8 Protocol uint8 pad uint16 States uint32 INode uint32 Show uint32 Cookie [2]uint32 } func (r *unixSocketRequest) Serialize() []byte { b := writeBuffer{Bytes: make([]byte, sizeofUnixSocketRequest)} b.Write(r.Family) b.Write(r.Protocol) native.PutUint16(b.Next(2), r.pad) native.PutUint32(b.Next(4), r.States) native.PutUint32(b.Next(4), r.INode) native.PutUint32(b.Next(4), r.Show) native.PutUint32(b.Next(4), r.Cookie[0]) native.PutUint32(b.Next(4), r.Cookie[1]) return b.Bytes } func (r *unixSocketRequest) Len() int { return sizeofUnixSocketRequest } type readBuffer struct { Bytes []byte pos int } func (b *readBuffer) Read() byte { c := b.Bytes[b.pos] b.pos++ return c } func (b *readBuffer) Next(n int) []byte { s := b.Bytes[b.pos : b.pos+n] b.pos += n return s } func (s *Socket) deserialize(b []byte) error { if len(b) < sizeofSocket { return fmt.Errorf("socket data short read (%d); want %d", len(b), sizeofSocket) } rb := readBuffer{Bytes: b} s.Family = rb.Read() s.State = rb.Read() s.Timer = rb.Read() s.Retrans = rb.Read() s.ID.SourcePort = networkOrder.Uint16(rb.Next(2)) s.ID.DestinationPort = networkOrder.Uint16(rb.Next(2)) if s.Family == unix.AF_INET6 { s.ID.Source = net.IP(rb.Next(16)) s.ID.Destination = net.IP(rb.Next(16)) } else { s.ID.Source = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read()) rb.Next(12) s.ID.Destination = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read()) rb.Next(12) } s.ID.Interface = native.Uint32(rb.Next(4)) s.ID.Cookie[0] = native.Uint32(rb.Next(4)) s.ID.Cookie[1] = native.Uint32(rb.Next(4)) s.Expires = native.Uint32(rb.Next(4)) s.RQueue = native.Uint32(rb.Next(4)) s.WQueue = native.Uint32(rb.Next(4)) s.UID = native.Uint32(rb.Next(4)) s.INode = native.Uint32(rb.Next(4)) return nil } func (u *UnixSocket) deserialize(b []byte) error { if len(b) < sizeofUnixSocket { return fmt.Errorf("unix diag data short read (%d); want %d", len(b), sizeofUnixSocket) } rb := readBuffer{Bytes: b} u.Type = rb.Read() u.Family = rb.Read() u.State = rb.Read() u.pad = rb.Read() u.INode = native.Uint32(rb.Next(4)) u.Cookie[0] = native.Uint32(rb.Next(4)) u.Cookie[1] = native.Uint32(rb.Next(4)) return nil } // SocketGet returns the Socket identified by its local and remote addresses. func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) { var protocol uint8 var localIP, remoteIP net.IP var localPort, remotePort uint16 switch l := local.(type) { case *net.TCPAddr: r, ok := remote.(*net.TCPAddr) if !ok { return nil, ErrNotImplemented } localIP = l.IP localPort = uint16(l.Port) remoteIP = r.IP remotePort = uint16(r.Port) protocol = unix.IPPROTO_TCP case *net.UDPAddr: r, ok := remote.(*net.UDPAddr) if !ok { return nil, ErrNotImplemented } localIP = l.IP localPort = uint16(l.Port) remoteIP = r.IP remotePort = uint16(r.Port) protocol = unix.IPPROTO_UDP default: return nil, ErrNotImplemented } var family uint8 if localIP.To4() != nil && remoteIP.To4() != nil { family = unix.AF_INET } if family == 0 && localIP.To16() != nil && remoteIP.To16() != nil { family = unix.AF_INET6 } if family == 0 { return nil, ErrNotImplemented } req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&socketRequest{ Family: family, Protocol: protocol, States: 0xffffffff, ID: SocketID{ SourcePort: localPort, DestinationPort: remotePort, Source: localIP, Destination: remoteIP, Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE}, }, }) msgs, err := req.Execute(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY) if err != nil { return nil, err } if len(msgs) == 0 { return nil, errors.New("no message nor error from netlink") } if len(msgs) > 2 { return nil, fmt.Errorf("multiple (%d) matching sockets", len(msgs)) } sock := &Socket{} if err := sock.deserialize(msgs[0]); err != nil { return nil, err } return sock, nil } // SocketGet returns the Socket identified by its local and remote addresses. func SocketGet(local, remote net.Addr) (*Socket, error) { return pkgHandle.SocketGet(local, remote) } // SocketDestroy kills the Socket identified by its local and remote addresses. func (h *Handle) SocketDestroy(local, remote net.Addr) error { localTCP, ok := local.(*net.TCPAddr) if !ok { return ErrNotImplemented } remoteTCP, ok := remote.(*net.TCPAddr) if !ok { return ErrNotImplemented } localIP := localTCP.IP.To4() if localIP == nil { return ErrNotImplemented } remoteIP := remoteTCP.IP.To4() if remoteIP == nil { return ErrNotImplemented } s, err := nl.Subscribe(unix.NETLINK_INET_DIAG) if err != nil { return err } defer s.Close() req := h.newNetlinkRequest(nl.SOCK_DESTROY, unix.NLM_F_ACK) req.AddData(&socketRequest{ Family: unix.AF_INET, Protocol: unix.IPPROTO_TCP, ID: SocketID{ SourcePort: uint16(localTCP.Port), DestinationPort: uint16(remoteTCP.Port), Source: localIP, Destination: remoteIP, Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE}, }, }) _, err = req.Execute(unix.NETLINK_INET_DIAG, 0) return err } // SocketDestroy kills the Socket identified by its local and remote addresses. func SocketDestroy(local, remote net.Addr) error { return pkgHandle.SocketDestroy(local, remote) } // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&socketRequest{ Family: family, Protocol: unix.IPPROTO_TCP, Ext: (1 << (INET_DIAG_VEGASINFO - 1)) | (1 << (INET_DIAG_INFO - 1)), States: uint32(0xfff), // all states }) // Do the query and parse the result var result []*InetDiagTCPInfoResp var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} if err = sockInfo.deserialize(msg); err != nil { return false } var attrs []syscall.NetlinkRouteAttr if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { return false } var res *InetDiagTCPInfoResp if res, err = attrsToInetDiagTCPInfoResp(attrs, sockInfo); err != nil { return false } result = append(result, res) return true }) if err != nil { return nil, err } return result, nil } // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. func SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { return pkgHandle.SocketDiagTCPInfo(family) } // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&socketRequest{ Family: family, Protocol: unix.IPPROTO_TCP, Ext: (1 << (INET_DIAG_VEGASINFO - 1)) | (1 << (INET_DIAG_INFO - 1)), States: uint32(0xfff), // all states }) // Do the query and parse the result var result []*Socket var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} if err = sockInfo.deserialize(msg); err != nil { return false } result = append(result, sockInfo) return true }) if err != nil { return nil, err } return result, nil } // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. func SocketDiagTCP(family uint8) ([]*Socket, error) { return pkgHandle.SocketDiagTCP(family) } // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { // Construct the request var extensions uint8 extensions = 1 << (INET_DIAG_VEGASINFO - 1) extensions |= 1 << (INET_DIAG_INFO - 1) extensions |= 1 << (INET_DIAG_MEMINFO - 1) req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&socketRequest{ Family: family, Protocol: unix.IPPROTO_UDP, Ext: extensions, States: uint32(0xfff), // all states }) // Do the query and parse the result var result []*InetDiagUDPInfoResp var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} if err = sockInfo.deserialize(msg); err != nil { return false } var attrs []syscall.NetlinkRouteAttr if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { return false } var res *InetDiagUDPInfoResp if res, err = attrsToInetDiagUDPInfoResp(attrs, sockInfo); err != nil { return false } result = append(result, res) return true }) if err != nil { return nil, err } return result, nil } // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. func SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { return pkgHandle.SocketDiagUDPInfo(family) } // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&socketRequest{ Family: family, Protocol: unix.IPPROTO_UDP, Ext: (1 << (INET_DIAG_VEGASINFO - 1)) | (1 << (INET_DIAG_INFO - 1)), States: uint32(0xfff), // all states }) // Do the query and parse the result var result []*Socket var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} if err = sockInfo.deserialize(msg); err != nil { return false } result = append(result, sockInfo) return true }) if err != nil { return nil, err } return result, nil } // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. func SocketDiagUDP(family uint8) ([]*Socket, error) { return pkgHandle.SocketDiagUDP(family) } // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { // Construct the request var extensions uint8 extensions = 1 << UNIX_DIAG_NAME extensions |= 1 << UNIX_DIAG_PEER extensions |= 1 << UNIX_DIAG_RQLEN req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&unixSocketRequest{ Family: unix.AF_UNIX, States: ^uint32(0), // all states Show: uint32(extensions), }) var result []*UnixDiagInfoResp var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &UnixSocket{} if err = sockInfo.deserialize(msg); err != nil { return false } // Diagnosis also delivers sockets with AF_INET family, filter those if sockInfo.Family != unix.AF_UNIX { return false } var attrs []syscall.NetlinkRouteAttr if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { return false } var res *UnixDiagInfoResp if res, err = attrsToUnixDiagInfoResp(attrs, sockInfo); err != nil { return false } result = append(result, res) return true }) if err != nil { return nil, err } return result, nil } // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. func UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { return pkgHandle.UnixSocketDiagInfo() } // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&unixSocketRequest{ Family: unix.AF_UNIX, States: ^uint32(0), // all states }) var result []*UnixSocket var err error err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &UnixSocket{} if err = sockInfo.deserialize(msg); err != nil { return false } // Diagnosis also delivers sockets with AF_INET family, filter those if sockInfo.Family == unix.AF_UNIX { result = append(result, sockInfo) } return true }) if err != nil { return nil, err } return result, nil } // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. func UnixSocketDiag() ([]*UnixSocket, error) { return pkgHandle.UnixSocketDiag() } func attrsToInetDiagTCPInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *Socket) (*InetDiagTCPInfoResp, error) { info := &InetDiagTCPInfoResp{ InetDiagMsg: sockInfo, } for _, a := range attrs { switch a.Attr.Type { case INET_DIAG_INFO: info.TCPInfo = &TCPInfo{} if err := info.TCPInfo.deserialize(a.Value); err != nil { return nil, err } case INET_DIAG_BBRINFO: info.TCPBBRInfo = &TCPBBRInfo{} if err := info.TCPBBRInfo.deserialize(a.Value); err != nil { return nil, err } } } return info, nil } func attrsToInetDiagUDPInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *Socket) (*InetDiagUDPInfoResp, error) { info := &InetDiagUDPInfoResp{ InetDiagMsg: sockInfo, } for _, a := range attrs { switch a.Attr.Type { case INET_DIAG_MEMINFO: info.Memory = &MemInfo{} if err := info.Memory.deserialize(a.Value); err != nil { return nil, err } } } return info, nil } func attrsToUnixDiagInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *UnixSocket) (*UnixDiagInfoResp, error) { info := &UnixDiagInfoResp{ DiagMsg: sockInfo, } for _, a := range attrs { switch a.Attr.Type { case UNIX_DIAG_NAME: name := string(a.Value[:a.Attr.Len]) info.Name = &name case UNIX_DIAG_PEER: peer := native.Uint32(a.Value) info.Peer = &peer case UNIX_DIAG_RQLEN: info.Queue = &QueueInfo{ RQueue: native.Uint32(a.Value[:4]), WQueue: native.Uint32(a.Value[4:]), } // default: // fmt.Println("unknown unix attribute type", a.Attr.Type, "with data", a.Value) } } return info, nil } netlink-1.3.0/socket_linux_test.go000066400000000000000000000052701466216277000172730ustar00rootroot00000000000000package netlink import ( "reflect" "syscall" "testing" ) func TestAttrsToInetDiagTCPInfoResp(t *testing.T) { tests := []struct { name string attrs []syscall.NetlinkRouteAttr expected *InetDiagTCPInfoResp wantFail bool }{ { name: "Empty", attrs: []syscall.NetlinkRouteAttr{}, expected: &InetDiagTCPInfoResp{}, }, { name: "BBRInfo Only", attrs: []syscall.NetlinkRouteAttr{ { Attr: syscall.RtAttr{ Len: 20, Type: INET_DIAG_BBRINFO, }, Value: []byte{ 100, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 222, 0, 0, 0, 123, 0, 0, 0, }, }, }, expected: &InetDiagTCPInfoResp{ TCPBBRInfo: &TCPBBRInfo{ BBRBW: 100, BBRMinRTT: 111, BBRPacingGain: 222, BBRCwndGain: 123, }, }, }, { name: "TCPInfo Only", attrs: []syscall.NetlinkRouteAttr{ { Attr: syscall.RtAttr{ Len: 232, Type: INET_DIAG_INFO, }, Value: tcpInfoData, }, }, expected: &InetDiagTCPInfoResp{ TCPInfo: tcpInfo, }, }, { name: "TCPInfo + TCPBBR", attrs: []syscall.NetlinkRouteAttr{ { Attr: syscall.RtAttr{ Len: 232, Type: INET_DIAG_INFO, }, Value: tcpInfoData, }, { Attr: syscall.RtAttr{ Len: 20, Type: INET_DIAG_BBRINFO, }, Value: []byte{ 100, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 222, 0, 0, 0, 123, 0, 0, 0, }, }, }, expected: &InetDiagTCPInfoResp{ TCPInfo: tcpInfo, TCPBBRInfo: &TCPBBRInfo{ BBRBW: 100, BBRMinRTT: 111, BBRPacingGain: 222, BBRCwndGain: 123, }, }, }, { name: "TCPBBR + TCPInfo (reverse)", attrs: []syscall.NetlinkRouteAttr{ { Attr: syscall.RtAttr{ Len: 20, Type: INET_DIAG_BBRINFO, }, Value: []byte{ 100, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 222, 0, 0, 0, 123, 0, 0, 0, }, }, { Attr: syscall.RtAttr{ Len: 232, Type: INET_DIAG_INFO, }, Value: tcpInfoData, }, }, expected: &InetDiagTCPInfoResp{ TCPInfo: tcpInfo, TCPBBRInfo: &TCPBBRInfo{ BBRBW: 100, BBRMinRTT: 111, BBRPacingGain: 222, BBRCwndGain: 123, }, }, }, } for _, test := range tests { res, err := attrsToInetDiagTCPInfoResp(test.attrs, nil) if err != nil && !test.wantFail { t.Errorf("Unexpected failure for test %q", test.name) continue } if err == nil && test.wantFail { t.Errorf("Unexpected success for test %q", test.name) continue } if !reflect.DeepEqual(test.expected, res) { t.Errorf("Unexpected failure for test %q", test.name) } } } netlink-1.3.0/socket_test.go000066400000000000000000000074651466216277000160640ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "fmt" "log" "net" "os/user" "strconv" "syscall" "testing" ) func TestSocketGet(t *testing.T) { defer setUpNetlinkTestWithLoopback(t)() type Addr struct { IP net.IP Port int } getAddr := func(a net.Addr) Addr { var addr Addr switch v := a.(type) { case *net.UDPAddr: addr.IP = v.IP addr.Port = v.Port case *net.TCPAddr: addr.IP = v.IP addr.Port = v.Port } return addr } checkSocket := func(t *testing.T, local, remote net.Addr) { socket, err := SocketGet(local, remote) if err != nil { t.Fatal(err) } localAddr, remoteAddr := getAddr(local), getAddr(remote) if got, want := socket.ID.Source, localAddr.IP; !got.Equal(want) { t.Fatalf("local ip = %v, want %v", got, want) } if got, want := socket.ID.Destination, remoteAddr.IP; !got.Equal(want) { t.Fatalf("remote ip = %v, want %v", got, want) } if got, want := int(socket.ID.SourcePort), localAddr.Port; got != want { t.Fatalf("local port = %d, want %d", got, want) } if got, want := int(socket.ID.DestinationPort), remoteAddr.Port; got != want { t.Fatalf("remote port = %d, want %d", got, want) } u, err := user.Current() if err != nil { t.Fatal(err) } if got, want := strconv.Itoa(int(socket.UID)), u.Uid; got != want { t.Fatalf("UID = %s, want %s", got, want) } } for _, v := range [...]string{"tcp4", "tcp6"} { addr, err := net.ResolveTCPAddr(v, "localhost:0") if err != nil { log.Fatal(err) } l, err := net.ListenTCP(v, addr) if err != nil { log.Fatal(err) } defer l.Close() conn, err := net.Dial(l.Addr().Network(), l.Addr().String()) if err != nil { t.Fatal(err) } defer conn.Close() checkSocket(t, conn.LocalAddr(), conn.RemoteAddr()) } for _, v := range [...]string{"udp4", "udp6"} { addr, err := net.ResolveUDPAddr(v, "localhost:0") if err != nil { log.Fatal(err) } l, err := net.ListenUDP(v, addr) if err != nil { log.Fatal(err) } defer l.Close() conn, err := net.Dial(l.LocalAddr().Network(), l.LocalAddr().String()) if err != nil { t.Fatal(err) } defer conn.Close() checkSocket(t, conn.LocalAddr(), conn.RemoteAddr()) } } func TestSocketDestroy(t *testing.T) { defer setUpNetlinkTestWithLoopback(t)() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if err != nil { log.Fatal(err) } l, err := net.ListenTCP("tcp", addr) if err != nil { log.Fatal(err) } defer l.Close() conn, err := net.Dial(l.Addr().Network(), l.Addr().String()) if err != nil { t.Fatal(err) } defer conn.Close() localAddr := conn.LocalAddr().(*net.TCPAddr) remoteAddr := conn.RemoteAddr().(*net.TCPAddr) err = SocketDestroy(localAddr, remoteAddr) if err != nil { t.Fatal(err) } } func TestSocketDiagTCPInfo(t *testing.T) { Family4 := uint8(syscall.AF_INET) Family6 := uint8(syscall.AF_INET6) families := []uint8{Family4, Family6} for _, wantFamily := range families { res, err := SocketDiagTCPInfo(wantFamily) if err != nil { t.Fatal(err) } for _, i := range res { gotFamily := i.InetDiagMsg.Family if gotFamily != wantFamily { t.Fatalf("Socket family = %d, want %d", gotFamily, wantFamily) } } } } func TestSocketDiagUDPnfo(t *testing.T) { for _, want := range []uint8{syscall.AF_INET, syscall.AF_INET6} { result, err := SocketDiagUDPInfo(want) if err != nil { t.Fatal(err) } for _, r := range result { if got := r.InetDiagMsg.Family; got != want { t.Fatalf("protocol family = %v, want %v", got, want) } } } } func TestUnixSocketDiagInfo(t *testing.T) { want := syscall.AF_UNIX result, err := UnixSocketDiagInfo() if err != nil { t.Fatal(err) } for i, r := range result { fmt.Println(r.DiagMsg) if got := r.DiagMsg.Family; got != uint8(want) { t.Fatalf("%d: protocol family = %v, want %v", i, got, want) } } } netlink-1.3.0/socket_xdp_linux.go000066400000000000000000000120011466216277000170750ustar00rootroot00000000000000package netlink import ( "errors" "fmt" "syscall" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) const ( sizeofXDPSocketRequest = 1 + 1 + 2 + 4 + 4 + 2*4 sizeofXDPSocket = 0x10 ) // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L12 type xdpSocketRequest struct { Family uint8 Protocol uint8 pad uint16 Ino uint32 Show uint32 Cookie [2]uint32 } func (r *xdpSocketRequest) Serialize() []byte { b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)} b.Write(r.Family) b.Write(r.Protocol) native.PutUint16(b.Next(2), r.pad) native.PutUint32(b.Next(4), r.Ino) native.PutUint32(b.Next(4), r.Show) native.PutUint32(b.Next(4), r.Cookie[0]) native.PutUint32(b.Next(4), r.Cookie[1]) return b.Bytes } func (r *xdpSocketRequest) Len() int { return sizeofXDPSocketRequest } func (s *XDPSocket) deserialize(b []byte) error { if len(b) < sizeofXDPSocket { return fmt.Errorf("XDP socket data short read (%d); want %d", len(b), sizeofXDPSocket) } rb := readBuffer{Bytes: b} s.Family = rb.Read() s.Type = rb.Read() s.pad = native.Uint16(rb.Next(2)) s.Ino = native.Uint32(rb.Next(4)) s.Cookie[0] = native.Uint32(rb.Next(4)) s.Cookie[1] = native.Uint32(rb.Next(4)) return nil } // XDPSocketGet returns the XDP socket identified by its inode number and/or // socket cookie. Specify the cookie as SOCK_ANY_COOKIE if func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) { // We have a problem here: dumping AF_XDP sockets currently does not support // filtering. We thus need to dump all XSKs and then only filter afterwards // :( xsks, err := SocketDiagXDP() if err != nil { return nil, err } checkCookie := cookie != SOCK_ANY_COOKIE && cookie != 0 crumblingCookie := [2]uint32{uint32(cookie), uint32(cookie >> 32)} checkIno := ino != 0 var xskinfo *XDPDiagInfoResp for _, xsk := range xsks { if checkIno && xsk.XDPDiagMsg.Ino != ino { continue } if checkCookie && xsk.XDPDiagMsg.Cookie != crumblingCookie { continue } if xskinfo != nil { return nil, errors.New("multiple matching XDP sockets") } xskinfo = xsk } if xskinfo == nil { return nil, errors.New("no matching XDP socket") } return xskinfo, nil } // SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets. func SocketDiagXDP() ([]*XDPDiagInfoResp, error) { var result []*XDPDiagInfoResp err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error { sockInfo := &XDPSocket{} if err := sockInfo.deserialize(m.Data); err != nil { return err } attrs, err := nl.ParseRouteAttr(m.Data[sizeofXDPSocket:]) if err != nil { return err } res, err := attrsToXDPDiagInfoResp(attrs, sockInfo) if err != nil { return err } result = append(result, res) return nil }) if err != nil { return nil, err } return result, nil } // socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets. func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error { s, err := nl.Subscribe(unix.NETLINK_INET_DIAG) if err != nil { return err } defer s.Close() req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) req.AddData(&xdpSocketRequest{ Family: unix.AF_XDP, Show: XDP_SHOW_INFO | XDP_SHOW_RING_CFG | XDP_SHOW_UMEM | XDP_SHOW_STATS, }) if err := s.Send(req); err != nil { return err } loop: for { msgs, from, err := s.Receive() if err != nil { return err } if from.Pid != nl.PidKernel { return fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) } if len(msgs) == 0 { return errors.New("no message nor error from netlink") } for _, m := range msgs { switch m.Header.Type { case unix.NLMSG_DONE: break loop case unix.NLMSG_ERROR: error := int32(native.Uint32(m.Data[0:4])) return syscall.Errno(-error) } if err := receiver(m); err != nil { return err } } } return nil } func attrsToXDPDiagInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *XDPSocket) (*XDPDiagInfoResp, error) { resp := &XDPDiagInfoResp{ XDPDiagMsg: sockInfo, XDPInfo: &XDPInfo{}, } for _, a := range attrs { switch a.Attr.Type { case XDP_DIAG_INFO: resp.XDPInfo.Ifindex = native.Uint32(a.Value[0:4]) resp.XDPInfo.QueueID = native.Uint32(a.Value[4:8]) case XDP_DIAG_UID: resp.XDPInfo.UID = native.Uint32(a.Value[0:4]) case XDP_DIAG_RX_RING: resp.XDPInfo.RxRingEntries = native.Uint32(a.Value[0:4]) case XDP_DIAG_TX_RING: resp.XDPInfo.TxRingEntries = native.Uint32(a.Value[0:4]) case XDP_DIAG_UMEM_FILL_RING: resp.XDPInfo.UmemFillRingEntries = native.Uint32(a.Value[0:4]) case XDP_DIAG_UMEM_COMPLETION_RING: resp.XDPInfo.UmemCompletionRingEntries = native.Uint32(a.Value[0:4]) case XDP_DIAG_UMEM: umem := &XDPDiagUmem{} if err := umem.deserialize(a.Value); err != nil { return nil, err } resp.XDPInfo.Umem = umem case XDP_DIAG_STATS: stats := &XDPDiagStats{} if err := stats.deserialize(a.Value); err != nil { return nil, err } resp.XDPInfo.Stats = stats } } return resp, nil } netlink-1.3.0/socket_xdp_linux_test.go000066400000000000000000000020251466216277000201410ustar00rootroot00000000000000//go:build linux // +build linux package netlink import ( "os" "testing" "golang.org/x/sys/unix" ) func TestSocketXDPGetInfo(t *testing.T) { xdpsockfd, err := unix.Socket(unix.AF_XDP, unix.SOCK_RAW, 0) if err != nil { t.Fatal(err) } defer unix.Close(xdpsockfd) wantFamily := unix.AF_XDP var xdpsockstat unix.Stat_t err = unix.Fstat(xdpsockfd, &xdpsockstat) if err != nil { t.Fatal(err) } wantIno := xdpsockstat.Ino result, err := SocketXDPGetInfo(uint32(wantIno), SOCK_ANY_COOKIE) if err != nil { if os.IsNotExist(err) { t.Skip("kernel lacks support for AF_XDP socket diagnosis") } t.Fatal(err) } if got := result.XDPDiagMsg.Family; got != uint8(wantFamily) { t.Fatalf("protocol family = %v, want %v", got, wantFamily) } if got := result.XDPDiagMsg.Ino; got != uint32(wantIno) { t.Fatalf("protocol ino = %v, want %v", got, wantIno) } if result.XDPInfo == nil { t.Fatalf("want non-nil XDPInfo, got nil") } if got := result.XDPInfo.Ifindex; got != 0 { t.Fatalf("ifindex = %v, want 0", got) } } netlink-1.3.0/tcp.go000066400000000000000000000057371466216277000143230ustar00rootroot00000000000000package netlink // TCP States const ( TCP_ESTABLISHED = iota + 0x01 TCP_SYN_SENT TCP_SYN_RECV TCP_FIN_WAIT1 TCP_FIN_WAIT2 TCP_TIME_WAIT TCP_CLOSE TCP_CLOSE_WAIT TCP_LAST_ACK TCP_LISTEN TCP_CLOSING TCP_NEW_SYN_REC TCP_MAX_STATES ) type TCPInfo struct { State uint8 Ca_state uint8 Retransmits uint8 Probes uint8 Backoff uint8 Options uint8 Snd_wscale uint8 // no uint4 Rcv_wscale uint8 Delivery_rate_app_limited uint8 Fastopen_client_fail uint8 Rto uint32 Ato uint32 Snd_mss uint32 Rcv_mss uint32 Unacked uint32 Sacked uint32 Lost uint32 Retrans uint32 Fackets uint32 Last_data_sent uint32 Last_ack_sent uint32 Last_data_recv uint32 Last_ack_recv uint32 Pmtu uint32 Rcv_ssthresh uint32 Rtt uint32 Rttvar uint32 Snd_ssthresh uint32 Snd_cwnd uint32 Advmss uint32 Reordering uint32 Rcv_rtt uint32 Rcv_space uint32 Total_retrans uint32 Pacing_rate uint64 Max_pacing_rate uint64 Bytes_acked uint64 /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ Bytes_received uint64 /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ Segs_out uint32 /* RFC4898 tcpEStatsPerfSegsOut */ Segs_in uint32 /* RFC4898 tcpEStatsPerfSegsIn */ Notsent_bytes uint32 Min_rtt uint32 Data_segs_in uint32 /* RFC4898 tcpEStatsDataSegsIn */ Data_segs_out uint32 /* RFC4898 tcpEStatsDataSegsOut */ Delivery_rate uint64 Busy_time uint64 /* Time (usec) busy sending data */ Rwnd_limited uint64 /* Time (usec) limited by receive window */ Sndbuf_limited uint64 /* Time (usec) limited by send buffer */ Delivered uint32 Delivered_ce uint32 Bytes_sent uint64 /* RFC4898 tcpEStatsPerfHCDataOctetsOut */ Bytes_retrans uint64 /* RFC4898 tcpEStatsPerfOctetsRetrans */ Dsack_dups uint32 /* RFC4898 tcpEStatsStackDSACKDups */ Reord_seen uint32 /* reordering events seen */ Rcv_ooopack uint32 /* Out-of-order packets received */ Snd_wnd uint32 /* peer's advertised receive window after * scaling (bytes) */ } type TCPBBRInfo struct { BBRBW uint64 BBRMinRTT uint32 BBRPacingGain uint32 BBRCwndGain uint32 } // According to https://man7.org/linux/man-pages/man7/sock_diag.7.html type MemInfo struct { RMem uint32 WMem uint32 FMem uint32 TMem uint32 } netlink-1.3.0/tcp_linux.go000066400000000000000000000136741466216277000155410ustar00rootroot00000000000000package netlink import ( "bytes" "errors" "io" ) const ( tcpBBRInfoLen = 20 memInfoLen = 16 ) func checkDeserErr(err error) error { if err == io.EOF { return nil } return err } func (t *TCPInfo) deserialize(b []byte) error { var err error rb := bytes.NewBuffer(b) t.State, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Ca_state, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Retransmits, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Probes, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Backoff, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Options, err = rb.ReadByte() if err != nil { return checkDeserErr(err) } scales, err := rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Snd_wscale = scales >> 4 // first 4 bits t.Rcv_wscale = scales & 0xf // last 4 bits rateLimAndFastOpen, err := rb.ReadByte() if err != nil { return checkDeserErr(err) } t.Delivery_rate_app_limited = rateLimAndFastOpen >> 7 // get first bit t.Fastopen_client_fail = rateLimAndFastOpen >> 5 & 3 // get next two bits next := rb.Next(4) if len(next) == 0 { return nil } t.Rto = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Ato = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Snd_mss = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rcv_mss = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Unacked = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Sacked = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Lost = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Retrans = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Fackets = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Last_data_sent = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Last_ack_sent = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Last_data_recv = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Last_ack_recv = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Pmtu = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rcv_ssthresh = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rtt = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rttvar = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Snd_ssthresh = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Snd_cwnd = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Advmss = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Reordering = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rcv_rtt = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rcv_space = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Total_retrans = native.Uint32(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Pacing_rate = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Max_pacing_rate = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Bytes_acked = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Bytes_received = native.Uint64(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Segs_out = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Segs_in = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Notsent_bytes = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Min_rtt = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Data_segs_in = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Data_segs_out = native.Uint32(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Delivery_rate = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Busy_time = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Rwnd_limited = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Sndbuf_limited = native.Uint64(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Delivered = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Delivered_ce = native.Uint32(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Bytes_sent = native.Uint64(next) next = rb.Next(8) if len(next) == 0 { return nil } t.Bytes_retrans = native.Uint64(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Dsack_dups = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Reord_seen = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Rcv_ooopack = native.Uint32(next) next = rb.Next(4) if len(next) == 0 { return nil } t.Snd_wnd = native.Uint32(next) return nil } func (t *TCPBBRInfo) deserialize(b []byte) error { if len(b) != tcpBBRInfoLen { return errors.New("Invalid length") } rb := bytes.NewBuffer(b) t.BBRBW = native.Uint64(rb.Next(8)) t.BBRMinRTT = native.Uint32(rb.Next(4)) t.BBRPacingGain = native.Uint32(rb.Next(4)) t.BBRCwndGain = native.Uint32(rb.Next(4)) return nil } func (m *MemInfo) deserialize(b []byte) error { if len(b) != memInfoLen { return errors.New("Invalid length") } rb := bytes.NewBuffer(b) m.RMem = native.Uint32(rb.Next(4)) m.WMem = native.Uint32(rb.Next(4)) m.FMem = native.Uint32(rb.Next(4)) m.TMem = native.Uint32(rb.Next(4)) return nil } netlink-1.3.0/tcp_linux_test.go000066400000000000000000000065141466216277000165730ustar00rootroot00000000000000package netlink import ( "reflect" "testing" ) var ( tcpInfoData []byte tcpInfo *TCPInfo ) func init() { tcpInfoData = []byte{ 1, 0, 0, 0, 0, 7, 120, 1, 96, 216, 3, 0, 64, 156, 0, 0, 120, 5, 0, 0, 64, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 236, 216, 0, 0, 0, 0, 0, 0, 56, 216, 0, 0, 144, 39, 0, 0, 220, 5, 0, 0, 88, 250, 0, 0, 79, 190, 0, 0, 7, 5, 0, 0, 255, 255, 255, 127, 10, 0, 0, 0, 168, 5, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 144, 56, 0, 0, 0, 0, 0, 0, 1, 197, 8, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 157, 42, 0, 0, 0, 0, 0, 0, 148, 26, 0, 0, 0, 0, 0, 0, 181, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 93, 180, 0, 0, 61, 0, 0, 0, 89, 0, 0, 0, 47, 216, 1, 0, 0, 0, 0, 0, 32, 65, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 156, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 1, 0, } tcpInfo = &TCPInfo{ State: 1, Options: 7, Snd_wscale: 7, Rcv_wscale: 8, Rto: 252000, Ato: 40000, Snd_mss: 1400, Rcv_mss: 832, Last_data_sent: 55532, Last_data_recv: 55352, Last_ack_recv: 10128, Pmtu: 1500, Rcv_ssthresh: 64088, Rtt: 48719, Rttvar: 1287, Snd_ssthresh: 2147483647, Snd_cwnd: 10, Advmss: 1448, Reordering: 3, Rcv_space: 14480, Pacing_rate: 574721, Max_pacing_rate: 18446744073709551615, Bytes_acked: 10909, Bytes_received: 6804, Segs_out: 181, Segs_in: 95, Min_rtt: 46173, Data_segs_in: 61, Data_segs_out: 89, Delivery_rate: 120879, Busy_time: 1524000, Delivered: 90, Bytes_sent: 10908, Snd_wnd: 115456, } } func TestTCPInfoDeserialize(t *testing.T) { tests := []struct { name string input []byte expected *TCPInfo wantFail bool }{ { name: "Valid data", input: tcpInfoData, expected: tcpInfo, }, } for _, test := range tests { tcpbbr := &TCPInfo{} err := tcpbbr.deserialize(test.input) if err != nil && !test.wantFail { t.Errorf("Unexpected failure for test %q", test.name) continue } if err != nil && test.wantFail { continue } if !reflect.DeepEqual(test.expected, tcpbbr) { t.Errorf("Unexpected failure for test %q", test.name) } } } func TestTCPBBRInfoDeserialize(t *testing.T) { tests := []struct { name string input []byte expected *TCPBBRInfo wantFail bool }{ { name: "Valid data", input: []byte{ 100, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 222, 0, 0, 0, 123, 0, 0, 0, }, expected: &TCPBBRInfo{ BBRBW: 100, BBRMinRTT: 111, BBRPacingGain: 222, BBRCwndGain: 123, }, }, { name: "Invalid length", input: []byte{ 100, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 222, 0, 0, 0, 123, 0, 0, }, wantFail: true, }, } for _, test := range tests { tcpbbr := &TCPBBRInfo{} err := tcpbbr.deserialize(test.input) if err != nil && !test.wantFail { t.Errorf("Unexpected failure for test %q", test.name) continue } if err != nil && test.wantFail { continue } if !reflect.DeepEqual(test.expected, tcpbbr) { t.Errorf("Unexpected failure for test %q", test.name) } } } netlink-1.3.0/testdata/000077500000000000000000000000001466216277000150035ustar00rootroot00000000000000netlink-1.3.0/testdata/ipset_list_result000066400000000000000000000003441466216277000205040ustar00rootroot00000000000000 clients hash:mac<€@@@@ð@@@p€<€ Þ­¾ï@ ù @ @* foo bar0€ @) @ÃÊ @netlink-1.3.0/testdata/ipset_protocol_result000066400000000000000000000000141466216277000213640ustar00rootroot00000000000000netlink-1.3.0/unix_diag.go000066400000000000000000000006501466216277000154710ustar00rootroot00000000000000package netlink // According to linux/include/uapi/linux/unix_diag.h const ( UNIX_DIAG_NAME = iota UNIX_DIAG_VFS UNIX_DIAG_PEER UNIX_DIAG_ICONS UNIX_DIAG_RQLEN UNIX_DIAG_MEMINFO UNIX_DIAG_SHUTDOWN UNIX_DIAG_UID UNIX_DIAG_MAX ) type UnixDiagInfoResp struct { DiagMsg *UnixSocket Name *string Peer *uint32 Queue *QueueInfo Shutdown *uint8 } type QueueInfo struct { RQueue uint32 WQueue uint32 } netlink-1.3.0/vdpa_linux.go000066400000000000000000000320211466216277000156700ustar00rootroot00000000000000package netlink import ( "fmt" "net" "syscall" "golang.org/x/sys/unix" "github.com/vishvananda/netlink/nl" ) type vdpaDevID struct { Name string ID uint32 } // VDPADev contains info about VDPA device type VDPADev struct { vdpaDevID VendorID uint32 MaxVQS uint32 MaxVQSize uint16 MinVQSize uint16 } // VDPADevConfig contains configuration of the VDPA device type VDPADevConfig struct { vdpaDevID Features uint64 NegotiatedFeatures uint64 Net VDPADevConfigNet } // VDPADevVStats conatins vStats for the VDPA device type VDPADevVStats struct { vdpaDevID QueueIndex uint32 Vendor []VDPADevVStatsVendor NegotiatedFeatures uint64 } // VDPADevVStatsVendor conatins name and value for vendor specific vstat option type VDPADevVStatsVendor struct { Name string Value uint64 } // VDPADevConfigNet conatins status and net config for the VDPA device type VDPADevConfigNet struct { Status VDPADevConfigNetStatus Cfg VDPADevConfigNetCfg } // VDPADevConfigNetStatus contains info about net status type VDPADevConfigNetStatus struct { LinkUp bool Announce bool } // VDPADevConfigNetCfg contains net config for the VDPA device type VDPADevConfigNetCfg struct { MACAddr net.HardwareAddr MaxVQP uint16 MTU uint16 } // VDPAMGMTDev conatins info about VDPA management device type VDPAMGMTDev struct { BusName string DevName string SupportedClasses uint64 SupportedFeatures uint64 MaxVQS uint32 } // VDPANewDevParams contains parameters for new VDPA device // use SetBits to configure requried features for the device // example: // // VDPANewDevParams{Features: SetBits(0, VIRTIO_NET_F_MTU, VIRTIO_NET_F_CTRL_MAC_ADDR)} type VDPANewDevParams struct { MACAddr net.HardwareAddr MaxVQP uint16 MTU uint16 Features uint64 } // SetBits set provided bits in the uint64 input value // usage example: // features := SetBits(0, VIRTIO_NET_F_MTU, VIRTIO_NET_F_CTRL_MAC_ADDR) func SetBits(input uint64, pos ...int) uint64 { for _, p := range pos { input |= 1 << uint64(p) } return input } // IsBitSet check if specific bit is set in the uint64 input value // usage example: // hasNetClass := IsBitSet(mgmtDev, VIRTIO_ID_NET) func IsBitSet(input uint64, pos int) bool { val := input & (1 << uint64(pos)) return val > 0 } // VDPANewDev adds new VDPA device // Equivalent to: `vdpa dev add name mgmtdev /mgmtName [params]` func VDPANewDev(name, mgmtBus, mgmtName string, params VDPANewDevParams) error { return pkgHandle.VDPANewDev(name, mgmtBus, mgmtName, params) } // VDPADelDev removes VDPA device // Equivalent to: `vdpa dev del ` func VDPADelDev(name string) error { return pkgHandle.VDPADelDev(name) } // VDPAGetDevList returns list of VDPA devices // Equivalent to: `vdpa dev show` func VDPAGetDevList() ([]*VDPADev, error) { return pkgHandle.VDPAGetDevList() } // VDPAGetDevByName returns VDPA device selected by name // Equivalent to: `vdpa dev show ` func VDPAGetDevByName(name string) (*VDPADev, error) { return pkgHandle.VDPAGetDevByName(name) } // VDPAGetDevConfigList returns list of VDPA devices configurations // Equivalent to: `vdpa dev config show` func VDPAGetDevConfigList() ([]*VDPADevConfig, error) { return pkgHandle.VDPAGetDevConfigList() } // VDPAGetDevConfigByName returns VDPA device configuration selected by name // Equivalent to: `vdpa dev config show ` func VDPAGetDevConfigByName(name string) (*VDPADevConfig, error) { return pkgHandle.VDPAGetDevConfigByName(name) } // VDPAGetDevVStats returns vstats for VDPA device // Equivalent to: `vdpa dev vstats show qidx ` func VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStats, error) { return pkgHandle.VDPAGetDevVStats(name, queueIndex) } // VDPAGetMGMTDevList returns list of mgmt devices // Equivalent to: `vdpa mgmtdev show` func VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { return pkgHandle.VDPAGetMGMTDevList() } // VDPAGetMGMTDevByBusAndName returns mgmt devices selected by bus and name // Equivalent to: `vdpa mgmtdev show /` func VDPAGetMGMTDevByBusAndName(bus, name string) (*VDPAMGMTDev, error) { return pkgHandle.VDPAGetMGMTDevByBusAndName(bus, name) } type vdpaNetlinkMessage []syscall.NetlinkRouteAttr func (id *vdpaDevID) parseIDAttribute(attr syscall.NetlinkRouteAttr) { switch attr.Attr.Type { case nl.VDPA_ATTR_DEV_NAME: id.Name = nl.BytesToString(attr.Value) case nl.VDPA_ATTR_DEV_ID: id.ID = native.Uint32(attr.Value) } } func (netStatus *VDPADevConfigNetStatus) parseStatusAttribute(value []byte) { a := native.Uint16(value) netStatus.Announce = (a & VIRTIO_NET_S_ANNOUNCE) > 0 netStatus.LinkUp = (a & VIRTIO_NET_S_LINK_UP) > 0 } func (d *VDPADev) parseAttributes(attrs vdpaNetlinkMessage) { for _, a := range attrs { d.parseIDAttribute(a) switch a.Attr.Type { case nl.VDPA_ATTR_DEV_VENDOR_ID: d.VendorID = native.Uint32(a.Value) case nl.VDPA_ATTR_DEV_MAX_VQS: d.MaxVQS = native.Uint32(a.Value) case nl.VDPA_ATTR_DEV_MAX_VQ_SIZE: d.MaxVQSize = native.Uint16(a.Value) case nl.VDPA_ATTR_DEV_MIN_VQ_SIZE: d.MinVQSize = native.Uint16(a.Value) } } } func (c *VDPADevConfig) parseAttributes(attrs vdpaNetlinkMessage) { for _, a := range attrs { c.parseIDAttribute(a) switch a.Attr.Type { case nl.VDPA_ATTR_DEV_NET_CFG_MACADDR: c.Net.Cfg.MACAddr = a.Value case nl.VDPA_ATTR_DEV_NET_STATUS: c.Net.Status.parseStatusAttribute(a.Value) case nl.VDPA_ATTR_DEV_NET_CFG_MAX_VQP: c.Net.Cfg.MaxVQP = native.Uint16(a.Value) case nl.VDPA_ATTR_DEV_NET_CFG_MTU: c.Net.Cfg.MTU = native.Uint16(a.Value) case nl.VDPA_ATTR_DEV_FEATURES: c.Features = native.Uint64(a.Value) case nl.VDPA_ATTR_DEV_NEGOTIATED_FEATURES: c.NegotiatedFeatures = native.Uint64(a.Value) } } } func (s *VDPADevVStats) parseAttributes(attrs vdpaNetlinkMessage) { for _, a := range attrs { s.parseIDAttribute(a) switch a.Attr.Type { case nl.VDPA_ATTR_DEV_QUEUE_INDEX: s.QueueIndex = native.Uint32(a.Value) case nl.VDPA_ATTR_DEV_VENDOR_ATTR_NAME: s.Vendor = append(s.Vendor, VDPADevVStatsVendor{Name: nl.BytesToString(a.Value)}) case nl.VDPA_ATTR_DEV_VENDOR_ATTR_VALUE: if len(s.Vendor) == 0 { break } s.Vendor[len(s.Vendor)-1].Value = native.Uint64(a.Value) case nl.VDPA_ATTR_DEV_NEGOTIATED_FEATURES: s.NegotiatedFeatures = native.Uint64(a.Value) } } } func (d *VDPAMGMTDev) parseAttributes(attrs vdpaNetlinkMessage) { for _, a := range attrs { switch a.Attr.Type { case nl.VDPA_ATTR_MGMTDEV_BUS_NAME: d.BusName = nl.BytesToString(a.Value) case nl.VDPA_ATTR_MGMTDEV_DEV_NAME: d.DevName = nl.BytesToString(a.Value) case nl.VDPA_ATTR_MGMTDEV_SUPPORTED_CLASSES: d.SupportedClasses = native.Uint64(a.Value) case nl.VDPA_ATTR_DEV_SUPPORTED_FEATURES: d.SupportedFeatures = native.Uint64(a.Value) case nl.VDPA_ATTR_DEV_MGMTDEV_MAX_VQS: d.MaxVQS = native.Uint32(a.Value) } } } func (h *Handle) vdpaRequest(command uint8, extraFlags int, attrs []*nl.RtAttr) ([]vdpaNetlinkMessage, error) { f, err := h.GenlFamilyGet(nl.VDPA_GENL_NAME) if err != nil { return nil, err } req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_ACK|extraFlags) req.AddData(&nl.Genlmsg{ Command: command, Version: nl.VDPA_GENL_VERSION, }) for _, a := range attrs { req.AddData(a) } resp, err := req.Execute(unix.NETLINK_GENERIC, 0) if err != nil { return nil, err } messages := make([]vdpaNetlinkMessage, 0, len(resp)) for _, m := range resp { attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) if err != nil { return nil, err } messages = append(messages, attrs) } return messages, nil } // dump all devices if dev is nil func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) { var extraFlags int var attrs []*nl.RtAttr if dev != nil { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_NAME, nl.ZeroTerminated(*dev))) } else { extraFlags = extraFlags | unix.NLM_F_DUMP } messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_GET, extraFlags, attrs) if err != nil { return nil, err } devs := make([]*VDPADev, 0, len(messages)) for _, m := range messages { d := &VDPADev{} d.parseAttributes(m) devs = append(devs, d) } return devs, nil } // dump all devices if dev is nil func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) { var extraFlags int var attrs []*nl.RtAttr if dev != nil { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_NAME, nl.ZeroTerminated(*dev))) } else { extraFlags = extraFlags | unix.NLM_F_DUMP } messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_CONFIG_GET, extraFlags, attrs) if err != nil { return nil, err } cfgs := make([]*VDPADevConfig, 0, len(messages)) for _, m := range messages { cfg := &VDPADevConfig{} cfg.parseAttributes(m) cfgs = append(cfgs, cfg) } return cfgs, nil } // dump all devices if dev is nil func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) { var extraFlags int var attrs []*nl.RtAttr if dev != nil { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_MGMTDEV_DEV_NAME, nl.ZeroTerminated(*dev)), ) if bus != nil { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_MGMTDEV_BUS_NAME, nl.ZeroTerminated(*bus)), ) } } else { extraFlags = extraFlags | unix.NLM_F_DUMP } messages, err := h.vdpaRequest(nl.VDPA_CMD_MGMTDEV_GET, extraFlags, attrs) if err != nil { return nil, err } cfgs := make([]*VDPAMGMTDev, 0, len(messages)) for _, m := range messages { cfg := &VDPAMGMTDev{} cfg.parseAttributes(m) cfgs = append(cfgs, cfg) } return cfgs, nil } // VDPANewDev adds new VDPA device // Equivalent to: `vdpa dev add name mgmtdev /mgmtName [params]` func (h *Handle) VDPANewDev(name, mgmtBus, mgmtName string, params VDPANewDevParams) error { attrs := []*nl.RtAttr{ nl.NewRtAttr(nl.VDPA_ATTR_DEV_NAME, nl.ZeroTerminated(name)), nl.NewRtAttr(nl.VDPA_ATTR_MGMTDEV_DEV_NAME, nl.ZeroTerminated(mgmtName)), } if mgmtBus != "" { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_MGMTDEV_BUS_NAME, nl.ZeroTerminated(mgmtBus))) } if len(params.MACAddr) != 0 { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_NET_CFG_MACADDR, params.MACAddr)) } if params.MaxVQP > 0 { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_NET_CFG_MAX_VQP, nl.Uint16Attr(params.MaxVQP))) } if params.MTU > 0 { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_NET_CFG_MTU, nl.Uint16Attr(params.MTU))) } if params.Features > 0 { attrs = append(attrs, nl.NewRtAttr(nl.VDPA_ATTR_DEV_FEATURES, nl.Uint64Attr(params.Features))) } _, err := h.vdpaRequest(nl.VDPA_CMD_DEV_NEW, 0, attrs) return err } // VDPADelDev removes VDPA device // Equivalent to: `vdpa dev del ` func (h *Handle) VDPADelDev(name string) error { _, err := h.vdpaRequest(nl.VDPA_CMD_DEV_DEL, 0, []*nl.RtAttr{ nl.NewRtAttr(nl.VDPA_ATTR_DEV_NAME, nl.ZeroTerminated(name))}) return err } // VDPAGetDevList returns list of VDPA devices // Equivalent to: `vdpa dev show` func (h *Handle) VDPAGetDevList() ([]*VDPADev, error) { return h.vdpaDevGet(nil) } // VDPAGetDevByName returns VDPA device selected by name // Equivalent to: `vdpa dev show ` func (h *Handle) VDPAGetDevByName(name string) (*VDPADev, error) { devs, err := h.vdpaDevGet(&name) if err != nil { return nil, err } if len(devs) == 0 { return nil, fmt.Errorf("device not found") } return devs[0], nil } // VDPAGetDevConfigList returns list of VDPA devices configurations // Equivalent to: `vdpa dev config show` func (h *Handle) VDPAGetDevConfigList() ([]*VDPADevConfig, error) { return h.vdpaDevConfigGet(nil) } // VDPAGetDevConfigByName returns VDPA device configuration selected by name // Equivalent to: `vdpa dev config show ` func (h *Handle) VDPAGetDevConfigByName(name string) (*VDPADevConfig, error) { cfgs, err := h.vdpaDevConfigGet(&name) if err != nil { return nil, err } if len(cfgs) == 0 { return nil, fmt.Errorf("configuration not found") } return cfgs[0], nil } // VDPAGetDevVStats returns vstats for VDPA device // Equivalent to: `vdpa dev vstats show qidx ` func (h *Handle) VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStats, error) { messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_VSTATS_GET, 0, []*nl.RtAttr{ nl.NewRtAttr(nl.VDPA_ATTR_DEV_NAME, nl.ZeroTerminated(name)), nl.NewRtAttr(nl.VDPA_ATTR_DEV_QUEUE_INDEX, nl.Uint32Attr(queueIndex)), }) if err != nil { return nil, err } if len(messages) == 0 { return nil, fmt.Errorf("stats not found") } stats := &VDPADevVStats{} stats.parseAttributes(messages[0]) return stats, nil } // VDPAGetMGMTDevList returns list of mgmt devices // Equivalent to: `vdpa mgmtdev show` func (h *Handle) VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { return h.vdpaMGMTDevGet(nil, nil) } // VDPAGetMGMTDevByBusAndName returns mgmt devices selected by bus and name // Equivalent to: `vdpa mgmtdev show /` func (h *Handle) VDPAGetMGMTDevByBusAndName(bus, name string) (*VDPAMGMTDev, error) { var busPtr *string if bus != "" { busPtr = &bus } devs, err := h.vdpaMGMTDevGet(busPtr, &name) if err != nil { return nil, err } if len(devs) == 0 { return nil, fmt.Errorf("mgmtdev not found") } return devs[0], nil } netlink-1.3.0/vdpa_linux_test.go000066400000000000000000000166041466216277000167400ustar00rootroot00000000000000package netlink import ( "errors" "syscall" "testing" "github.com/vishvananda/netlink/nl" ) // tests in this package require following modules: vdpa, vdpa_sim, vdpa_sim_net // The vpda_sim_net module creates virtual VDPA mgmt device with name vdpasim_net. const ( vdpaSimMGMTDev = "vdpasim_net" vdpaTestDeviceName = "__nl_test_dev" ) var ( vdapTestReqModules = []string{"vdpa", "vdpa_sim", "vdpa_sim_net"} ) func setupVDPATest(t *testing.T, reqCommands ...int) func() { t.Helper() skipUnlessRoot(t) skipUnlessKModuleLoaded(t, vdapTestReqModules...) gFam, err := GenlFamilyGet(nl.VDPA_GENL_NAME) if err != nil { t.Skip("can't check for supported VDPA commands") } for _, c := range reqCommands { found := false for _, supportedOpt := range gFam.Ops { if supportedOpt.ID == uint32(c) { found = true } } if !found { t.Skip("host doesn't support required VDPA command for the test") } } return func() { _ = VDPADelDev(vdpaTestDeviceName) } } func TestVDPAGetMGMTDevList(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_MGMTDEV_GET)() mgmtDevs, err := VDPAGetMGMTDevList() if err != nil { t.Fatalf("Failed to list VDPA mgmt devs: %v", err) } simMGMTFound := false for _, d := range mgmtDevs { if d.DevName != vdpaSimMGMTDev || d.BusName != "" { continue } simMGMTFound = true checkVDPAMGMTDev(t, d) } if !simMGMTFound { t.Fatal("VDPA vdpasim_net MGMT device not found") } } func TestVDPAGetMGMTDevByBusAndName(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_MGMTDEV_GET)() mgmtDev, err := VDPAGetMGMTDevByBusAndName("", vdpaSimMGMTDev) if err != nil { t.Fatalf("Failed to get VDPA sim mgmt dev: %v", err) } checkVDPAMGMTDev(t, mgmtDev) if mgmtDev.DevName != vdpaSimMGMTDev || mgmtDev.BusName != "" { t.Fatalf("Invalid device received for Get call, expected: %s, actual: %s", vdpaSimMGMTDev, mgmtDev.DevName) } } func TestVDPAGetMGMTDevByBusAndName_Unknown_Device(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_MGMTDEV_GET)() _, err := VDPAGetMGMTDevByBusAndName("pci", "__should_not_exist") if !errors.Is(err, syscall.ENODEV) { t.Fatal("VDPAGetMGMTDevByBusAndName returns unexpected error for unknown device") } } func TestVDPANewDev(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_NEW)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } _, err := VDPAGetDevByName(vdpaTestDeviceName) if err != nil { t.Fatalf("failed to get created VDPA devvice: %v", err) } } func TestVDPANewDev_Already_Exist(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_NEW)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } err := createVDPATestDev() if !errors.Is(err, syscall.EEXIST) { t.Fatal("VDPANewDev returns unexpected error for device which is already exist") } } func TestVDPANewDev_Unknown_MGMT_DEV(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_NEW)() err := VDPANewDev(vdpaTestDeviceName, "", "__should_not_exist", VDPANewDevParams{}) if !errors.Is(err, syscall.ENODEV) { t.Fatal("VDPANewDev returns unexpected error for unknown mgmt device") } } func TestVDPADelDev(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_DEL, nl.VDPA_CMD_DEV_NEW)() defer setupVDPATest(t)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } if err := VDPADelDev(vdpaTestDeviceName); err != nil { t.Fatalf("VDPADelDev failed: %v", err) } } func TestVDPADelDev_Unknown_Device(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_DEL)() err := VDPADelDev("__should_not_exist") if !errors.Is(err, syscall.ENODEV) { t.Fatal("VDPADelDev returns unexpected error for unknown device") } } func TestVDPAGetDevList(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_NEW)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } devs, err := VDPAGetDevList() if err != nil { t.Fatalf("VDPAGetDevList failed: %v", err) } testDevFound := false for _, d := range devs { if d.Name != vdpaTestDeviceName { continue } testDevFound = true checkVDPADev(t, d) } if !testDevFound { t.Fatal("VDPA test device not found") } } func TestVDPAGetDevByName(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_NEW)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } dev, err := VDPAGetDevByName(vdpaTestDeviceName) if err != nil { t.Fatalf("VDPAGetDevByName failed: %v", err) } checkVDPADev(t, dev) if dev.Name != vdpaTestDeviceName { t.Fatalf("Invalid device received for Get call, expected: %s, actual: %s", vdpaTestDeviceName, dev.Name) } } func TestVDPAGetDevByName_Unknown(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET)() _, err := VDPAGetDevByName("__should_not_exist") if !errors.Is(err, syscall.ENODEV) { t.Fatal("VDPAGetDevByName returns unexpected error for unknown device") } } func TestVDPAGetDevConfigList(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_CONFIG_GET)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } devConfs, err := VDPAGetDevConfigList() if err != nil { t.Fatalf("VDPAGetDevConfigList failed: %v", err) } testDevConfFound := false for _, d := range devConfs { if d.Name != vdpaTestDeviceName { continue } testDevConfFound = true checkVDPADevConf(t, d) } if !testDevConfFound { t.Fatal("VDPA test device config not found") } } func TestVDPAGetDevConfigByName(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_CONFIG_GET)() if err := createVDPATestDev(); err != nil { t.Fatalf("failed to create VDPA device: %v", err) } dev, err := VDPAGetDevConfigByName(vdpaTestDeviceName) if err != nil { t.Fatalf("VDPAGetDevConfigByName failed: %v", err) } checkVDPADevConf(t, dev) if dev.Name != vdpaTestDeviceName { t.Fatalf("Invalid device received for Get call, expected: %s, actual: %s", vdpaTestDeviceName, dev.Name) } } func TestVDPAGetDevConfigByName_Unknowm(t *testing.T) { defer setupVDPATest(t, nl.VDPA_CMD_DEV_GET, nl.VDPA_CMD_DEV_CONFIG_GET)() _, err := VDPAGetDevConfigByName("__should_not_exist") if !errors.Is(err, syscall.ENODEV) { t.Fatal("VDPAGetDevConfigByName returns unexpected error for unknown device") } } func TestSetGetBits(t *testing.T) { features := SetBits(0, VIRTIO_NET_F_CSUM, VIRTIO_NET_F_MQ) if !IsBitSet(features, VIRTIO_NET_F_CSUM) || !IsBitSet(features, VIRTIO_NET_F_MQ) { t.Fatal("BitSet test failed") } if IsBitSet(features, VIRTIO_NET_F_STATUS) { t.Fatal("unexpected bit is set") } } func createVDPATestDev() error { return VDPANewDev(vdpaTestDeviceName, "", vdpaSimMGMTDev, VDPANewDevParams{}) } func checkVDPAMGMTDev(t *testing.T, d *VDPAMGMTDev) { if d == nil { t.Fatal("VDPA MGMT dev is nil") } if d.DevName == "" { t.Fatal("VDPA MGMT dev name is not set") } } func checkVDPADev(t *testing.T, d *VDPADev) { if d == nil { t.Fatal("VDPA dev is nil") } if d.Name == "" { t.Fatal("VDPA dev name is not set") } if d.ID == 0 { t.Fatal("VDPA dev ID is not set") } } func checkVDPADevConf(t *testing.T, d *VDPADevConfig) { if d == nil { t.Fatal("VDPA dev config is nil") } if d.Name == "" { t.Fatal("VDPA dev name is not set") } if d.ID == 0 { t.Fatal("VDPA dev ID is not set") } } netlink-1.3.0/virtio.go000066400000000000000000000152031466216277000150360ustar00rootroot00000000000000package netlink // features for virtio net const ( VIRTIO_NET_F_CSUM = 0 // Host handles pkts w/ partial csum VIRTIO_NET_F_GUEST_CSUM = 1 // Guest handles pkts w/ partial csum VIRTIO_NET_F_CTRL_GUEST_OFFLOADS = 2 // Dynamic offload configuration. VIRTIO_NET_F_MTU = 3 // Initial MTU advice VIRTIO_NET_F_MAC = 5 // Host has given MAC address. VIRTIO_NET_F_GUEST_TSO4 = 7 // Guest can handle TSOv4 in. VIRTIO_NET_F_GUEST_TSO6 = 8 // Guest can handle TSOv6 in. VIRTIO_NET_F_GUEST_ECN = 9 // Guest can handle TSO[6] w/ ECN in. VIRTIO_NET_F_GUEST_UFO = 10 // Guest can handle UFO in. VIRTIO_NET_F_HOST_TSO4 = 11 // Host can handle TSOv4 in. VIRTIO_NET_F_HOST_TSO6 = 12 // Host can handle TSOv6 in. VIRTIO_NET_F_HOST_ECN = 13 // Host can handle TSO[6] w/ ECN in. VIRTIO_NET_F_HOST_UFO = 14 // Host can handle UFO in. VIRTIO_NET_F_MRG_RXBUF = 15 // Host can merge receive buffers. VIRTIO_NET_F_STATUS = 16 // virtio_net_config.status available VIRTIO_NET_F_CTRL_VQ = 17 // Control channel available VIRTIO_NET_F_CTRL_RX = 18 // Control channel RX mode support VIRTIO_NET_F_CTRL_VLAN = 19 // Control channel VLAN filtering VIRTIO_NET_F_CTRL_RX_EXTRA = 20 // Extra RX mode control support VIRTIO_NET_F_GUEST_ANNOUNCE = 21 // Guest can announce device on the* network VIRTIO_NET_F_MQ = 22 // Device supports Receive Flow Steering VIRTIO_NET_F_CTRL_MAC_ADDR = 23 // Set MAC address VIRTIO_NET_F_VQ_NOTF_COAL = 52 // Device supports virtqueue notification coalescing VIRTIO_NET_F_NOTF_COAL = 53 // Device supports notifications coalescing VIRTIO_NET_F_GUEST_USO4 = 54 // Guest can handle USOv4 in. VIRTIO_NET_F_GUEST_USO6 = 55 // Guest can handle USOv6 in. VIRTIO_NET_F_HOST_USO = 56 // Host can handle USO in. VIRTIO_NET_F_HASH_REPORT = 57 // Supports hash report VIRTIO_NET_F_GUEST_HDRLEN = 59 // Guest provides the exact hdr_len value. VIRTIO_NET_F_RSS = 60 // Supports RSS RX steering VIRTIO_NET_F_RSC_EXT = 61 // extended coalescing info VIRTIO_NET_F_STANDBY = 62 // Act as standby for another device with the same MAC. VIRTIO_NET_F_SPEED_DUPLEX = 63 // Device set linkspeed and duplex VIRTIO_NET_F_GSO = 6 // Host handles pkts any GSO type ) // virtio net status const ( VIRTIO_NET_S_LINK_UP = 1 // Link is up VIRTIO_NET_S_ANNOUNCE = 2 // Announcement is needed ) // virtio config const ( // Do we get callbacks when the ring is completely used, even if we've // suppressed them? VIRTIO_F_NOTIFY_ON_EMPTY = 24 // Can the device handle any descriptor layout? VIRTIO_F_ANY_LAYOUT = 27 // v1.0 compliant VIRTIO_F_VERSION_1 = 32 // If clear - device has the platform DMA (e.g. IOMMU) bypass quirk feature. // If set - use platform DMA tools to access the memory. // Note the reverse polarity (compared to most other features), // this is for compatibility with legacy systems. VIRTIO_F_ACCESS_PLATFORM = 33 // Legacy name for VIRTIO_F_ACCESS_PLATFORM (for compatibility with old userspace) VIRTIO_F_IOMMU_PLATFORM = VIRTIO_F_ACCESS_PLATFORM // This feature indicates support for the packed virtqueue layout. VIRTIO_F_RING_PACKED = 34 // Inorder feature indicates that all buffers are used by the device // in the same order in which they have been made available. VIRTIO_F_IN_ORDER = 35 // This feature indicates that memory accesses by the driver and the // device are ordered in a way described by the platform. VIRTIO_F_ORDER_PLATFORM = 36 // Does the device support Single Root I/O Virtualization? VIRTIO_F_SR_IOV = 37 // This feature indicates that the driver passes extra data (besides // identifying the virtqueue) in its device notifications. VIRTIO_F_NOTIFICATION_DATA = 38 // This feature indicates that the driver uses the data provided by the device // as a virtqueue identifier in available buffer notifications. VIRTIO_F_NOTIF_CONFIG_DATA = 39 // This feature indicates that the driver can reset a queue individually. VIRTIO_F_RING_RESET = 40 ) // virtio device ids const ( VIRTIO_ID_NET = 1 // virtio net VIRTIO_ID_BLOCK = 2 // virtio block VIRTIO_ID_CONSOLE = 3 // virtio console VIRTIO_ID_RNG = 4 // virtio rng VIRTIO_ID_BALLOON = 5 // virtio balloon VIRTIO_ID_IOMEM = 6 // virtio ioMemory VIRTIO_ID_RPMSG = 7 // virtio remote processor messaging VIRTIO_ID_SCSI = 8 // virtio scsi VIRTIO_ID_9P = 9 // 9p virtio console VIRTIO_ID_MAC80211_WLAN = 10 // virtio WLAN MAC VIRTIO_ID_RPROC_SERIAL = 11 // virtio remoteproc serial link VIRTIO_ID_CAIF = 12 // Virtio caif VIRTIO_ID_MEMORY_BALLOON = 13 // virtio memory balloon VIRTIO_ID_GPU = 16 // virtio GPU VIRTIO_ID_CLOCK = 17 // virtio clock/timer VIRTIO_ID_INPUT = 18 // virtio input VIRTIO_ID_VSOCK = 19 // virtio vsock transport VIRTIO_ID_CRYPTO = 20 // virtio crypto VIRTIO_ID_SIGNAL_DIST = 21 // virtio signal distribution device VIRTIO_ID_PSTORE = 22 // virtio pstore device VIRTIO_ID_IOMMU = 23 // virtio IOMMU VIRTIO_ID_MEM = 24 // virtio mem VIRTIO_ID_SOUND = 25 // virtio sound VIRTIO_ID_FS = 26 // virtio filesystem VIRTIO_ID_PMEM = 27 // virtio pmem VIRTIO_ID_RPMB = 28 // virtio rpmb VIRTIO_ID_MAC80211_HWSIM = 29 // virtio mac80211-hwsim VIRTIO_ID_VIDEO_ENCODER = 30 // virtio video encoder VIRTIO_ID_VIDEO_DECODER = 31 // virtio video decoder VIRTIO_ID_SCMI = 32 // virtio SCMI VIRTIO_ID_NITRO_SEC_MOD = 33 // virtio nitro secure module VIRTIO_ID_I2C_ADAPTER = 34 // virtio i2c adapter VIRTIO_ID_WATCHDOG = 35 // virtio watchdog VIRTIO_ID_CAN = 36 // virtio can VIRTIO_ID_DMABUF = 37 // virtio dmabuf VIRTIO_ID_PARAM_SERV = 38 // virtio parameter server VIRTIO_ID_AUDIO_POLICY = 39 // virtio audio policy VIRTIO_ID_BT = 40 // virtio bluetooth VIRTIO_ID_GPIO = 41 // virtio gpio // Virtio Transitional IDs VIRTIO_TRANS_ID_NET = 0x1000 // transitional virtio net VIRTIO_TRANS_ID_BLOCK = 0x1001 // transitional virtio block VIRTIO_TRANS_ID_BALLOON = 0x1002 // transitional virtio balloon VIRTIO_TRANS_ID_CONSOLE = 0x1003 // transitional virtio console VIRTIO_TRANS_ID_SCSI = 0x1004 // transitional virtio SCSI VIRTIO_TRANS_ID_RNG = 0x1005 // transitional virtio rng VIRTIO_TRANS_ID_9P = 0x1009 // transitional virtio 9p console ) netlink-1.3.0/xdp_diag.go000066400000000000000000000021331466216277000152770ustar00rootroot00000000000000package netlink import "github.com/vishvananda/netlink/nl" const SOCK_ANY_COOKIE = uint64(nl.TCPDIAG_NOCOOKIE)<<32 + uint64(nl.TCPDIAG_NOCOOKIE) // XDP diagnosis show flag constants to request particular information elements. const ( XDP_SHOW_INFO = 1 << iota XDP_SHOW_RING_CFG XDP_SHOW_UMEM XDP_SHOW_MEMINFO XDP_SHOW_STATS ) // XDP diag element constants const ( XDP_DIAG_NONE = iota XDP_DIAG_INFO // when using XDP_SHOW_INFO XDP_DIAG_UID // when using XDP_SHOW_INFO XDP_DIAG_RX_RING // when using XDP_SHOW_RING_CFG XDP_DIAG_TX_RING // when using XDP_SHOW_RING_CFG XDP_DIAG_UMEM // when using XDP_SHOW_UMEM XDP_DIAG_UMEM_FILL_RING // when using XDP_SHOW_UMEM XDP_DIAG_UMEM_COMPLETION_RING // when using XDP_SHOW_UMEM XDP_DIAG_MEMINFO // when using XDP_SHOW_MEMINFO XDP_DIAG_STATS // when using XDP_SHOW_STATS ) // https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21 type XDPDiagInfoResp struct { XDPDiagMsg *XDPSocket XDPInfo *XDPInfo } netlink-1.3.0/xdp_linux.go000066400000000000000000000021761466216277000155410ustar00rootroot00000000000000package netlink import ( "bytes" "fmt" ) const ( xdrDiagUmemLen = 8 + 8*4 xdrDiagStatsLen = 6 * 8 ) func (x *XDPDiagUmem) deserialize(b []byte) error { if len(b) < xdrDiagUmemLen { return fmt.Errorf("XDP umem diagnosis data short read (%d); want %d", len(b), xdrDiagUmemLen) } rb := bytes.NewBuffer(b) x.Size = native.Uint64(rb.Next(8)) x.ID = native.Uint32(rb.Next(4)) x.NumPages = native.Uint32(rb.Next(4)) x.ChunkSize = native.Uint32(rb.Next(4)) x.Headroom = native.Uint32(rb.Next(4)) x.Ifindex = native.Uint32(rb.Next(4)) x.QueueID = native.Uint32(rb.Next(4)) x.Flags = native.Uint32(rb.Next(4)) x.Refs = native.Uint32(rb.Next(4)) return nil } func (x *XDPDiagStats) deserialize(b []byte) error { if len(b) < xdrDiagStatsLen { return fmt.Errorf("XDP diagnosis statistics short read (%d); want %d", len(b), xdrDiagStatsLen) } rb := bytes.NewBuffer(b) x.RxDropped = native.Uint64(rb.Next(8)) x.RxInvalid = native.Uint64(rb.Next(8)) x.RxFull = native.Uint64(rb.Next(8)) x.FillRingEmpty = native.Uint64(rb.Next(8)) x.TxInvalid = native.Uint64(rb.Next(8)) x.TxRingEmpty = native.Uint64(rb.Next(8)) return nil } netlink-1.3.0/xfrm_linux.go000066400000000000000000000027131466216277000157170ustar00rootroot00000000000000package netlink import ( "fmt" "golang.org/x/sys/unix" ) // Proto is an enum representing an ipsec protocol. type Proto uint8 const ( XFRM_PROTO_ROUTE2 Proto = unix.IPPROTO_ROUTING XFRM_PROTO_ESP Proto = unix.IPPROTO_ESP XFRM_PROTO_AH Proto = unix.IPPROTO_AH XFRM_PROTO_HAO Proto = unix.IPPROTO_DSTOPTS XFRM_PROTO_COMP Proto = unix.IPPROTO_COMP XFRM_PROTO_IPSEC_ANY Proto = unix.IPPROTO_RAW ) func (p Proto) String() string { switch p { case XFRM_PROTO_ROUTE2: return "route2" case XFRM_PROTO_ESP: return "esp" case XFRM_PROTO_AH: return "ah" case XFRM_PROTO_HAO: return "hao" case XFRM_PROTO_COMP: return "comp" case XFRM_PROTO_IPSEC_ANY: return "ipsec-any" } return fmt.Sprintf("%d", p) } // Mode is an enum representing an ipsec transport. type Mode uint8 const ( XFRM_MODE_TRANSPORT Mode = iota XFRM_MODE_TUNNEL XFRM_MODE_ROUTEOPTIMIZATION XFRM_MODE_IN_TRIGGER XFRM_MODE_BEET XFRM_MODE_MAX ) func (m Mode) String() string { switch m { case XFRM_MODE_TRANSPORT: return "transport" case XFRM_MODE_TUNNEL: return "tunnel" case XFRM_MODE_ROUTEOPTIMIZATION: return "ro" case XFRM_MODE_IN_TRIGGER: return "in_trigger" case XFRM_MODE_BEET: return "beet" } return fmt.Sprintf("%d", m) } // XfrmMark represents the mark associated to the state or policy type XfrmMark struct { Value uint32 Mask uint32 } func (m *XfrmMark) String() string { return fmt.Sprintf("(0x%x,0x%x)", m.Value, m.Mask) } netlink-1.3.0/xfrm_monitor_linux.go000066400000000000000000000034701466216277000174670ustar00rootroot00000000000000package netlink import ( "fmt" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) type XfrmMsg interface { Type() nl.XfrmMsgType } type XfrmMsgExpire struct { XfrmState *XfrmState Hard bool } func (ue *XfrmMsgExpire) Type() nl.XfrmMsgType { return nl.XFRM_MSG_EXPIRE } func parseXfrmMsgExpire(b []byte) *XfrmMsgExpire { var e XfrmMsgExpire msg := nl.DeserializeXfrmUserExpire(b) e.XfrmState = xfrmStateFromXfrmUsersaInfo(&msg.XfrmUsersaInfo) e.Hard = msg.Hard == 1 return &e } func XfrmMonitor(ch chan<- XfrmMsg, done <-chan struct{}, errorChan chan<- error, types ...nl.XfrmMsgType) error { groups, err := xfrmMcastGroups(types) if err != nil { return nil } s, err := nl.SubscribeAt(netns.None(), netns.None(), unix.NETLINK_XFRM, groups...) if err != nil { return err } if done != nil { go func() { <-done s.Close() }() } go func() { defer close(ch) for { msgs, from, err := s.Receive() if err != nil { errorChan <- err return } if from.Pid != nl.PidKernel { errorChan <- fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel) return } for _, m := range msgs { switch m.Header.Type { case nl.XFRM_MSG_EXPIRE: ch <- parseXfrmMsgExpire(m.Data) default: errorChan <- fmt.Errorf("unsupported msg type: %x", m.Header.Type) } } } }() return nil } func xfrmMcastGroups(types []nl.XfrmMsgType) ([]uint, error) { groups := make([]uint, 0) if len(types) == 0 { return nil, fmt.Errorf("no xfrm msg type specified") } for _, t := range types { var group uint switch t { case nl.XFRM_MSG_EXPIRE: group = nl.XFRMNLGRP_EXPIRE default: return nil, fmt.Errorf("unsupported group: %x", t) } groups = append(groups, group) } return groups, nil } netlink-1.3.0/xfrm_monitor_linux_test.go000066400000000000000000000022441466216277000205240ustar00rootroot00000000000000package netlink import ( "os" "testing" "github.com/vishvananda/netlink/nl" ) func TestXfrmMonitorExpire(t *testing.T) { if os.Getenv("CI") == "true" { t.Skipf("Flaky in CI: Intermittently causes 10 minute timeout") } defer setUpNetlinkTest(t)() ch := make(chan XfrmMsg) done := make(chan struct{}) defer close(done) errChan := make(chan error) if err := XfrmMonitor(ch, nil, errChan, nl.XFRM_MSG_EXPIRE); err != nil { t.Fatal(err) } // Program state with limits state := getBaseState() state.Limits.TimeHard = 2 state.Limits.TimeSoft = 1 if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } hardFound := false softFound := false msg := (<-ch).(*XfrmMsgExpire) if msg.XfrmState.Spi != state.Spi { t.Fatal("Received unexpected msg, spi does not match") } hardFound = msg.Hard || hardFound softFound = !msg.Hard || softFound msg = (<-ch).(*XfrmMsgExpire) if msg.XfrmState.Spi != state.Spi { t.Fatal("Received unexpected msg, spi does not match") } hardFound = msg.Hard || hardFound softFound = !msg.Hard || softFound if !hardFound || !softFound { t.Fatal("Missing expire msg: hard found:", hardFound, "soft found:", softFound) } } netlink-1.3.0/xfrm_policy_linux.go000066400000000000000000000244721466216277000173040ustar00rootroot00000000000000package netlink import ( "fmt" "net" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // Dir is an enum representing an ipsec template direction. type Dir uint8 const ( XFRM_DIR_IN Dir = iota XFRM_DIR_OUT XFRM_DIR_FWD XFRM_SOCKET_IN XFRM_SOCKET_OUT XFRM_SOCKET_FWD ) func (d Dir) String() string { switch d { case XFRM_DIR_IN: return "dir in" case XFRM_DIR_OUT: return "dir out" case XFRM_DIR_FWD: return "dir fwd" case XFRM_SOCKET_IN: return "socket in" case XFRM_SOCKET_OUT: return "socket out" case XFRM_SOCKET_FWD: return "socket fwd" } return fmt.Sprintf("socket %d", d-XFRM_SOCKET_IN) } // PolicyAction is an enum representing an ipsec policy action. type PolicyAction uint8 const ( XFRM_POLICY_ALLOW PolicyAction = 0 XFRM_POLICY_BLOCK PolicyAction = 1 ) func (a PolicyAction) String() string { switch a { case XFRM_POLICY_ALLOW: return "allow" case XFRM_POLICY_BLOCK: return "block" default: return fmt.Sprintf("action %d", a) } } // XfrmPolicyTmpl encapsulates a rule for the base addresses of an ipsec // policy. These rules are matched with XfrmState to determine encryption // and authentication algorithms. type XfrmPolicyTmpl struct { Dst net.IP Src net.IP Proto Proto Mode Mode Spi int Reqid int Optional int } func (t XfrmPolicyTmpl) String() string { return fmt.Sprintf("{Dst: %v, Src: %v, Proto: %s, Mode: %s, Spi: 0x%x, Reqid: 0x%x}", t.Dst, t.Src, t.Proto, t.Mode, t.Spi, t.Reqid) } // XfrmPolicy represents an ipsec policy. It represents the overlay network // and has a list of XfrmPolicyTmpls representing the base addresses of // the policy. type XfrmPolicy struct { Dst *net.IPNet Src *net.IPNet Proto Proto DstPort int SrcPort int Dir Dir Priority int Index int Action PolicyAction Ifindex int Ifid int Mark *XfrmMark Tmpls []XfrmPolicyTmpl } func (p XfrmPolicy) String() string { return fmt.Sprintf("{Dst: %v, Src: %v, Proto: %s, DstPort: %d, SrcPort: %d, Dir: %s, Priority: %d, Index: %d, Action: %s, Ifindex: %d, Ifid: %d, Mark: %s, Tmpls: %s}", p.Dst, p.Src, p.Proto, p.DstPort, p.SrcPort, p.Dir, p.Priority, p.Index, p.Action, p.Ifindex, p.Ifid, p.Mark, p.Tmpls) } func selFromPolicy(sel *nl.XfrmSelector, policy *XfrmPolicy) { sel.Family = uint16(nl.FAMILY_V4) if policy.Dst != nil { sel.Family = uint16(nl.GetIPFamily(policy.Dst.IP)) sel.Daddr.FromIP(policy.Dst.IP) prefixlenD, _ := policy.Dst.Mask.Size() sel.PrefixlenD = uint8(prefixlenD) } if policy.Src != nil { sel.Saddr.FromIP(policy.Src.IP) prefixlenS, _ := policy.Src.Mask.Size() sel.PrefixlenS = uint8(prefixlenS) } sel.Proto = uint8(policy.Proto) sel.Dport = nl.Swap16(uint16(policy.DstPort)) sel.Sport = nl.Swap16(uint16(policy.SrcPort)) if sel.Dport != 0 { sel.DportMask = ^uint16(0) } if sel.Sport != 0 { sel.SportMask = ^uint16(0) } sel.Ifindex = int32(policy.Ifindex) } // XfrmPolicyAdd will add an xfrm policy to the system. // Equivalent to: `ip xfrm policy add $policy` func XfrmPolicyAdd(policy *XfrmPolicy) error { return pkgHandle.XfrmPolicyAdd(policy) } // XfrmPolicyAdd will add an xfrm policy to the system. // Equivalent to: `ip xfrm policy add $policy` func (h *Handle) XfrmPolicyAdd(policy *XfrmPolicy) error { return h.xfrmPolicyAddOrUpdate(policy, nl.XFRM_MSG_NEWPOLICY) } // XfrmPolicyUpdate will update an xfrm policy to the system. // Equivalent to: `ip xfrm policy update $policy` func XfrmPolicyUpdate(policy *XfrmPolicy) error { return pkgHandle.XfrmPolicyUpdate(policy) } // XfrmPolicyUpdate will update an xfrm policy to the system. // Equivalent to: `ip xfrm policy update $policy` func (h *Handle) XfrmPolicyUpdate(policy *XfrmPolicy) error { return h.xfrmPolicyAddOrUpdate(policy, nl.XFRM_MSG_UPDPOLICY) } func (h *Handle) xfrmPolicyAddOrUpdate(policy *XfrmPolicy, nlProto int) error { req := h.newNetlinkRequest(nlProto, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) msg := &nl.XfrmUserpolicyInfo{} selFromPolicy(&msg.Sel, policy) msg.Priority = uint32(policy.Priority) msg.Index = uint32(policy.Index) msg.Dir = uint8(policy.Dir) msg.Action = uint8(policy.Action) msg.Lft.SoftByteLimit = nl.XFRM_INF msg.Lft.HardByteLimit = nl.XFRM_INF msg.Lft.SoftPacketLimit = nl.XFRM_INF msg.Lft.HardPacketLimit = nl.XFRM_INF req.AddData(msg) tmplData := make([]byte, nl.SizeofXfrmUserTmpl*len(policy.Tmpls)) for i, tmpl := range policy.Tmpls { start := i * nl.SizeofXfrmUserTmpl userTmpl := nl.DeserializeXfrmUserTmpl(tmplData[start : start+nl.SizeofXfrmUserTmpl]) userTmpl.XfrmId.Daddr.FromIP(tmpl.Dst) userTmpl.Saddr.FromIP(tmpl.Src) userTmpl.Family = uint16(nl.GetIPFamily(tmpl.Dst)) userTmpl.XfrmId.Proto = uint8(tmpl.Proto) userTmpl.XfrmId.Spi = nl.Swap32(uint32(tmpl.Spi)) userTmpl.Mode = uint8(tmpl.Mode) userTmpl.Reqid = uint32(tmpl.Reqid) userTmpl.Optional = uint8(tmpl.Optional) userTmpl.Aalgos = ^uint32(0) userTmpl.Ealgos = ^uint32(0) userTmpl.Calgos = ^uint32(0) } if len(tmplData) > 0 { tmpls := nl.NewRtAttr(nl.XFRMA_TMPL, tmplData) req.AddData(tmpls) } if policy.Mark != nil { out := nl.NewRtAttr(nl.XFRMA_MARK, writeMark(policy.Mark)) req.AddData(out) } if policy.Ifid != 0 { ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(policy.Ifid))) req.AddData(ifId) } _, err := req.Execute(unix.NETLINK_XFRM, 0) return err } // XfrmPolicyDel will delete an xfrm policy from the system. Note that // the Tmpls are ignored when matching the policy to delete. // Equivalent to: `ip xfrm policy del $policy` func XfrmPolicyDel(policy *XfrmPolicy) error { return pkgHandle.XfrmPolicyDel(policy) } // XfrmPolicyDel will delete an xfrm policy from the system. Note that // the Tmpls are ignored when matching the policy to delete. // Equivalent to: `ip xfrm policy del $policy` func (h *Handle) XfrmPolicyDel(policy *XfrmPolicy) error { _, err := h.xfrmPolicyGetOrDelete(policy, nl.XFRM_MSG_DELPOLICY) return err } // XfrmPolicyList gets a list of xfrm policies in the system. // Equivalent to: `ip xfrm policy show`. // The list can be filtered by ip family. func XfrmPolicyList(family int) ([]XfrmPolicy, error) { return pkgHandle.XfrmPolicyList(family) } // XfrmPolicyList gets a list of xfrm policies in the system. // Equivalent to: `ip xfrm policy show`. // The list can be filtered by ip family. func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) { req := h.newNetlinkRequest(nl.XFRM_MSG_GETPOLICY, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(family) req.AddData(msg) msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWPOLICY) if err != nil { return nil, err } var res []XfrmPolicy for _, m := range msgs { if policy, err := parseXfrmPolicy(m, family); err == nil { res = append(res, *policy) } else if err == familyError { continue } else { return nil, err } } return res, nil } // XfrmPolicyGet gets a the policy described by the index or selector, if found. // Equivalent to: `ip xfrm policy get { SELECTOR | index INDEX } dir DIR [ctx CTX ] [ mark MARK [ mask MASK ] ] [ ptype PTYPE ]`. func XfrmPolicyGet(policy *XfrmPolicy) (*XfrmPolicy, error) { return pkgHandle.XfrmPolicyGet(policy) } // XfrmPolicyGet gets a the policy described by the index or selector, if found. // Equivalent to: `ip xfrm policy get { SELECTOR | index INDEX } dir DIR [ctx CTX ] [ mark MARK [ mask MASK ] ] [ ptype PTYPE ]`. func (h *Handle) XfrmPolicyGet(policy *XfrmPolicy) (*XfrmPolicy, error) { return h.xfrmPolicyGetOrDelete(policy, nl.XFRM_MSG_GETPOLICY) } // XfrmPolicyFlush will flush the policies on the system. // Equivalent to: `ip xfrm policy flush` func XfrmPolicyFlush() error { return pkgHandle.XfrmPolicyFlush() } // XfrmPolicyFlush will flush the policies on the system. // Equivalent to: `ip xfrm policy flush` func (h *Handle) XfrmPolicyFlush() error { req := h.newNetlinkRequest(nl.XFRM_MSG_FLUSHPOLICY, unix.NLM_F_ACK) _, err := req.Execute(unix.NETLINK_XFRM, 0) return err } func (h *Handle) xfrmPolicyGetOrDelete(policy *XfrmPolicy, nlProto int) (*XfrmPolicy, error) { req := h.newNetlinkRequest(nlProto, unix.NLM_F_ACK) msg := &nl.XfrmUserpolicyId{} selFromPolicy(&msg.Sel, policy) msg.Index = uint32(policy.Index) msg.Dir = uint8(policy.Dir) req.AddData(msg) if policy.Mark != nil { out := nl.NewRtAttr(nl.XFRMA_MARK, writeMark(policy.Mark)) req.AddData(out) } if policy.Ifid != 0 { ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(policy.Ifid))) req.AddData(ifId) } resType := nl.XFRM_MSG_NEWPOLICY if nlProto == nl.XFRM_MSG_DELPOLICY { resType = 0 } msgs, err := req.Execute(unix.NETLINK_XFRM, uint16(resType)) if err != nil { return nil, err } if nlProto == nl.XFRM_MSG_DELPOLICY { return nil, err } return parseXfrmPolicy(msgs[0], FAMILY_ALL) } func parseXfrmPolicy(m []byte, family int) (*XfrmPolicy, error) { msg := nl.DeserializeXfrmUserpolicyInfo(m) // This is mainly for the policy dump if family != FAMILY_ALL && family != int(msg.Sel.Family) { return nil, familyError } var policy XfrmPolicy policy.Dst = msg.Sel.Daddr.ToIPNet(msg.Sel.PrefixlenD, uint16(family)) policy.Src = msg.Sel.Saddr.ToIPNet(msg.Sel.PrefixlenS, uint16(family)) policy.Proto = Proto(msg.Sel.Proto) policy.DstPort = int(nl.Swap16(msg.Sel.Dport)) policy.SrcPort = int(nl.Swap16(msg.Sel.Sport)) policy.Ifindex = int(msg.Sel.Ifindex) policy.Priority = int(msg.Priority) policy.Index = int(msg.Index) policy.Dir = Dir(msg.Dir) policy.Action = PolicyAction(msg.Action) attrs, err := nl.ParseRouteAttr(m[msg.Len():]) if err != nil { return nil, err } for _, attr := range attrs { switch attr.Attr.Type { case nl.XFRMA_TMPL: max := len(attr.Value) for i := 0; i < max; i += nl.SizeofXfrmUserTmpl { var resTmpl XfrmPolicyTmpl tmpl := nl.DeserializeXfrmUserTmpl(attr.Value[i : i+nl.SizeofXfrmUserTmpl]) resTmpl.Dst = tmpl.XfrmId.Daddr.ToIP() resTmpl.Src = tmpl.Saddr.ToIP() resTmpl.Proto = Proto(tmpl.XfrmId.Proto) resTmpl.Mode = Mode(tmpl.Mode) resTmpl.Spi = int(nl.Swap32(tmpl.XfrmId.Spi)) resTmpl.Reqid = int(tmpl.Reqid) resTmpl.Optional = int(tmpl.Optional) policy.Tmpls = append(policy.Tmpls, resTmpl) } case nl.XFRMA_MARK: mark := nl.DeserializeXfrmMark(attr.Value[:]) policy.Mark = new(XfrmMark) policy.Mark.Value = mark.Value policy.Mark.Mask = mark.Mask case nl.XFRMA_IF_ID: policy.Ifid = int(native.Uint32(attr.Value)) } } return &policy, nil } netlink-1.3.0/xfrm_policy_linux_test.go000066400000000000000000000141461466216277000203400ustar00rootroot00000000000000package netlink import ( "bytes" "net" "testing" ) const zeroCIDR = "0.0.0.0/0" func TestXfrmPolicyAddUpdateDel(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() policy := getPolicy() if err := XfrmPolicyAdd(policy); err != nil { t.Fatal(err) } policies, err := XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 1 { t.Fatal("Policy not added properly") } if !comparePolicies(policy, &policies[0]) { t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", policy, policies[0]) } if policies[0].Ifindex != 0 { t.Fatalf("default policy has a non-zero interface index.\nGot %d", policies[0].Ifindex) } if policies[0].Ifid != 0 { t.Fatalf("default policy has non-zero if_id.\nGot %d", policies[0].Ifid) } if policies[0].Action != XFRM_POLICY_ALLOW { t.Fatalf("default policy has non-allow action.\nGot %s", policies[0].Action) } // Look for a specific policy sp, err := XfrmPolicyGet(policy) if err != nil { t.Fatal(err) } if !comparePolicies(policy, sp) { t.Fatalf("unexpected policy returned") } // Modify the policy policy.Priority = 100 if err := XfrmPolicyUpdate(policy); err != nil { t.Fatal(err) } sp, err = XfrmPolicyGet(policy) if err != nil { t.Fatal(err) } if sp.Priority != 100 { t.Fatalf("failed to modify the policy") } if err = XfrmPolicyDel(policy); err != nil { t.Fatal(err) } policies, err = XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 0 { t.Fatal("Policy not removed properly") } // Src and dst are not mandatory field. Creation should succeed policy.Src = nil policy.Dst = nil if err = XfrmPolicyAdd(policy); err != nil { t.Fatal(err) } sp, err = XfrmPolicyGet(policy) if err != nil { t.Fatal(err) } if !comparePolicies(policy, sp) { t.Fatalf("unexpected policy returned") } if err = XfrmPolicyDel(policy); err != nil { t.Fatal(err) } if _, err := XfrmPolicyGet(policy); err == nil { t.Fatalf("Unexpected success") } } func TestXfrmPolicyFlush(t *testing.T) { defer setUpNetlinkTest(t)() p1 := getPolicy() if err := XfrmPolicyAdd(p1); err != nil { t.Fatal(err) } p1.Dir = XFRM_DIR_IN s := p1.Src p1.Src = p1.Dst p1.Dst = s if err := XfrmPolicyAdd(p1); err != nil { t.Fatal(err) } policies, err := XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 2 { t.Fatalf("unexpected number of policies: %d", len(policies)) } if err := XfrmPolicyFlush(); err != nil { t.Fatal(err) } policies, err = XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 0 { t.Fatalf("unexpected number of policies: %d", len(policies)) } } func TestXfrmPolicyBlockWithIfindex(t *testing.T) { defer setUpNetlinkTest(t)() pBlock := getPolicy() pBlock.Action = XFRM_POLICY_BLOCK pBlock.Ifindex = 1 // loopback interface if err := XfrmPolicyAdd(pBlock); err != nil { t.Fatal(err) } policies, err := XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 1 { t.Fatalf("unexpected number of policies: %d", len(policies)) } if !comparePolicies(pBlock, &policies[0]) { t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", pBlock, policies[0]) } if err = XfrmPolicyDel(pBlock); err != nil { t.Fatal(err) } } func TestXfrmPolicyWithIfid(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() pol := getPolicy() pol.Ifid = 54321 if err := XfrmPolicyAdd(pol); err != nil { t.Fatal(err) } policies, err := XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 1 { t.Fatalf("unexpected number of policies: %d", len(policies)) } if !comparePolicies(pol, &policies[0]) { t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", pol, policies[0]) } if err = XfrmPolicyDel(&policies[0]); err != nil { t.Fatal(err) } } func TestXfrmPolicyWithOptional(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() pol := getPolicy() pol.Dir = XFRM_DIR_IN pol.Tmpls[0].Optional = 1 if err := XfrmPolicyAdd(pol); err != nil { t.Fatal(err) } policies, err := XfrmPolicyList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(policies) != 1 { t.Fatalf("unexpected number of policies: %d", len(policies)) } if !comparePolicies(pol, &policies[0]) { t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", pol, policies[0]) } if err = XfrmPolicyDel(&policies[0]); err != nil { t.Fatal(err) } } func comparePolicies(a, b *XfrmPolicy) bool { if a == b { return true } if a == nil || b == nil { return false } // Do not check Index which is assigned by kernel return a.Dir == b.Dir && a.Priority == b.Priority && compareIPNet(a.Src, b.Src) && compareIPNet(a.Dst, b.Dst) && a.Action == b.Action && a.Ifindex == b.Ifindex && a.Mark.Value == b.Mark.Value && a.Mark.Mask == b.Mark.Mask && a.Ifid == b.Ifid && compareTemplates(a.Tmpls, b.Tmpls) } func compareTemplates(a, b []XfrmPolicyTmpl) bool { if len(a) != len(b) { return false } for i, ta := range a { tb := b[i] if !ta.Dst.Equal(tb.Dst) || !ta.Src.Equal(tb.Src) || ta.Spi != tb.Spi || ta.Mode != tb.Mode || ta.Reqid != tb.Reqid || ta.Proto != tb.Proto || ta.Optional != tb.Optional { return false } } return true } func compareIPNet(a, b *net.IPNet) bool { if a == b { return true } // For unspecified src/dst parseXfrmPolicy would set the zero address cidr if (a == nil && b.String() == zeroCIDR) || (b == nil && a.String() == zeroCIDR) { return true } if a == nil || b == nil { return false } return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask) } func getPolicy() *XfrmPolicy { src, _ := ParseIPNet("127.1.1.1/32") dst, _ := ParseIPNet("127.1.1.2/32") policy := &XfrmPolicy{ Src: src, Dst: dst, Proto: 17, DstPort: 1234, SrcPort: 5678, Dir: XFRM_DIR_OUT, Mark: &XfrmMark{ Value: 0xabff22, Mask: 0xffffffff, }, Priority: 10, } tmpl := XfrmPolicyTmpl{ Src: net.ParseIP("127.0.0.1"), Dst: net.ParseIP("127.0.0.2"), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, Spi: 0x1bcdef99, } policy.Tmpls = append(policy.Tmpls, tmpl) return policy } netlink-1.3.0/xfrm_state_linux.go000066400000000000000000000462231466216277000171230ustar00rootroot00000000000000package netlink import ( "fmt" "net" "time" "unsafe" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // XfrmStateAlgo represents the algorithm to use for the ipsec encryption. type XfrmStateAlgo struct { Name string Key []byte TruncateLen int // Auth only ICVLen int // AEAD only } func (a XfrmStateAlgo) String() string { base := fmt.Sprintf("{Name: %s, Key: 0x%x", a.Name, a.Key) if a.TruncateLen != 0 { base = fmt.Sprintf("%s, Truncate length: %d", base, a.TruncateLen) } if a.ICVLen != 0 { base = fmt.Sprintf("%s, ICV length: %d", base, a.ICVLen) } return fmt.Sprintf("%s}", base) } // EncapType is an enum representing the optional packet encapsulation. type EncapType uint8 const ( XFRM_ENCAP_ESPINUDP_NONIKE EncapType = iota + 1 XFRM_ENCAP_ESPINUDP ) func (e EncapType) String() string { switch e { case XFRM_ENCAP_ESPINUDP_NONIKE: return "espinudp-non-ike" case XFRM_ENCAP_ESPINUDP: return "espinudp" } return "unknown" } // XfrmStateEncap represents the encapsulation to use for the ipsec encryption. type XfrmStateEncap struct { Type EncapType SrcPort int DstPort int OriginalAddress net.IP } func (e XfrmStateEncap) String() string { return fmt.Sprintf("{Type: %s, Srcport: %d, DstPort: %d, OriginalAddress: %v}", e.Type, e.SrcPort, e.DstPort, e.OriginalAddress) } // XfrmStateLimits represents the configured limits for the state. type XfrmStateLimits struct { ByteSoft uint64 ByteHard uint64 PacketSoft uint64 PacketHard uint64 TimeSoft uint64 TimeHard uint64 TimeUseSoft uint64 TimeUseHard uint64 } // XfrmStateStats represents the current number of bytes/packets // processed by this State, the State's installation and first use // time and the replay window counters. type XfrmStateStats struct { ReplayWindow uint32 Replay uint32 Failed uint32 Bytes uint64 Packets uint64 AddTime uint64 UseTime uint64 } // XfrmReplayState represents the sequence number states for // "legacy" anti-replay mode. type XfrmReplayState struct { OSeq uint32 Seq uint32 BitMap uint32 } func (r XfrmReplayState) String() string { return fmt.Sprintf("{OSeq: 0x%x, Seq: 0x%x, BitMap: 0x%x}", r.OSeq, r.Seq, r.BitMap) } // XfrmState represents the state of an ipsec policy. It optionally // contains an XfrmStateAlgo for encryption and one for authentication. type XfrmState struct { Dst net.IP Src net.IP Proto Proto Mode Mode Spi int Reqid int ReplayWindow int Limits XfrmStateLimits Statistics XfrmStateStats Mark *XfrmMark OutputMark *XfrmMark Ifid int Auth *XfrmStateAlgo Crypt *XfrmStateAlgo Aead *XfrmStateAlgo Encap *XfrmStateEncap ESN bool DontEncapDSCP bool OSeqMayWrap bool Replay *XfrmReplayState Selector *XfrmPolicy } func (sa XfrmState) String() string { return fmt.Sprintf("Dst: %v, Src: %v, Proto: %s, Mode: %s, SPI: 0x%x, ReqID: 0x%x, ReplayWindow: %d, Mark: %v, OutputMark: %v, Ifid: %d, Auth: %v, Crypt: %v, Aead: %v, Encap: %v, ESN: %t, DontEncapDSCP: %t, OSeqMayWrap: %t, Replay: %v", sa.Dst, sa.Src, sa.Proto, sa.Mode, sa.Spi, sa.Reqid, sa.ReplayWindow, sa.Mark, sa.OutputMark, sa.Ifid, sa.Auth, sa.Crypt, sa.Aead, sa.Encap, sa.ESN, sa.DontEncapDSCP, sa.OSeqMayWrap, sa.Replay) } func (sa XfrmState) Print(stats bool) string { if !stats { return sa.String() } at := time.Unix(int64(sa.Statistics.AddTime), 0).Format(time.UnixDate) ut := "-" if sa.Statistics.UseTime > 0 { ut = time.Unix(int64(sa.Statistics.UseTime), 0).Format(time.UnixDate) } return fmt.Sprintf("%s, ByteSoft: %s, ByteHard: %s, PacketSoft: %s, PacketHard: %s, TimeSoft: %d, TimeHard: %d, TimeUseSoft: %d, TimeUseHard: %d, Bytes: %d, Packets: %d, "+ "AddTime: %s, UseTime: %s, ReplayWindow: %d, Replay: %d, Failed: %d", sa.String(), printLimit(sa.Limits.ByteSoft), printLimit(sa.Limits.ByteHard), printLimit(sa.Limits.PacketSoft), printLimit(sa.Limits.PacketHard), sa.Limits.TimeSoft, sa.Limits.TimeHard, sa.Limits.TimeUseSoft, sa.Limits.TimeUseHard, sa.Statistics.Bytes, sa.Statistics.Packets, at, ut, sa.Statistics.ReplayWindow, sa.Statistics.Replay, sa.Statistics.Failed) } func printLimit(lmt uint64) string { if lmt == ^uint64(0) { return "(INF)" } return fmt.Sprintf("%d", lmt) } func writeStateAlgo(a *XfrmStateAlgo) []byte { algo := nl.XfrmAlgo{ AlgKeyLen: uint32(len(a.Key) * 8), AlgKey: a.Key, } end := len(a.Name) if end > 64 { end = 64 } copy(algo.AlgName[:end], a.Name) return algo.Serialize() } func writeStateAlgoAuth(a *XfrmStateAlgo) []byte { algo := nl.XfrmAlgoAuth{ AlgKeyLen: uint32(len(a.Key) * 8), AlgTruncLen: uint32(a.TruncateLen), AlgKey: a.Key, } end := len(a.Name) if end > 64 { end = 64 } copy(algo.AlgName[:end], a.Name) return algo.Serialize() } func writeStateAlgoAead(a *XfrmStateAlgo) []byte { algo := nl.XfrmAlgoAEAD{ AlgKeyLen: uint32(len(a.Key) * 8), AlgICVLen: uint32(a.ICVLen), AlgKey: a.Key, } end := len(a.Name) if end > 64 { end = 64 } copy(algo.AlgName[:end], a.Name) return algo.Serialize() } func writeMark(m *XfrmMark) []byte { mark := &nl.XfrmMark{ Value: m.Value, Mask: m.Mask, } if mark.Mask == 0 { mark.Mask = ^uint32(0) } return mark.Serialize() } func writeReplayEsn(replayWindow int) []byte { replayEsn := &nl.XfrmReplayStateEsn{ OSeq: 0, Seq: 0, OSeqHi: 0, SeqHi: 0, ReplayWindow: uint32(replayWindow), } // Linux stores the bitmap to identify the already received sequence packets in blocks of uint32 elements. // Therefore bitmap length is the minimum number of uint32 elements needed. The following is a ceiling operation. bytesPerElem := int(unsafe.Sizeof(replayEsn.BmpLen)) // Any uint32 variable is good for this replayEsn.BmpLen = uint32((replayWindow + (bytesPerElem * 8) - 1) / (bytesPerElem * 8)) return replayEsn.Serialize() } func writeReplay(r *XfrmReplayState) []byte { return (&nl.XfrmReplayState{ OSeq: r.OSeq, Seq: r.Seq, BitMap: r.BitMap, }).Serialize() } // XfrmStateAdd will add an xfrm state to the system. // Equivalent to: `ip xfrm state add $state` func XfrmStateAdd(state *XfrmState) error { return pkgHandle.XfrmStateAdd(state) } // XfrmStateAdd will add an xfrm state to the system. // Equivalent to: `ip xfrm state add $state` func (h *Handle) XfrmStateAdd(state *XfrmState) error { return h.xfrmStateAddOrUpdate(state, nl.XFRM_MSG_NEWSA) } // XfrmStateAllocSpi will allocate an xfrm state in the system. // Equivalent to: `ip xfrm state allocspi` func XfrmStateAllocSpi(state *XfrmState) (*XfrmState, error) { return pkgHandle.xfrmStateAllocSpi(state) } // XfrmStateUpdate will update an xfrm state to the system. // Equivalent to: `ip xfrm state update $state` func XfrmStateUpdate(state *XfrmState) error { return pkgHandle.XfrmStateUpdate(state) } // XfrmStateUpdate will update an xfrm state to the system. // Equivalent to: `ip xfrm state update $state` func (h *Handle) XfrmStateUpdate(state *XfrmState) error { return h.xfrmStateAddOrUpdate(state, nl.XFRM_MSG_UPDSA) } func (h *Handle) xfrmStateAddOrUpdate(state *XfrmState, nlProto int) error { // A state with spi 0 can't be deleted so don't allow it to be set if state.Spi == 0 { return fmt.Errorf("Spi must be set when adding xfrm state") } req := h.newNetlinkRequest(nlProto, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) msg := xfrmUsersaInfoFromXfrmState(state) if state.ESN { if state.ReplayWindow == 0 { return fmt.Errorf("ESN flag set without ReplayWindow") } msg.Flags |= nl.XFRM_STATE_ESN msg.ReplayWindow = 0 } limitsToLft(state.Limits, &msg.Lft) req.AddData(msg) if state.Auth != nil { out := nl.NewRtAttr(nl.XFRMA_ALG_AUTH_TRUNC, writeStateAlgoAuth(state.Auth)) req.AddData(out) } if state.Crypt != nil { out := nl.NewRtAttr(nl.XFRMA_ALG_CRYPT, writeStateAlgo(state.Crypt)) req.AddData(out) } if state.Aead != nil { out := nl.NewRtAttr(nl.XFRMA_ALG_AEAD, writeStateAlgoAead(state.Aead)) req.AddData(out) } if state.Encap != nil { encapData := make([]byte, nl.SizeofXfrmEncapTmpl) encap := nl.DeserializeXfrmEncapTmpl(encapData) encap.EncapType = uint16(state.Encap.Type) encap.EncapSport = nl.Swap16(uint16(state.Encap.SrcPort)) encap.EncapDport = nl.Swap16(uint16(state.Encap.DstPort)) encap.EncapOa.FromIP(state.Encap.OriginalAddress) out := nl.NewRtAttr(nl.XFRMA_ENCAP, encapData) req.AddData(out) } if state.Mark != nil { out := nl.NewRtAttr(nl.XFRMA_MARK, writeMark(state.Mark)) req.AddData(out) } if state.ESN { out := nl.NewRtAttr(nl.XFRMA_REPLAY_ESN_VAL, writeReplayEsn(state.ReplayWindow)) req.AddData(out) } if state.OutputMark != nil { out := nl.NewRtAttr(nl.XFRMA_SET_MARK, nl.Uint32Attr(state.OutputMark.Value)) req.AddData(out) if state.OutputMark.Mask != 0 { out = nl.NewRtAttr(nl.XFRMA_SET_MARK_MASK, nl.Uint32Attr(state.OutputMark.Mask)) req.AddData(out) } } if state.OSeqMayWrap || state.DontEncapDSCP { var flags uint32 if state.DontEncapDSCP { flags |= nl.XFRM_SA_XFLAG_DONT_ENCAP_DSCP } if state.OSeqMayWrap { flags |= nl.XFRM_SA_XFLAG_OSEQ_MAY_WRAP } out := nl.NewRtAttr(nl.XFRMA_SA_EXTRA_FLAGS, nl.Uint32Attr(flags)) req.AddData(out) } if state.Replay != nil { out := nl.NewRtAttr(nl.XFRMA_REPLAY_VAL, writeReplay(state.Replay)) req.AddData(out) } if state.Ifid != 0 { ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(state.Ifid))) req.AddData(ifId) } _, err := req.Execute(unix.NETLINK_XFRM, 0) return err } func (h *Handle) xfrmStateAllocSpi(state *XfrmState) (*XfrmState, error) { req := h.newNetlinkRequest(nl.XFRM_MSG_ALLOCSPI, unix.NLM_F_CREATE|unix.NLM_F_EXCL|unix.NLM_F_ACK) msg := &nl.XfrmUserSpiInfo{} msg.XfrmUsersaInfo = *(xfrmUsersaInfoFromXfrmState(state)) // 1-255 is reserved by IANA for future use msg.Min = 0x100 msg.Max = 0xffffffff req.AddData(msg) if state.Mark != nil { out := nl.NewRtAttr(nl.XFRMA_MARK, writeMark(state.Mark)) req.AddData(out) } msgs, err := req.Execute(unix.NETLINK_XFRM, 0) if err != nil { return nil, err } return parseXfrmState(msgs[0], FAMILY_ALL) } // XfrmStateDel will delete an xfrm state from the system. Note that // the Algos are ignored when matching the state to delete. // Equivalent to: `ip xfrm state del $state` func XfrmStateDel(state *XfrmState) error { return pkgHandle.XfrmStateDel(state) } // XfrmStateDel will delete an xfrm state from the system. Note that // the Algos are ignored when matching the state to delete. // Equivalent to: `ip xfrm state del $state` func (h *Handle) XfrmStateDel(state *XfrmState) error { _, err := h.xfrmStateGetOrDelete(state, nl.XFRM_MSG_DELSA) return err } // XfrmStateList gets a list of xfrm states in the system. // Equivalent to: `ip [-4|-6] xfrm state show`. // The list can be filtered by ip family. func XfrmStateList(family int) ([]XfrmState, error) { return pkgHandle.XfrmStateList(family) } // XfrmStateList gets a list of xfrm states in the system. // Equivalent to: `ip xfrm state show`. // The list can be filtered by ip family. func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) { req := h.newNetlinkRequest(nl.XFRM_MSG_GETSA, unix.NLM_F_DUMP) msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWSA) if err != nil { return nil, err } var res []XfrmState for _, m := range msgs { if state, err := parseXfrmState(m, family); err == nil { res = append(res, *state) } else if err == familyError { continue } else { return nil, err } } return res, nil } // XfrmStateGet gets the xfrm state described by the ID, if found. // Equivalent to: `ip xfrm state get ID [ mark MARK [ mask MASK ] ]`. // Only the fields which constitue the SA ID must be filled in: // ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ] // mark is optional func XfrmStateGet(state *XfrmState) (*XfrmState, error) { return pkgHandle.XfrmStateGet(state) } // XfrmStateGet gets the xfrm state described by the ID, if found. // Equivalent to: `ip xfrm state get ID [ mark MARK [ mask MASK ] ]`. // Only the fields which constitue the SA ID must be filled in: // ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ] // mark is optional func (h *Handle) XfrmStateGet(state *XfrmState) (*XfrmState, error) { return h.xfrmStateGetOrDelete(state, nl.XFRM_MSG_GETSA) } func (h *Handle) xfrmStateGetOrDelete(state *XfrmState, nlProto int) (*XfrmState, error) { req := h.newNetlinkRequest(nlProto, unix.NLM_F_ACK) msg := &nl.XfrmUsersaId{} msg.Family = uint16(nl.GetIPFamily(state.Dst)) msg.Daddr.FromIP(state.Dst) msg.Proto = uint8(state.Proto) msg.Spi = nl.Swap32(uint32(state.Spi)) req.AddData(msg) if state.Mark != nil { out := nl.NewRtAttr(nl.XFRMA_MARK, writeMark(state.Mark)) req.AddData(out) } if state.Src != nil { out := nl.NewRtAttr(nl.XFRMA_SRCADDR, state.Src.To16()) req.AddData(out) } if state.Ifid != 0 { ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(state.Ifid))) req.AddData(ifId) } resType := nl.XFRM_MSG_NEWSA if nlProto == nl.XFRM_MSG_DELSA { resType = 0 } msgs, err := req.Execute(unix.NETLINK_XFRM, uint16(resType)) if err != nil { return nil, err } if nlProto == nl.XFRM_MSG_DELSA { return nil, nil } s, err := parseXfrmState(msgs[0], FAMILY_ALL) if err != nil { return nil, err } return s, nil } var familyError = fmt.Errorf("family error") func xfrmStateFromXfrmUsersaInfo(msg *nl.XfrmUsersaInfo) *XfrmState { var state XfrmState state.Dst = msg.Id.Daddr.ToIP() state.Src = msg.Saddr.ToIP() state.Proto = Proto(msg.Id.Proto) state.Mode = Mode(msg.Mode) state.Spi = int(nl.Swap32(msg.Id.Spi)) state.Reqid = int(msg.Reqid) state.ReplayWindow = int(msg.ReplayWindow) lftToLimits(&msg.Lft, &state.Limits) curToStats(&msg.Curlft, &msg.Stats, &state.Statistics) state.Selector = &XfrmPolicy{ Dst: msg.Sel.Daddr.ToIPNet(msg.Sel.PrefixlenD, msg.Sel.Family), Src: msg.Sel.Saddr.ToIPNet(msg.Sel.PrefixlenS, msg.Sel.Family), Proto: Proto(msg.Sel.Proto), DstPort: int(nl.Swap16(msg.Sel.Dport)), SrcPort: int(nl.Swap16(msg.Sel.Sport)), Ifindex: int(msg.Sel.Ifindex), } return &state } func parseXfrmState(m []byte, family int) (*XfrmState, error) { msg := nl.DeserializeXfrmUsersaInfo(m) // This is mainly for the state dump if family != FAMILY_ALL && family != int(msg.Family) { return nil, familyError } state := xfrmStateFromXfrmUsersaInfo(msg) attrs, err := nl.ParseRouteAttr(m[nl.SizeofXfrmUsersaInfo:]) if err != nil { return nil, err } for _, attr := range attrs { switch attr.Attr.Type { case nl.XFRMA_ALG_AUTH, nl.XFRMA_ALG_CRYPT: var resAlgo *XfrmStateAlgo if attr.Attr.Type == nl.XFRMA_ALG_AUTH { if state.Auth == nil { state.Auth = new(XfrmStateAlgo) } resAlgo = state.Auth } else { state.Crypt = new(XfrmStateAlgo) resAlgo = state.Crypt } algo := nl.DeserializeXfrmAlgo(attr.Value[:]) (*resAlgo).Name = nl.BytesToString(algo.AlgName[:]) (*resAlgo).Key = algo.AlgKey case nl.XFRMA_ALG_AUTH_TRUNC: if state.Auth == nil { state.Auth = new(XfrmStateAlgo) } algo := nl.DeserializeXfrmAlgoAuth(attr.Value[:]) state.Auth.Name = nl.BytesToString(algo.AlgName[:]) state.Auth.Key = algo.AlgKey state.Auth.TruncateLen = int(algo.AlgTruncLen) case nl.XFRMA_ALG_AEAD: state.Aead = new(XfrmStateAlgo) algo := nl.DeserializeXfrmAlgoAEAD(attr.Value[:]) state.Aead.Name = nl.BytesToString(algo.AlgName[:]) state.Aead.Key = algo.AlgKey state.Aead.ICVLen = int(algo.AlgICVLen) case nl.XFRMA_ENCAP: encap := nl.DeserializeXfrmEncapTmpl(attr.Value[:]) state.Encap = new(XfrmStateEncap) state.Encap.Type = EncapType(encap.EncapType) state.Encap.SrcPort = int(nl.Swap16(encap.EncapSport)) state.Encap.DstPort = int(nl.Swap16(encap.EncapDport)) state.Encap.OriginalAddress = encap.EncapOa.ToIP() case nl.XFRMA_MARK: mark := nl.DeserializeXfrmMark(attr.Value[:]) state.Mark = new(XfrmMark) state.Mark.Value = mark.Value state.Mark.Mask = mark.Mask case nl.XFRMA_SA_EXTRA_FLAGS: flags := native.Uint32(attr.Value) if (flags & nl.XFRM_SA_XFLAG_DONT_ENCAP_DSCP) != 0 { state.DontEncapDSCP = true } if (flags & nl.XFRM_SA_XFLAG_OSEQ_MAY_WRAP) != 0 { state.OSeqMayWrap = true } case nl.XFRMA_SET_MARK: if state.OutputMark == nil { state.OutputMark = new(XfrmMark) } state.OutputMark.Value = native.Uint32(attr.Value) case nl.XFRMA_SET_MARK_MASK: if state.OutputMark == nil { state.OutputMark = new(XfrmMark) } state.OutputMark.Mask = native.Uint32(attr.Value) if state.OutputMark.Mask == 0xffffffff { state.OutputMark.Mask = 0 } case nl.XFRMA_IF_ID: state.Ifid = int(native.Uint32(attr.Value)) case nl.XFRMA_REPLAY_VAL: if state.Replay == nil { state.Replay = new(XfrmReplayState) } replay := nl.DeserializeXfrmReplayState(attr.Value[:]) state.Replay.OSeq = replay.OSeq state.Replay.Seq = replay.Seq state.Replay.BitMap = replay.BitMap } } return state, nil } // XfrmStateFlush will flush the xfrm state on the system. // proto = 0 means any transformation protocols // Equivalent to: `ip xfrm state flush [ proto XFRM-PROTO ]` func XfrmStateFlush(proto Proto) error { return pkgHandle.XfrmStateFlush(proto) } // XfrmStateFlush will flush the xfrm state on the system. // proto = 0 means any transformation protocols // Equivalent to: `ip xfrm state flush [ proto XFRM-PROTO ]` func (h *Handle) XfrmStateFlush(proto Proto) error { req := h.newNetlinkRequest(nl.XFRM_MSG_FLUSHSA, unix.NLM_F_ACK) req.AddData(&nl.XfrmUsersaFlush{Proto: uint8(proto)}) _, err := req.Execute(unix.NETLINK_XFRM, 0) return err } func limitsToLft(lmts XfrmStateLimits, lft *nl.XfrmLifetimeCfg) { if lmts.ByteSoft != 0 { lft.SoftByteLimit = lmts.ByteSoft } else { lft.SoftByteLimit = nl.XFRM_INF } if lmts.ByteHard != 0 { lft.HardByteLimit = lmts.ByteHard } else { lft.HardByteLimit = nl.XFRM_INF } if lmts.PacketSoft != 0 { lft.SoftPacketLimit = lmts.PacketSoft } else { lft.SoftPacketLimit = nl.XFRM_INF } if lmts.PacketHard != 0 { lft.HardPacketLimit = lmts.PacketHard } else { lft.HardPacketLimit = nl.XFRM_INF } lft.SoftAddExpiresSeconds = lmts.TimeSoft lft.HardAddExpiresSeconds = lmts.TimeHard lft.SoftUseExpiresSeconds = lmts.TimeUseSoft lft.HardUseExpiresSeconds = lmts.TimeUseHard } func lftToLimits(lft *nl.XfrmLifetimeCfg, lmts *XfrmStateLimits) { *lmts = *(*XfrmStateLimits)(unsafe.Pointer(lft)) } func curToStats(cur *nl.XfrmLifetimeCur, wstats *nl.XfrmStats, stats *XfrmStateStats) { stats.Bytes = cur.Bytes stats.Packets = cur.Packets stats.AddTime = cur.AddTime stats.UseTime = cur.UseTime stats.ReplayWindow = wstats.ReplayWindow stats.Replay = wstats.Replay stats.Failed = wstats.IntegrityFailed } func xfrmUsersaInfoFromXfrmState(state *XfrmState) *nl.XfrmUsersaInfo { msg := &nl.XfrmUsersaInfo{} msg.Family = uint16(nl.GetIPFamily(state.Dst)) msg.Id.Daddr.FromIP(state.Dst) msg.Saddr.FromIP(state.Src) msg.Id.Proto = uint8(state.Proto) msg.Mode = uint8(state.Mode) msg.Id.Spi = nl.Swap32(uint32(state.Spi)) msg.Reqid = uint32(state.Reqid) msg.ReplayWindow = uint8(state.ReplayWindow) msg.Sel = nl.XfrmSelector{} if state.Selector != nil { selFromPolicy(&msg.Sel, state.Selector) } return msg } netlink-1.3.0/xfrm_state_linux_test.go000066400000000000000000000234061466216277000201600ustar00rootroot00000000000000package netlink import ( "bytes" "encoding/hex" "net" "testing" "time" ) func TestXfrmStateAddGetDel(t *testing.T) { for _, s := range []*XfrmState{ getBaseState(), getAeadState(), getBaseStateV6oV4(), getBaseStateV4oV6(), } { testXfrmStateAddGetDel(t, s) } } func testXfrmStateAddGetDel(t *testing.T, state *XfrmState) { tearDown := setUpNetlinkTest(t) defer tearDown() if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } states, err := XfrmStateList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(states) != 1 { t.Fatal("State not added properly") } if !compareStates(state, &states[0]) { t.Fatalf("unexpected states returned") } // Get specific state sa, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } if !compareStates(state, sa) { t.Fatalf("unexpected state returned") } if err = XfrmStateDel(state); err != nil { t.Fatal(err) } states, err = XfrmStateList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(states) != 0 { t.Fatal("State not removed properly") } if _, err := XfrmStateGet(state); err == nil { t.Fatalf("Unexpected success") } } func TestXfrmStateAllocSpi(t *testing.T) { defer setUpNetlinkTest(t)() state := getBaseState() state.Spi = 0 state.Auth = nil state.Crypt = nil rstate, err := XfrmStateAllocSpi(state) if err != nil { t.Fatal(err) } if rstate.Spi == 0 { t.Fatalf("SPI is not allocated") } rstate.Spi = 0 if !compareStates(state, rstate) { t.Fatalf("State not properly allocated") } } func TestXfrmStateFlush(t *testing.T) { defer setUpNetlinkTest(t)() state1 := getBaseState() state2 := getBaseState() state2.Src = net.ParseIP("127.1.0.1") state2.Dst = net.ParseIP("127.1.0.2") state2.Proto = XFRM_PROTO_AH state2.Mode = XFRM_MODE_TUNNEL state2.Spi = 20 state2.Mark = nil state2.Crypt = nil if err := XfrmStateAdd(state1); err != nil { t.Fatal(err) } if err := XfrmStateAdd(state2); err != nil { t.Fatal(err) } // flushing proto for which no state is present should return silently if err := XfrmStateFlush(XFRM_PROTO_COMP); err != nil { t.Fatal(err) } if err := XfrmStateFlush(XFRM_PROTO_AH); err != nil { t.Fatal(err) } if _, err := XfrmStateGet(state2); err == nil { t.Fatalf("Unexpected success") } if err := XfrmStateAdd(state2); err != nil { t.Fatal(err) } if err := XfrmStateFlush(0); err != nil { t.Fatal(err) } states, err := XfrmStateList(FAMILY_ALL) if err != nil { t.Fatal(err) } if len(states) != 0 { t.Fatal("State not flushed properly") } } func TestXfrmStateUpdateLimits(t *testing.T) { defer setUpNetlinkTest(t)() // Program state with limits state := getBaseState() state.Limits.TimeHard = 3600 state.Limits.TimeSoft = 60 state.Limits.PacketHard = 1000 state.Limits.PacketSoft = 50 state.Limits.ByteHard = 1000000 state.Limits.ByteSoft = 50000 state.Limits.TimeUseHard = 3000 state.Limits.TimeUseSoft = 1500 if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } // Verify limits s, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } if !compareLimits(state, s) { t.Fatalf("Incorrect time hard/soft retrieved: %s", s.Print(true)) } // Update limits state.Limits.TimeHard = 1800 state.Limits.TimeSoft = 30 state.Limits.PacketHard = 500 state.Limits.PacketSoft = 25 state.Limits.ByteHard = 500000 state.Limits.ByteSoft = 25000 state.Limits.TimeUseHard = 2000 state.Limits.TimeUseSoft = 1000 if err := XfrmStateUpdate(state); err != nil { t.Fatal(err) } // Verify new limits s, err = XfrmStateGet(state) if err != nil { t.Fatal(err) } if s.Limits.TimeHard != 1800 || s.Limits.TimeSoft != 30 { t.Fatalf("Incorrect time hard retrieved: (%d, %d)", s.Limits.TimeHard, s.Limits.TimeSoft) } } func TestXfrmStateStats(t *testing.T) { defer setUpNetlinkTest(t)() // Program state and record time state := getBaseState() now := time.Now() if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } // Retrieve state s, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } // Verify stats: We expect zero counters, same second add time and unset use time if s.Statistics.Bytes != 0 || s.Statistics.Packets != 0 || s.Statistics.AddTime != uint64(now.Unix()) || s.Statistics.UseTime != 0 { t.Fatalf("Unexpected statistics (addTime: %s) for state:\n%s", now.Format(time.UnixDate), s.Print(true)) } } func TestXfrmStateWithIfid(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() state := getBaseState() state.Ifid = 54321 if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } s, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } if !compareStates(state, s) { t.Fatalf("unexpected state returned.\nExpected: %v.\nGot %v", state, s) } if err = XfrmStateDel(s); err != nil { t.Fatal(err) } } func TestXfrmStateWithOutputMark(t *testing.T) { minKernelRequired(t, 4, 14) defer setUpNetlinkTest(t)() state := getBaseState() state.OutputMark = &XfrmMark{ Value: 0x0000000a, } if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } s, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } if !compareStates(state, s) { t.Fatalf("unexpected state returned.\nExpected: %v.\nGot %v", state, s) } if err = XfrmStateDel(s); err != nil { t.Fatal(err) } } func TestXfrmStateWithOutputMarkAndMask(t *testing.T) { minKernelRequired(t, 4, 19) defer setUpNetlinkTest(t)() state := getBaseState() state.OutputMark = &XfrmMark{ Value: 0x0000000a, Mask: 0x0000000f, } if err := XfrmStateAdd(state); err != nil { t.Fatal(err) } s, err := XfrmStateGet(state) if err != nil { t.Fatal(err) } if !compareStates(state, s) { t.Fatalf("unexpected state returned.\nExpected: %v.\nGot %v", state, s) } if err = XfrmStateDel(s); err != nil { t.Fatal(err) } } func genStateSelectorForV6Payload() *XfrmPolicy { _, wildcardV6Net, _ := net.ParseCIDR("::/0") return &XfrmPolicy{ Src: wildcardV6Net, Dst: wildcardV6Net, } } func genStateSelectorForV4Payload() *XfrmPolicy { _, wildcardV4Net, _ := net.ParseCIDR("0.0.0.0/0") return &XfrmPolicy{ Src: wildcardV4Net, Dst: wildcardV4Net, } } func getBaseState() *XfrmState { return &XfrmState{ // Force 4 byte notation for the IPv4 addresses Src: net.ParseIP("127.0.0.1").To4(), Dst: net.ParseIP("127.0.0.2").To4(), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, Spi: 1, Auth: &XfrmStateAlgo{ Name: "hmac(sha256)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Crypt: &XfrmStateAlgo{ Name: "cbc(aes)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Mark: &XfrmMark{ Value: 0x12340000, Mask: 0xffff0000, }, } } func getBaseStateV4oV6() *XfrmState { return &XfrmState{ // Force 4 byte notation for the IPv4 addressesd Src: net.ParseIP("2001:dead::1").To16(), Dst: net.ParseIP("2001:beef::1").To16(), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, Spi: 1, Auth: &XfrmStateAlgo{ Name: "hmac(sha256)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Crypt: &XfrmStateAlgo{ Name: "cbc(aes)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Mark: &XfrmMark{ Value: 0x12340000, Mask: 0xffff0000, }, Selector: genStateSelectorForV4Payload(), } } func getBaseStateV6oV4() *XfrmState { return &XfrmState{ // Force 4 byte notation for the IPv4 addressesd Src: net.ParseIP("192.168.1.1").To4(), Dst: net.ParseIP("192.168.2.2").To4(), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, Spi: 1, Auth: &XfrmStateAlgo{ Name: "hmac(sha256)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Crypt: &XfrmStateAlgo{ Name: "cbc(aes)", Key: []byte("abcdefghijklmnopqrstuvwzyzABCDEF"), }, Mark: &XfrmMark{ Value: 0x12340000, Mask: 0xffff0000, }, Selector: genStateSelectorForV6Payload(), } } func getAeadState() *XfrmState { // 128 key bits + 32 salt bits k, _ := hex.DecodeString("d0562776bf0e75830ba3f7f8eb6c09b555aa1177") return &XfrmState{ // Leave IPv4 addresses in Ipv4 in IPv6 notation Src: net.ParseIP("192.168.1.1"), Dst: net.ParseIP("192.168.2.2"), Proto: XFRM_PROTO_ESP, Mode: XFRM_MODE_TUNNEL, Spi: 2, Aead: &XfrmStateAlgo{ Name: "rfc4106(gcm(aes))", Key: k, ICVLen: 64, }, } } func compareSelector(a, b *XfrmPolicy) bool { return a.Src.String() == b.Src.String() && a.Dst.String() == b.Dst.String() && a.Proto == b.Proto && a.DstPort == b.DstPort && a.SrcPort == b.SrcPort && a.Ifindex == b.Ifindex } func compareStates(a, b *XfrmState) bool { if a == b { return true } if a == nil || b == nil { return false } if a.Selector != nil && b.Selector != nil { if !compareSelector(a.Selector, b.Selector) { return false } } return a.Src.Equal(b.Src) && a.Dst.Equal(b.Dst) && a.Mode == b.Mode && a.Spi == b.Spi && a.Proto == b.Proto && a.Ifid == b.Ifid && compareAlgo(a.Auth, b.Auth) && compareAlgo(a.Crypt, b.Crypt) && compareAlgo(a.Aead, b.Aead) && compareMarks(a.Mark, b.Mark) && compareMarks(a.OutputMark, b.OutputMark) } func compareLimits(a, b *XfrmState) bool { return a.Limits.TimeHard == b.Limits.TimeHard && a.Limits.TimeSoft == b.Limits.TimeSoft && a.Limits.PacketHard == b.Limits.PacketHard && a.Limits.PacketSoft == b.Limits.PacketSoft && a.Limits.ByteHard == b.Limits.ByteHard && a.Limits.ByteSoft == b.Limits.ByteSoft && a.Limits.TimeUseHard == b.Limits.TimeUseHard && a.Limits.TimeUseSoft == b.Limits.TimeUseSoft } func compareAlgo(a, b *XfrmStateAlgo) bool { if a == b { return true } if a == nil || b == nil { return false } return a.Name == b.Name && bytes.Equal(a.Key, b.Key) && (a.TruncateLen == 0 || a.TruncateLen == b.TruncateLen) && (a.ICVLen == 0 || a.ICVLen == b.ICVLen) } func compareMarks(a, b *XfrmMark) bool { if a == b { return true } if a == nil || b == nil { return false } return a.Value == b.Value && a.Mask == b.Mask } netlink-1.3.0/xfrm_unspecified.go000066400000000000000000000001461466216277000170540ustar00rootroot00000000000000//go:build !linux // +build !linux package netlink type XfrmPolicy struct{} type XfrmState struct{}