Initial commit of netlink package

This commit is contained in:
Vishvananda Ishaya 2014-08-31 20:27:34 -07:00
commit 8dab8b7462
29 changed files with 3646 additions and 0 deletions

192
LICENSE Normal file
View File

@ -0,0 +1,192 @@
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.

81
README.md Normal file
View File

@ -0,0 +1,81 @@
# nelink - netlink library for go #
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 provied 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 (
"net"
"github.com/vishvananada/netlink"
)
func main() {
mybridge := &netlink.Link{Name: "mybridge", Type: "bridge"}
netlink, _ := netlink.LinkAdd(mybridge)
eth1, _ := netlink.LinkByName("eth1")
netlink.LinkSetMaster(eth1, mybridge)
}
```
Add a new ip address to loopback:
```go
package main
import (
"net"
"github.com/vishvananada/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 primitives don't exist yet.
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.

43
addr.go Normal file
View File

@ -0,0 +1,43 @@
package 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
}
// String returns $ip/$netmask $label
func (addr Addr) String() string {
return fmt.Sprintf("%s %s", addr.IPNet, addr.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
}

148
addr_linux.go Normal file
View File

@ -0,0 +1,148 @@
package netlink
import (
"fmt"
"net"
"strings"
"syscall"
"unsafe"
)
type IfAddrmsg struct {
syscall.IfAddrmsg
}
func newIfAddrmsg(family int) *IfAddrmsg {
return &IfAddrmsg{
IfAddrmsg: syscall.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:syscall.SizeofIfAddrmsg][0]))
}
func (msg *IfAddrmsg) Serialize() []byte {
return (*(*[syscall.SizeofIfAddrmsg]byte)(unsafe.Pointer(msg)))[:]
}
func (msg *IfAddrmsg) Len() int {
return syscall.SizeofIfAddrmsg
}
// AddrAdd will add an IP address to a link device.
// Equivalent to: `ip addr del $addr dev $link`
func AddrAdd(link *Link, addr *Addr) error {
req := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
return addrHandle(link, addr, req)
}
// AddrDel will delete an IP address from a link device.
// Equivalent to: `ip addr del $addr dev $link`
func AddrDel(link *Link, addr *Addr) error {
req := newNetlinkRequest(syscall.RTM_DELADDR, syscall.NLM_F_ACK)
return addrHandle(link, addr, req)
}
func addrHandle(link *Link, addr *Addr, req *NetlinkRequest) error {
if addr.Label != "" && !strings.HasPrefix(addr.Label, link.Name) {
return fmt.Errorf("label must begin with interface name")
}
ensureIndex(link)
family := GetIPFamily(addr.IP)
msg := newIfAddrmsg(family)
msg.Index = uint32(link.Index)
prefixlen, _ := addr.Mask.Size()
msg.Prefixlen = uint8(prefixlen)
req.AddData(msg)
var addrData []byte
if family == FAMILY_V4 {
addrData = addr.IP.To4()
} else {
addrData = addr.IP.To16()
}
localData := newRtAttr(syscall.IFA_LOCAL, addrData)
req.AddData(localData)
addressData := newRtAttr(syscall.IFA_ADDRESS, addrData)
req.AddData(addressData)
if addr.Label != "" {
labelData := newRtAttr(syscall.IFA_LABEL, zeroTerminated(addr.Label))
req.AddData(labelData)
}
_, err := req.Execute(syscall.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) {
req := newNetlinkRequest(syscall.RTM_GETADDR, syscall.NLM_F_DUMP)
msg := newIfInfomsg(family)
req.AddData(msg)
msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWADDR)
if err != nil {
return nil, err
}
ensureIndex(link)
res := make([]Addr, 0)
for _, m := range msgs {
msg := DeserializeIfAddrmsg(m)
if link != nil && msg.Index != uint32(link.Index) {
// Ignore messages from other interfaces
continue
}
attrs, err := parseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
var addr Addr
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.IFA_ADDRESS:
addr.IPNet = net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Prefixlen), 8*len(attr.Value)),
}
case syscall.IFA_LABEL:
addr.Label = string(attr.Value[:len(attr.Value)-1])
}
}
res = append(res, addr)
}
return res, nil
}

39
addr_linux_test.go Normal file
View File

@ -0,0 +1,39 @@
package netlink
import (
"bytes"
"crypto/rand"
"encoding/binary"
"syscall"
"testing"
)
func (msg *IfAddrmsg) write(b []byte) {
native := nativeEndian()
b[0] = msg.Family
b[1] = msg.Prefixlen
b[2] = msg.Flags
b[3] = msg.Scope
native.PutUint32(b[4:8], msg.Index)
}
func (msg *IfAddrmsg) serializeSafe() []byte {
len := syscall.SizeofIfAddrmsg
b := make([]byte, len)
msg.write(b)
return b
}
func deserializeIfAddrmsgSafe(b []byte) *IfAddrmsg {
var msg = IfAddrmsg{}
binary.Read(bytes.NewReader(b[0:syscall.SizeofIfAddrmsg]), nativeEndian(), &msg)
return &msg
}
func TestIfAddrmsgDeserializeSerialize(t *testing.T) {
var orig = make([]byte, syscall.SizeofIfAddrmsg)
rand.Read(orig)
safemsg := deserializeIfAddrmsgSafe(orig)
msg := DeserializeIfAddrmsg(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

46
addr_test.go Normal file
View File

@ -0,0 +1,46 @@
package netlink
import (
"syscall"
"testing"
)
func TestAddrAddDel(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
link, err := LinkByName("lo")
if err != nil {
t.Fatal(err)
}
addr, err := ParseAddr("127.1.1.1/24 local")
if err != nil {
t.Fatal(err)
}
if err = AddrAdd(link, addr); err != nil {
t.Fatal(err)
}
addrs, err := AddrList(link, syscall.AF_INET)
if err != nil {
t.Fatal(err)
}
if len(addrs) != 1 || !addr.Equal(addrs[0]) || addrs[0].Label != addr.Label {
t.Fatal("Address not added properly")
}
if err = AddrDel(link, addr); err != nil {
t.Fatal(err)
}
addrs, err = AddrList(link, syscall.AF_INET)
if err != nil {
t.Fatal(err)
}
if len(addrs) != 0 {
t.Fatal("Address not removed properly")
}
}

27
link.go Normal file
View File

@ -0,0 +1,27 @@
package netlink
import (
"net"
)
// Link represents a link device from netlink. The Type is a string
// representing the type of device. Currently supported types include:
// "dummy", "bridge", "vlan", "macvlan", and "veth". Some of the
// members of Link only apply to some types of link devices.
type Link struct {
Type string
Index int
MTU int
Name string
HardwareAddr net.HardwareAddr
Flags net.Flags
Parent *Link // vlan and macvlan
Master *Link // bridge only
VlanId int // vlan only
PeerName string // veth on create only
}
// iproute2 supported devices;
// vlan | veth | vcan | dummy | ifb | macvlan | macvtap |
// can | bridge | bond | ipoib | ip6tnl | ipip | sit |
// vxlan | gre | gretap | ip6gre | ip6gretap | vti

407
link_linux.go Normal file
View File

@ -0,0 +1,407 @@
package netlink
import (
"encoding/binary"
"fmt"
"net"
"syscall"
)
const (
DEFAULT_CHANGE = 0xFFFFFFFF
)
const (
IFLA_INFO_UNSPEC = iota
IFLA_INFO_KIND = iota
IFLA_INFO_DATA = iota
IFLA_INFO_XSTATS = iota
IFLA_INFO_MAX = IFLA_INFO_XSTATS
)
const (
IFLA_VLAN_UNSPEC = iota
IFLA_VLAN_ID = iota
IFLA_VLAN_FLAGS = iota
IFLA_VLAN_EGRESS_QOS = iota
IFLA_VLAN_INGRESS_QOS = iota
IFLA_VLAN_PROTOCOL = iota
IFLA_VLAN_MAX = IFLA_VLAN_PROTOCOL
)
const (
VETH_INFO_UNSPEC = iota
VETH_INFO_PEER = iota
VETH_INFO_MAX = VETH_INFO_PEER
)
const (
// not defined in syscall
IFLA_NET_NS_FD = 28
)
func ensureIndex(link *Link) {
if link != nil && link.Index == 0 {
newlink, _ := LinkByName(link.Name)
if newlink != nil {
link.Index = newlink.Index
}
}
}
// LinkSetUp enables the link device.
// Equivalent to: `ip link set $link up`
func LinkSetUp(link *Link) error {
ensureIndex(link)
req := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_UP
msg.Flags = syscall.IFF_UP
msg.Index = int32(link.Index)
req.AddData(msg)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LinkSetUp disables link device.
// Equivalent to: `ip link set $link down`
func LinkSetDown(link *Link) error {
ensureIndex(link)
req := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_UP
msg.Flags = 0 & ^syscall.IFF_UP
msg.Index = int32(link.Index)
req.AddData(msg)
_, err := req.Execute(syscall.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 {
ensureIndex(link)
req := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(link.Index)
msg.Change = DEFAULT_CHANGE
req.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(mtu))
data := newRtAttr(syscall.IFLA_MTU, b)
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LinkSetMTU sets the master of the link device. This only works
// for bridges.
// Equivalent to: `ip link set $link master $master`
func LinkSetMaster(link *Link, master *Link) error {
ensureIndex(link)
ensureIndex(master)
req := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(link.Index)
msg.Change = DEFAULT_CHANGE
req.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
index = 0
)
if master != nil {
index = master.Index
}
native.PutUint32(b, uint32(index))
data := newRtAttr(syscall.IFLA_MASTER, b)
req.AddData(data)
_, err := req.Execute(syscall.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 {
req := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(link.Index)
msg.Change = DEFAULT_CHANGE
req.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(nspid))
data := newRtAttr(syscall.IFLA_NET_NS_PID, b)
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LinkSetNsPid 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 {
req := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(link.Index)
msg.Change = DEFAULT_CHANGE
req.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(fd))
data := newRtAttr(IFLA_NET_NS_FD, b)
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg {
msg := newIfInfomsg(family)
parent.children = append(parent.children, msg)
return msg
}
// LinkAdd adds a new link device. The type and features of the device
// are taken fromt the parameters in the link object.
// Equivalent to: `ip link add $link`
func LinkAdd(link *Link) error {
// TODO: set mtu and hardware address
// TODO: support extra data for macvlan
if link.Type == "" || link.Name == "" {
return fmt.Errorf("Neither link.Name nor link.Type can be empty!")
}
req := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
req.AddData(msg)
native := nativeEndian()
if link.Parent != nil {
ensureIndex(link.Parent)
b := make([]byte, 4)
native.PutUint32(b, uint32(link.Parent.Index))
data := newRtAttr(syscall.IFLA_LINK, b)
req.AddData(data)
}
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(link.Name))
req.AddData(nameData)
linkInfo := newRtAttr(syscall.IFLA_LINKINFO, nil)
newRtAttrChild(linkInfo, IFLA_INFO_KIND, nonZeroTerminated(link.Type))
if link.Type == "vlan" {
b := make([]byte, 2)
native.PutUint16(b, uint16(link.VlanId))
data := newRtAttrChild(linkInfo, IFLA_INFO_DATA, nil)
newRtAttrChild(data, IFLA_VLAN_ID, b)
} else if link.Type == "veth" {
data := newRtAttrChild(linkInfo, IFLA_INFO_DATA, nil)
peer := newRtAttrChild(data, VETH_INFO_PEER, nil)
newIfInfomsgChild(peer, syscall.AF_UNSPEC)
newRtAttrChild(peer, syscall.IFLA_IFNAME, zeroTerminated(link.PeerName))
}
req.AddData(linkInfo)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
if err != nil {
return err
}
// can't set master during create, so set it afterwards
if link.Master != nil {
return LinkSetMaster(link, link.Master)
}
return nil
}
// LinkAdd adds a new 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 {
ensureIndex(link)
req := newNetlinkRequest(syscall.RTM_DELLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Index = int32(link.Index)
req.AddData(msg)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LikByName finds a link by name and returns a pointer to the object.
func LinkByName(name string) (*Link, error) {
links, err := LinkList()
if err != nil {
return nil, err
}
for _, link := range links {
if link.Name == name {
return &link, nil
}
}
return nil, fmt.Errorf("Link %s not found", name)
}
// LikByName finds a link by index and returns a pointer to the object.
func LinkByIndex(index int) (*Link, error) {
links, err := LinkList()
if err != nil {
return nil, err
}
for _, link := range links {
if link.Index == index {
return &link, nil
}
}
return nil, fmt.Errorf("Link with index %d not found", index)
}
// LinkList gets a list of link devices.
// Equivalent to: `ip link show`
func 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 := newNetlinkRequest(syscall.RTM_GETLINK, syscall.NLM_F_DUMP)
msg := newIfInfomsg(syscall.AF_UNSPEC)
req.AddData(msg)
msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWLINK)
if err != nil {
return nil, err
}
native := nativeEndian()
res := make([]Link, 0)
for _, m := range msgs {
msg := DeserializeIfInfomsg(m)
attrs, err := parseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
link := Link{Index: int(msg.Index), Flags: linkFlags(msg.Flags)}
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.IFLA_LINKINFO:
infos, err := parseRouteAttr(attr.Value)
if err != nil {
return nil, err
}
for _, info := range infos {
switch info.Attr.Type {
case IFLA_INFO_KIND:
link.Type = string(info.Value[:len(info.Value)-1])
case IFLA_INFO_DATA:
data, err := parseRouteAttr(info.Value)
if err != nil {
return nil, err
}
switch link.Type {
case "vlan":
parseVlanData(&link, data, native)
}
}
}
case syscall.IFLA_ADDRESS:
var nonzero bool
for _, b := range attr.Value {
if b != 0 {
nonzero = true
}
}
if nonzero {
link.HardwareAddr = attr.Value[:]
}
case syscall.IFLA_IFNAME:
link.Name = string(attr.Value[:len(attr.Value)-1])
case syscall.IFLA_MTU:
link.MTU = int(native.Uint32(attr.Value[0:4]))
case syscall.IFLA_LINK:
link.Parent = &Link{Index: int(native.Uint32(attr.Value[0:4]))}
case syscall.IFLA_MASTER:
link.Master = &Link{Index: int(native.Uint32(attr.Value[0:4]))}
}
}
res = append(res, link)
}
return res, nil
}
func parseVlanData(link *Link, data []syscall.NetlinkRouteAttr, native binary.ByteOrder) {
for _, datum := range data {
switch datum.Attr.Type {
case IFLA_VLAN_ID:
link.VlanId = int(native.Uint16(datum.Value[0:2]))
}
}
}
// copied from pkg/net_linux.go
func linkFlags(rawFlags uint32) net.Flags {
var f net.Flags
if rawFlags&syscall.IFF_UP != 0 {
f |= net.FlagUp
}
if rawFlags&syscall.IFF_BROADCAST != 0 {
f |= net.FlagBroadcast
}
if rawFlags&syscall.IFF_LOOPBACK != 0 {
f |= net.FlagLoopback
}
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
f |= net.FlagPointToPoint
}
if rawFlags&syscall.IFF_MULTICAST != 0 {
f |= net.FlagMulticast
}
return f
}

260
link_test.go Normal file
View File

@ -0,0 +1,260 @@
package netlink
import (
"github.com/vishvananda/netns"
"testing"
)
func testLinkAddDel(t *testing.T, link *Link) {
links, err := LinkList()
if err != nil {
t.Fatal(err)
}
num := len(links)
if err := LinkAdd(link); err != nil {
t.Fatal(err)
}
l, err := LinkByName(link.Name)
if err != nil {
t.Fatal(err)
}
if l.Type != link.Type {
t.Fatal("Link.Type doesn't match")
}
if l.VlanId != link.VlanId {
t.Fatal("Link.VlanId id doesn't match")
}
if l.Parent == nil && link.Parent != nil {
t.Fatal("Created link doesn't have a Parent but it should")
} else if l.Parent != nil && link.Parent == nil {
t.Fatal("Created link has a Parent but it shouldn't")
} else if l.Parent != nil && link.Parent != nil {
if l.Parent.Index != link.Parent.Index {
t.Fatal("Link.Parent.Index doesn't match")
}
}
if link.PeerName != "" {
_, err := LinkByName(link.PeerName)
if err != nil {
t.Fatal("Peer %s not created", link.PeerName)
}
}
if err = LinkDel(link); err != nil {
t.Fatal(err)
}
links, err = LinkList()
if err != nil {
t.Fatal(err)
}
if len(links) != num {
t.Fatal("Link not removed properly")
}
}
func TestLinkAddDelDummy(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
testLinkAddDel(t, &Link{Name: "foo", Type: "dummy"})
}
func TestLinkAddDelBridge(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
testLinkAddDel(t, &Link{Name: "foo", Type: "bridge"})
}
func TestLinkAddDelVlan(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
parent := &Link{Name: "foo", Type: "dummy"}
if err := LinkAdd(parent); err != nil {
t.Fatal(err)
}
testLinkAddDel(t, &Link{Name: "bar", Type: "vlan", Parent: parent, VlanId: 900})
if err := LinkDel(parent); err != nil {
t.Fatal(err)
}
}
func TestLinkAddDelMacvlan(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
parent := &Link{Name: "foo", Type: "dummy"}
if err := LinkAdd(parent); err != nil {
t.Fatal(err)
}
testLinkAddDel(t, &Link{Name: "bar", Type: "macvlan", Parent: parent})
if err := LinkDel(parent); err != nil {
t.Fatal(err)
}
}
func TestLinkAddDelVeth(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
testLinkAddDel(t, &Link{Name: "foo", Type: "veth", PeerName: "bar"})
}
func TestLinkAddDelBridgeMaster(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
master := &Link{Name: "foo", Type: "bridge"}
if err := LinkAdd(master); err != nil {
t.Fatal(err)
}
testLinkAddDel(t, &Link{Name: "bar", Type: "dummy", Master: master})
if err := LinkDel(master); err != nil {
t.Fatal(err)
}
}
func TestLinkSetUnsetResetMaster(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
master := &Link{Name: "foo", Type: "bridge"}
if err := LinkAdd(master); err != nil {
t.Fatal(err)
}
newmaster := &Link{Name: "bar", Type: "bridge"}
if err := LinkAdd(newmaster); err != nil {
t.Fatal(err)
}
slave := &Link{Name: "baz", Type: "dummy"}
if err := LinkAdd(slave); err != nil {
t.Fatal(err)
}
if err := LinkSetMaster(slave, master); err != nil {
t.Fatal(err)
}
link, err := LinkByName("baz")
if err != nil {
t.Fatal(err)
}
if link.Master == nil || link.Master.Index != master.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.Master == nil || link.Master.Index != newmaster.Index {
t.Fatal("Master not reset properly")
}
if err := LinkSetMaster(slave, nil); err != nil {
t.Fatal(err)
}
link, err = LinkByName("baz")
if err != nil {
t.Fatal(err)
}
if link.Master != nil {
t.Fatal("Master not unset properly")
}
if err := LinkDel(slave); err != nil {
t.Fatal(err)
}
if err := LinkDel(newmaster); err != nil {
t.Fatal(err)
}
if err := LinkDel(master); err != nil {
t.Fatal(err)
}
}
func TestLinkSetNs(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
basens, err := netns.Get()
if err != nil {
t.Fatal("Failed to get basens")
}
defer basens.Close()
newns, err := netns.New()
if err != nil {
t.Fatal("Failed to create newns")
}
defer newns.Close()
link := &Link{Name: "foo", Type: "veth", PeerName: "bar"}
if err := LinkAdd(link); err != nil {
t.Fatal(err)
}
peer, err := LinkByName("bar")
if err != nil {
t.Fatal(err)
}
LinkSetNsFd(peer, int(basens))
if err != nil {
t.Fatal("Failed to set newns for link")
}
_, err = LinkByName("bar")
if err == nil {
t.Fatal("Link bar is still in newns")
}
err = netns.Set(basens)
if err != nil {
t.Fatal("Failed to set basens")
}
peer, err = LinkByName("bar")
if err != nil {
t.Fatal("Link is not in basens")
}
if err := LinkDel(peer); err != nil {
t.Fatal(err)
}
err = netns.Set(newns)
if err != nil {
t.Fatal("Failed to set newns")
}
_, err = LinkByName("foo")
if err == nil {
t.Fatal("Other half of veth pair not deleted")
}
}

44
netlink.go Normal file
View File

@ -0,0 +1,44 @@
// 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. In addition to dealing with netlink primitives, the
// library attempts to provide an high-level interface that is loosly
// modeled on the iproute2 command line interface.
package netlink
import (
"net"
"syscall"
)
const (
// Family type definitions
FAMILY_ALL = syscall.AF_UNSPEC
FAMILY_V4 = syscall.AF_INET
FAMILY_V6 = syscall.AF_INET6
)
// 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
}
// 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 net.IPNet{}, err
}
return net.IPNet{ip, ipNet.Mask}, nil
}

333
netlink_linux.go Normal file
View File

@ -0,0 +1,333 @@
package netlink
import (
"bytes"
"encoding/binary"
"fmt"
"sync/atomic"
"syscall"
"unsafe"
)
var nextSeqNr uint32
// Get native endianness for the system
func nativeEndian() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
// Byte swap a 16 bit value in place
func swap16(i uint16) uint16 {
return (i&0xff00)>>8 | (i&0xff)<<8
}
// Byte swap a 32 bit value in place
func swap32(i uint32) uint32 {
return (i&0xff000000)>>24 | (i&0xff0000)>>8 | (i&0xff00)<<8 | (i&0xff)<<24
}
type NetlinkRequestData interface {
Len() int
Serialize() []byte
}
// IfInfomsg is related to links, but it is used for list requests as well
type IfInfomsg struct {
syscall.IfInfomsg
}
// Create an IfInfomsg with family specified
func newIfInfomsg(family int) *IfInfomsg {
return &IfInfomsg{
IfInfomsg: syscall.IfInfomsg{
Family: uint8(family),
},
}
}
func DeserializeIfInfomsg(b []byte) *IfInfomsg {
return (*IfInfomsg)(unsafe.Pointer(&b[0:syscall.SizeofIfInfomsg][0]))
}
func (msg *IfInfomsg) Serialize() []byte {
return (*(*[syscall.SizeofIfInfomsg]byte)(unsafe.Pointer(msg)))[:]
}
func (msg *IfInfomsg) Len() int {
return syscall.SizeofIfInfomsg
}
func rtaAlignOf(attrlen int) int {
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
}
// Extend RtAttr to handle data and children
type RtAttr struct {
syscall.RtAttr
Data []byte
children []NetlinkRequestData
}
// Create a new Extended RtAttr object
func newRtAttr(attrType int, data []byte) *RtAttr {
return &RtAttr{
RtAttr: syscall.RtAttr{
Type: uint16(attrType),
},
children: []NetlinkRequestData{},
Data: data,
}
}
// Create a new RtAttr obj anc add it as a child of an existing object
func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
attr := newRtAttr(attrType, data)
parent.children = append(parent.children, attr)
return attr
}
func (a *RtAttr) Len() int {
if len(a.children) == 0 {
return (syscall.SizeofRtAttr + len(a.Data))
}
l := 0
for _, child := range a.children {
l += child.Len()
}
l += syscall.SizeofRtAttr
return rtaAlignOf(l + len(a.Data))
}
// Serialize the RtAttr into a byte array
// This can't ust unsafe.cast because it must iterate through children.
func (a *RtAttr) Serialize() []byte {
native := nativeEndian()
length := a.Len()
buf := make([]byte, rtaAlignOf(length))
if a.Data != nil {
copy(buf[4:], a.Data)
} else {
next := 4
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 {
syscall.NlMsghdr
Data []NetlinkRequestData
}
// Serialize the Netlink Request into a byte array
func (msg *NetlinkRequest) Serialize() []byte {
length := syscall.SizeofNlMsghdr
dataBytes := make([][]byte, len(msg.Data))
for i, data := range msg.Data {
dataBytes[i] = data.Serialize()
length = length + len(dataBytes[i])
}
msg.Len = uint32(length)
b := make([]byte, length)
hdr := (*(*[syscall.SizeofNlMsghdr]byte)(unsafe.Pointer(msg)))[:]
next := syscall.SizeofNlMsghdr
copy(b[0:next], hdr)
for _, data := range dataBytes {
for _, dataByte := range data {
b[next] = dataByte
next = next + 1
}
}
return b
}
func (msg *NetlinkRequest) AddData(data NetlinkRequestData) {
if data != nil {
msg.Data = append(msg.Data, data)
}
}
// Execute the request against a the given sockType.
// Returns a list of netlink messages in seriaized format, optionally filtered
// by resType.
func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) {
s, err := getNetlinkSocket(sockType)
if err != nil {
return nil, err
}
defer s.Close()
if err := s.Send(req); err != nil {
return nil, err
}
pid, err := s.GetPid()
if err != nil {
return nil, err
}
res := make([][]byte, 0)
done:
for {
msgs, err := s.Recieve()
if err != nil {
return nil, err
}
for _, m := range msgs {
if m.Header.Seq != req.Seq {
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
}
if m.Header.Pid != pid {
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
native := nativeEndian()
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
}
return nil, syscall.Errno(-error)
}
if resType != 0 && m.Header.Type != resType {
continue
}
res = append(res, m.Data)
}
}
return res, nil
}
// 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: syscall.NlMsghdr{
Len: uint32(syscall.SizeofNlMsghdr),
Type: uint16(proto),
Flags: syscall.NLM_F_REQUEST | uint16(flags),
Seq: atomic.AddUint32(&nextSeqNr, 1),
},
}
}
type NetlinkSocket struct {
fd int
lsa syscall.SockaddrNetlink
}
func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
if err != nil {
return nil, err
}
s := &NetlinkSocket{
fd: fd,
}
s.lsa.Family = syscall.AF_NETLINK
if err := syscall.Bind(fd, &s.lsa); err != nil {
syscall.Close(fd)
return nil, err
}
return s, nil
}
func (s *NetlinkSocket) Close() {
syscall.Close(s.fd)
}
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
if err := syscall.Sendto(s.fd, request.Serialize(), 0, &s.lsa); err != nil {
return err
}
return nil
}
func (s *NetlinkSocket) Recieve() ([]syscall.NetlinkMessage, error) {
rb := make([]byte, syscall.Getpagesize())
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
if err != nil {
return nil, err
}
if nr < syscall.NLMSG_HDRLEN {
return nil, fmt.Errorf("Got short response from netlink")
}
rb = rb[:nr]
return syscall.ParseNetlinkMessage(rb)
}
func (s *NetlinkSocket) GetPid() (uint32, error) {
lsa, err := syscall.Getsockname(s.fd)
if err != nil {
return 0, err
}
switch v := lsa.(type) {
case *syscall.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 parseRouteAttr(b []byte) ([]syscall.NetlinkRouteAttr, error) {
var attrs []syscall.NetlinkRouteAttr
for len(b) >= syscall.SizeofRtAttr {
a, vbuf, alen, err := netlinkRouteAttrAndValue(b)
if err != nil {
return nil, err
}
ra := syscall.NetlinkRouteAttr{Attr: *a, Value: vbuf[:int(a.Len)-syscall.SizeofRtAttr]}
attrs = append(attrs, ra)
b = b[alen:]
}
return attrs, nil
}
func netlinkRouteAttrAndValue(b []byte) (*syscall.RtAttr, []byte, int, error) {
a := (*syscall.RtAttr)(unsafe.Pointer(&b[0]))
if int(a.Len) < syscall.SizeofRtAttr || int(a.Len) > len(b) {
return nil, nil, 0, syscall.EINVAL
}
return a, b[syscall.SizeofRtAttr:], rtaAlignOf(int(a.Len)), nil
}

40
netlink_linux_test.go Normal file
View File

@ -0,0 +1,40 @@
package netlink
import (
"bytes"
"crypto/rand"
"encoding/binary"
"syscall"
"testing"
)
func (msg *IfInfomsg) write(b []byte) {
native := nativeEndian()
b[0] = msg.Family
b[1] = msg.X__ifi_pad
native.PutUint16(b[2:4], msg.Type)
native.PutUint32(b[4:8], uint32(msg.Index))
native.PutUint32(b[8:12], msg.Flags)
native.PutUint32(b[12:16], msg.Change)
}
func (msg *IfInfomsg) serializeSafe() []byte {
length := syscall.SizeofIfInfomsg
b := make([]byte, length)
msg.write(b)
return b
}
func deserializeIfInfomsgSafe(b []byte) *IfInfomsg {
var msg = IfInfomsg{}
binary.Read(bytes.NewReader(b[0:syscall.SizeofIfInfomsg]), nativeEndian(), &msg)
return &msg
}
func TestIfInfomsgDeserializeSerialize(t *testing.T) {
var orig = make([]byte, syscall.SizeofIfInfomsg)
rand.Read(orig)
safemsg := deserializeIfInfomsgSafe(orig)
msg := DeserializeIfInfomsg(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

56
netlink_test.go Normal file
View File

@ -0,0 +1,56 @@
package netlink
import (
"bytes"
"github.com/vishvananda/netns"
"log"
"os"
"reflect"
"runtime"
"testing"
)
type tearDownNetlinkTest func()
func setUpNetlinkTest(t *testing.T) tearDownNetlinkTest {
if os.Getuid() != 0 {
msg := "Skipped test because it requires root privileges."
log.Printf(msg)
t.Skip(msg)
}
// 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()
}
}
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)
}
}

95
netlink_unspecified.go Normal file
View File

@ -0,0 +1,95 @@
// +build !linux
package netlink
import (
"errors"
)
var (
ErrNotImplemented = errors.New("not implemented")
)
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 LinkAdd(link *Link) error {
return ErrNotImplemented
}
func LinkDel(link *Link) error {
return ErrNotImplemented
}
func LinkList() ([]Link, error) {
return nil, ErrNotImplemented
}
func AddrAdd(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 RouteDel(route *Route) error {
return ErrNotImplemented
}
func RouteList(link *Link, family int) ([]Route, error) {
return nil, 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 XfrmStateAdd(policy *XfrmState) error {
return ErrNotImplemented
}
func XfrmStateDel(policy *XfrmState) error {
return ErrNotImplemented
}
func XfrmStateList(family int) ([]XfrmState, error) {
return nil, ErrNotImplemented
}

22
route.go Normal file
View File

@ -0,0 +1,22 @@
package netlink
import (
"fmt"
"net"
)
// Route represents a netlink route. A route is associated with a link,
// has a destination network, an optional source ip, and optional
// gateway. Advanced route parameters and non-main routing tables are
// currently not supported.
type Route struct {
Link Link
Dst net.IPNet
Src net.IP
Gw net.IP
}
func (r Route) String() string {
return fmt.Sprintf("{%s Dst: %s Src: %s Gw: %s}", r.Link.Name, r.Dst.String(),
r.Src, r.Gw)
}

185
route_linux.go Normal file
View File

@ -0,0 +1,185 @@
package netlink
import (
"fmt"
"net"
"syscall"
"unsafe"
)
type RtMsg struct {
syscall.RtMsg
}
func newRtMsg() *RtMsg {
return &RtMsg{
RtMsg: syscall.RtMsg{
Table: syscall.RT_TABLE_MAIN,
Scope: syscall.RT_SCOPE_UNIVERSE,
Protocol: syscall.RTPROT_BOOT,
Type: syscall.RTN_UNICAST,
},
}
}
func (msg *RtMsg) Len() int {
return syscall.SizeofRtMsg
}
func DeserializeRtMsg(b []byte) *RtMsg {
return (*RtMsg)(unsafe.Pointer(&b[0:syscall.SizeofRtMsg][0]))
}
func (msg *RtMsg) Serialize() []byte {
return (*(*[syscall.SizeofRtMsg]byte)(unsafe.Pointer(msg)))[:]
}
// RtAttr is shared so it is in netlink_linux.go
// RouteAdd will add a route to the system.
// Equivalent to: `ip route add $route`
func RouteAdd(route *Route) error {
req := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
return routeHandle(route, req)
}
// RouteAdd will delete a route from the system.
// Equivalent to: `ip route del $route`
func RouteDel(route *Route) error {
req := newNetlinkRequest(syscall.RTM_DELROUTE, syscall.NLM_F_ACK)
return routeHandle(route, req)
}
func routeHandle(route *Route, req *NetlinkRequest) error {
if route.Dst.IP == nil && route.Src == nil && route.Gw == nil {
return fmt.Errorf("one of Dst.IP, Src, or Gw must not be nil")
}
msg := newRtMsg()
family := -1
var rtAttrs []*RtAttr
if route.Dst.IP != nil {
dstLen, _ := route.Dst.Mask.Size()
msg.Dst_len = uint8(dstLen)
dstFamily := GetIPFamily(route.Dst.IP)
family = dstFamily
var dstData []byte
if dstFamily == syscall.AF_INET {
dstData = route.Dst.IP.To4()
} else {
dstData = route.Dst.IP.To16()
}
rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_DST, dstData))
}
if route.Src != nil {
srcFamily := 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 == syscall.AF_INET {
srcData = route.Src.To4()
} else {
srcData = route.Src.To16()
}
// The commonly used src ip for routes is actually PREFSRC
rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_PREFSRC, srcData))
}
if route.Gw != nil {
gwFamily := 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 == syscall.AF_INET {
gwData = route.Gw.To4()
} else {
gwData = route.Gw.To16()
}
rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_GATEWAY, gwData))
}
msg.Family = uint8(family)
req.AddData(msg)
for _, attr := range rtAttrs {
req.AddData(attr)
}
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(route.Link.Index))
req.AddData(newRtAttr(syscall.RTA_OIF, b))
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// 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) {
req := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
msg := newIfInfomsg(family)
req.AddData(msg)
msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWROUTE)
if err != nil {
return nil, err
}
native := nativeEndian()
res := make([]Route, 0)
for _, m := range msgs {
msg := DeserializeRtMsg(m)
if msg.Flags&syscall.RTM_F_CLONED != 0 {
// Ignore cloned routes
continue
}
if msg.Table != syscall.RT_TABLE_MAIN {
// Ignore non-main tables
continue
}
attrs, err := parseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
var route Route
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_GATEWAY:
route.Gw = net.IP(attr.Value)
case syscall.RTA_PREFSRC:
route.Src = net.IP(attr.Value)
case syscall.RTA_DST:
route.Dst = net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
}
case syscall.RTA_OIF:
index := int(native.Uint32(attr.Value[0:4]))
if link != nil && index != link.Index {
// Ignore routes from other interfaces
continue
}
resLink, _ := LinkByIndex(index)
route.Link = *resLink
}
}
res = append(res, route)
}
return res, nil
}

43
route_linux_test.go Normal file
View File

@ -0,0 +1,43 @@
package netlink
import (
"bytes"
"crypto/rand"
"encoding/binary"
"syscall"
"testing"
)
func (msg *RtMsg) write(b []byte) {
native := nativeEndian()
b[0] = msg.Family
b[1] = msg.Dst_len
b[2] = msg.Src_len
b[3] = msg.Tos
b[4] = msg.Table
b[5] = msg.Protocol
b[6] = msg.Scope
b[7] = msg.Type
native.PutUint32(b[8:12], msg.Flags)
}
func (msg *RtMsg) serializeSafe() []byte {
len := syscall.SizeofRtMsg
b := make([]byte, len)
msg.write(b)
return b
}
func deserializeRtMsgSafe(b []byte) *RtMsg {
var msg = RtMsg{}
binary.Read(bytes.NewReader(b[0:syscall.SizeofRtMsg]), nativeEndian(), &msg)
return &msg
}
func TestRtMsgDeserializeSerialize(t *testing.T) {
var orig = make([]byte, syscall.SizeofRtMsg)
rand.Read(orig)
safemsg := deserializeRtMsgSafe(orig)
msg := DeserializeRtMsg(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

53
route_test.go Normal file
View File

@ -0,0 +1,53 @@
package netlink
import (
"net"
"testing"
)
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, err := net.ParseCIDR("192.168.0.0/24")
ip := net.ParseIP("127.1.1.1")
route := Route{Link: *link, Dst: *dst, Src: ip}
err = RouteAdd(&route)
if err != nil {
t.Fatal(err)
}
routes, err := RouteList(link, FAMILY_V4)
if err != nil {
t.Fatal(err)
}
if len(routes) != 1 {
t.Fatal("Link not removed properly")
}
err = RouteDel(&route)
if 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")
}
}

65
xfrm.go Normal file
View File

@ -0,0 +1,65 @@
package netlink
import (
"fmt"
"syscall"
)
// Proto is an enum representing an ipsec protocol.
type Proto uint8
const (
XFRM_PROTO_ROUTE2 Proto = syscall.IPPROTO_ROUTING
XFRM_PROTO_ESP Proto = syscall.IPPROTO_ESP
XFRM_PROTO_AH Proto = syscall.IPPROTO_AH
XFRM_PROTO_HAO Proto = syscall.IPPROTO_DSTOPTS
XFRM_PROTO_COMP Proto = syscall.IPPROTO_COMP
XFRM_PROTO_IPSEC_ANY Proto = syscall.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 Mode = iota
XFRM_MODE_ROUTEOPTIMIZATION Mode = iota
XFRM_MODE_IN_TRIGGER Mode = iota
XFRM_MODE_BEET Mode = iota
XFRM_MODE_MAX Mode = iota
)
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)
}

273
xfrm_linux.go Normal file
View File

@ -0,0 +1,273 @@
package netlink
import (
"bytes"
"net"
"unsafe"
)
const (
XFRM_MSG_BASE = 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 = 0x00
XFRMA_ALG_AUTH = 0x01 /* struct xfrm_algo */
XFRMA_ALG_CRYPT = 0x02 /* struct xfrm_algo */
XFRMA_ALG_COMP = 0x03 /* struct xfrm_algo */
XFRMA_ENCAP = 0x04 /* struct xfrm_algo + struct xfrm_encap_tmpl */
XFRMA_TMPL = 0x05 /* 1 or more struct xfrm_user_tmpl */
XFRMA_SA = 0x06 /* struct xfrm_usersa_info */
XFRMA_POLICY = 0x07 /* struct xfrm_userpolicy_info */
XFRMA_SEC_CTX = 0x08 /* struct xfrm_sec_ctx */
XFRMA_LTIME_VAL = 0x09
XFRMA_REPLAY_VAL = 0x0a
XFRMA_REPLAY_THRESH = 0x0b
XFRMA_ETIMER_THRESH = 0x0c
XFRMA_SRCADDR = 0x0d /* xfrm_address_t */
XFRMA_COADDR = 0x0e /* xfrm_address_t */
XFRMA_LASTUSED = 0x0f /* unsigned long */
XFRMA_POLICY_TYPE = 0x10 /* struct xfrm_userpolicy_type */
XFRMA_MIGRATE = 0x11
XFRMA_ALG_AEAD = 0x12 /* struct xfrm_algo_aead */
XFRMA_KMADDRESS = 0x13 /* struct xfrm_user_kmaddress */
XFRMA_ALG_AUTH_TRUNC = 0x14 /* struct xfrm_algo_auth */
XFRMA_MARK = 0x15 /* struct xfrm_mark */
XFRMA_TFCPAD = 0x16 /* __u32 */
XFRMA_REPLAY_ESN_VAL = 0x17 /* struct xfrm_replay_esn */
XFRMA_SA_EXTRA_FLAGS = 0x18 /* __u32 */
XFRMA_MAX = 0x18
)
const (
SizeofXfrmAddress = 0x10
SizeofXfrmSelector = 0x38
SizeofXfrmLifetimeCfg = 0x40
SizeofXfrmLifetimeCur = 0x20
SizeofXfrmId = 0x18
)
// 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
}
func (x *XfrmAddress) ToIPNet(prefixlen uint8) net.IPNet {
ip := x.ToIP()
if GetIPFamily(ip) == FAMILY_V4 {
return net.IPNet{ip, net.CIDRMask(int(prefixlen), 32)}
} else {
return net.IPNet{ip, 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 (msg *XfrmAddress) Serialize() []byte {
return (*(*[SizeofXfrmAddress]byte)(unsafe.Pointer(msg)))[:]
}
// 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)))[:]
}
func (sel *XfrmSelector) FromPolicy(policy *XfrmPolicy) {
sel.Family = uint16(GetIPFamily(policy.Dst.IP))
sel.Daddr.FromIP(policy.Dst.IP)
sel.Saddr.FromIP(policy.Src.IP)
prefixlenD, _ := policy.Dst.Mask.Size()
sel.PrefixlenD = uint8(prefixlenD)
prefixlenS, _ := policy.Src.Mask.Size()
sel.PrefixlenS = uint8(prefixlenS)
}
func (sel *XfrmSelector) FromState(state *XfrmPolicy) {
sel.Family = uint16(GetIPFamily(state.Dst.IP))
sel.Daddr.FromIP(state.Dst.IP)
sel.Saddr.FromIP(state.Src.IP)
prefixlenD, _ := state.Dst.Mask.Size()
sel.PrefixlenD = uint8(prefixlenD)
prefixlenS, _ := state.Src.Mask.Size()
sel.PrefixlenS = uint8(prefixlenS)
}
// 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)))[:]
}

161
xfrm_linux_test.go Normal file
View File

@ -0,0 +1,161 @@
package netlink
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)
}

47
xfrm_policy.go Normal file
View File

@ -0,0 +1,47 @@
package netlink
import (
"fmt"
"net"
)
// Dir is an enum representing an ipsec template direction.
type Dir uint8
const (
XFRM_DIR_IN = iota
XFRM_DIR_OUT = iota
)
func (d Dir) String() string {
switch d {
case XFRM_DIR_IN:
return "in"
case XFRM_DIR_OUT:
return "out"
}
return fmt.Sprintf("%d", d)
}
// 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
Reqid int
}
// 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
Dir Dir
Priority int
Index int
Tmpls []XfrmPolicyTmpl
}

223
xfrm_policy_linux.go Normal file
View File

@ -0,0 +1,223 @@
package netlink
import (
"syscall"
"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)))[:]
}
// XfrmPolicyAdd will add an xfrm policy to the system.
// Equivalent to: `ip xfrm policy add $policy`
func XfrmPolicyAdd(policy *XfrmPolicy) error {
req := newNetlinkRequest(XFRM_MSG_NEWPOLICY, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := &XfrmUserpolicyInfo{}
msg.Sel.FromPolicy(policy)
msg.Priority = uint32(policy.Priority)
msg.Index = uint32(policy.Index)
msg.Dir = uint8(policy.Dir)
req.AddData(msg)
tmplData := make([]byte, SizeofXfrmUserTmpl*len(policy.Tmpls))
for i, tmpl := range policy.Tmpls {
start := i * SizeofXfrmUserTmpl
userTmpl := DeserializeXfrmUserTmpl(tmplData[start : start+SizeofXfrmUserTmpl])
userTmpl.XfrmId.Daddr.FromIP(tmpl.Dst)
userTmpl.Saddr.FromIP(tmpl.Src)
userTmpl.XfrmId.Proto = uint8(tmpl.Proto)
userTmpl.Mode = uint8(tmpl.Mode)
userTmpl.Reqid = uint32(tmpl.Reqid)
}
if len(tmplData) > 0 {
tmpls := newRtAttr(XFRMA_TMPL, tmplData)
req.AddData(tmpls)
}
_, err := req.Execute(syscall.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 {
req := newNetlinkRequest(XFRM_MSG_DELPOLICY, syscall.NLM_F_ACK)
msg := &XfrmUserpolicyId{}
msg.Sel.FromPolicy(policy)
msg.Index = uint32(policy.Index)
msg.Dir = uint8(policy.Dir)
req.AddData(msg)
_, err := req.Execute(syscall.NETLINK_XFRM, 0)
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) {
req := newNetlinkRequest(XFRM_MSG_GETPOLICY, syscall.NLM_F_DUMP)
msg := newIfInfomsg(family)
req.AddData(msg)
msgs, err := req.Execute(syscall.NETLINK_XFRM, XFRM_MSG_NEWPOLICY)
if err != nil {
return nil, err
}
res := make([]XfrmPolicy, 0)
for _, m := range msgs {
msg := DeserializeXfrmUserpolicyInfo(m)
if family != FAMILY_ALL && family != int(msg.Sel.Family) {
continue
}
var policy XfrmPolicy
policy.Dst = msg.Sel.Daddr.ToIPNet(msg.Sel.PrefixlenD)
policy.Src = msg.Sel.Saddr.ToIPNet(msg.Sel.PrefixlenS)
policy.Priority = int(msg.Priority)
policy.Index = int(msg.Index)
policy.Dir = Dir(msg.Dir)
attrs, err := parseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
for _, attr := range attrs {
switch attr.Attr.Type {
case XFRMA_TMPL:
max := len(attr.Value)
for i := 0; i < max; i += SizeofXfrmUserTmpl {
var resTmpl XfrmPolicyTmpl
tmpl := DeserializeXfrmUserTmpl(attr.Value[i : i+SizeofXfrmUserTmpl])
resTmpl.Dst = tmpl.XfrmId.Daddr.ToIP()
resTmpl.Src = tmpl.Saddr.ToIP()
resTmpl.Proto = Proto(tmpl.XfrmId.Proto)
resTmpl.Mode = Mode(tmpl.Mode)
resTmpl.Reqid = int(tmpl.Reqid)
policy.Tmpls = append(policy.Tmpls, resTmpl)
}
}
}
res = append(res, policy)
}
return res, nil
}

109
xfrm_policy_linux_test.go Normal file
View File

@ -0,0 +1,109 @@
package netlink
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)
}

49
xfrm_policy_test.go Normal file
View File

@ -0,0 +1,49 @@
package netlink
import (
"net"
"testing"
)
func TestXfrmPolicyAddDel(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
src, _ := ParseIPNet("127.1.1.1/32")
dst, _ := ParseIPNet("127.1.1.2/32")
policy := XfrmPolicy{
Src: src,
Dst: dst,
Dir: XFRM_DIR_OUT,
}
tmpl := XfrmPolicyTmpl{
Src: net.ParseIP("127.0.0.1"),
Dst: net.ParseIP("127.0.0.2"),
Proto: XFRM_PROTO_ESP,
Mode: XFRM_MODE_TUNNEL,
}
policy.Tmpls = append(policy.Tmpls, tmpl)
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 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")
}
}

25
xfrm_state.go Normal file
View File

@ -0,0 +1,25 @@
package netlink
import (
"net"
)
// XfrmStateAlgo represents the algorithm to use for the ipsec encryption.
type XfrmStateAlgo struct {
Name string
Key []byte
TruncateLen int // Auth only
}
// 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
Auth *XfrmStateAlgo
Crypt *XfrmStateAlgo
}

352
xfrm_state_linux.go Normal file
View File

@ -0,0 +1,352 @@
package netlink
import (
"fmt"
"syscall"
"unsafe"
)
const (
SizeofXfrmUsersaId = 0x18
SizeofXfrmStats = 0x0c
SizeofXfrmUsersaInfo = 0xe0
SizeofXfrmAlgo = 0x44
SizeofXfrmAlgoAuth = 0x48
)
// 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
//
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 // TODO: investigate enum
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_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];
// }
// struct xfrm_encap_tmpl {
// __u16 encap_type;
// __be16 encap_sport;
// __be16 encap_dport;
// xfrm_address_t encap_oa;
// };
func writeStateAlgo(a *XfrmStateAlgo) []byte {
algo := 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 := 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()
}
// XfrmStateAdd will add an xfrm state to the system.
// Equivalent to: `ip xfrm state add $state`
func XfrmStateAdd(state *XfrmState) 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 := newNetlinkRequest(XFRM_MSG_NEWSA, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := &XfrmUsersaInfo{}
msg.Family = uint16(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 = swap32(uint32(state.Spi))
msg.Reqid = uint32(state.Reqid)
req.AddData(msg)
if state.Auth != nil {
out := newRtAttr(XFRMA_ALG_AUTH_TRUNC, writeStateAlgoAuth(state.Auth))
req.AddData(out)
}
if state.Crypt != nil {
out := newRtAttr(XFRMA_ALG_CRYPT, writeStateAlgo(state.Crypt))
req.AddData(out)
}
_, err := req.Execute(syscall.NETLINK_XFRM, 0)
return err
}
// 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 {
req := newNetlinkRequest(XFRM_MSG_DELSA, syscall.NLM_F_ACK)
msg := &XfrmUsersaId{}
msg.Daddr.FromIP(state.Dst)
msg.Family = uint16(GetIPFamily(state.Dst))
msg.Proto = uint8(state.Proto)
msg.Spi = swap32(uint32(state.Spi))
req.AddData(msg)
saddr := XfrmAddress{}
saddr.FromIP(state.Src)
srcdata := newRtAttr(XFRMA_SRCADDR, saddr.Serialize())
req.AddData(srcdata)
_, err := req.Execute(syscall.NETLINK_XFRM, 0)
return err
}
// 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 XfrmStateList(family int) ([]XfrmState, error) {
req := newNetlinkRequest(XFRM_MSG_GETSA, syscall.NLM_F_DUMP)
msg := newIfInfomsg(family)
req.AddData(msg)
msgs, err := req.Execute(syscall.NETLINK_XFRM, XFRM_MSG_NEWSA)
if err != nil {
return nil, err
}
res := make([]XfrmState, 0)
for _, m := range msgs {
msg := DeserializeXfrmUsersaInfo(m)
if family != FAMILY_ALL && family != int(msg.Family) {
continue
}
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(swap32(msg.Id.Spi))
state.Reqid = int(msg.Reqid)
attrs, err := parseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
for _, attr := range attrs {
switch attr.Attr.Type {
case XFRMA_ALG_AUTH, XFRMA_ALG_CRYPT:
var resAlgo *XfrmStateAlgo
if attr.Attr.Type == XFRMA_ALG_AUTH {
if state.Auth == nil {
state.Auth = new(XfrmStateAlgo)
}
resAlgo = state.Auth
} else {
state.Crypt = new(XfrmStateAlgo)
resAlgo = state.Crypt
}
algo := DeserializeXfrmAlgo(attr.Value[:])
(*resAlgo).Name = bytesToString(algo.AlgName[:])
(*resAlgo).Key = algo.AlgKey
case XFRMA_ALG_AUTH_TRUNC:
if state.Auth == nil {
state.Auth = new(XfrmStateAlgo)
}
algo := DeserializeXfrmAlgoAuth(attr.Value[:])
state.Auth.Name = bytesToString(algo.AlgName[:])
state.Auth.Key = algo.AlgKey
state.Auth.TruncateLen = int(algo.AlgTruncLen)
}
}
res = append(res, state)
}
return res, nil
}

178
xfrm_state_linux_test.go Normal file
View File

@ -0,0 +1,178 @@
package netlink
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 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) {
// use a 32 byte key len
var orig = make([]byte, SizeofXfrmAlgo+32)
rand.Read(orig)
// set the key len to 256 bits
orig[64] = 0
orig[65] = 1
orig[66] = 0
orig[67] = 0
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) {
// use a 32 byte key len
var orig = make([]byte, SizeofXfrmAlgoAuth+32)
rand.Read(orig)
// set the key len to 256 bits
orig[64] = 0
orig[65] = 1
orig[66] = 0
orig[67] = 0
safemsg := deserializeXfrmAlgoAuthSafe(orig)
msg := DeserializeXfrmAlgoAuth(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

50
xfrm_state_test.go Normal file
View File

@ -0,0 +1,50 @@
package netlink
import (
"net"
"testing"
)
func TestXfrmStateAddDel(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
state := XfrmState{
Src: net.ParseIP("127.0.0.1"),
Dst: net.ParseIP("127.0.0.2"),
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"),
},
}
if err := XfrmStateAdd(&state); err != nil {
t.Fatal(err)
}
policies, err := XfrmStateList(FAMILY_ALL)
if err != nil {
t.Fatal(err)
}
if len(policies) != 1 {
t.Fatal("State not added properly")
}
if err = XfrmStateDel(&state); err != nil {
t.Fatal(err)
}
policies, err = XfrmStateList(FAMILY_ALL)
if err != nil {
t.Fatal(err)
}
if len(policies) != 0 {
t.Fatal("State not removed properly")
}
}