Skip to content

Commit

Permalink
added support for Foo-over-UDP netlink calls
Browse files Browse the repository at this point in the history
Signed-off-by: Reinier Schoof <reinier@skoef.nl>
  • Loading branch information
skoef authored and aboch committed Nov 13, 2017
1 parent aa48b8c commit bdf753e
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 0 deletions.
64 changes: 64 additions & 0 deletions fou.go
@@ -0,0 +1,64 @@
package netlink

import (
"encoding/binary"
"errors"
)

var (
// ErrAttrHeaderTruncated is returned when a netlink attribute's header is
// truncated.
ErrAttrHeaderTruncated = errors.New("attribute header truncated")
// ErrAttrBodyTruncated is returned when a netlink attribute's body is
// truncated.
ErrAttrBodyTruncated = errors.New("attribute body truncated")
)

type Fou struct {
Family int
Port int
Protocol int
EncapType int
}

func deserializeFouMsg(msg []byte) (Fou, error) {
// we'll skip to byte 4 to first attribute
msg = msg[3:]
var shift int
fou := Fou{}

for {
// attribute header is at least 16 bits
if len(msg) < 4 {
return fou, ErrAttrHeaderTruncated
}

lgt := int(binary.BigEndian.Uint16(msg[0:2]))
if len(msg) < lgt+4 {
return fou, ErrAttrBodyTruncated
}
attr := binary.BigEndian.Uint16(msg[2:4])

shift = lgt + 3
switch attr {
case FOU_ATTR_AF:
fou.Family = int(msg[5])
case FOU_ATTR_PORT:
fou.Port = int(binary.BigEndian.Uint16(msg[5:7]))
// port is 2 bytes
shift = lgt + 2
case FOU_ATTR_IPPROTO:
fou.Protocol = int(msg[5])
case FOU_ATTR_TYPE:
fou.EncapType = int(msg[5])
}

msg = msg[shift:]

if len(msg) < 4 {
break
}
}

return fou, nil
}
173 changes: 173 additions & 0 deletions fou_linux.go
@@ -0,0 +1,173 @@
// +build linux

package netlink

import (
"encoding/binary"
"errors"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

const (
FOU_GENL_NAME = "fou"
)

const (
FOU_CMD_UNSPEC uint8 = iota
FOU_CMD_ADD
FOU_CMD_DEL
FOU_CMD_GET
FOU_CMD_MAX = FOU_CMD_GET
)

const (
FOU_ATTR_UNSPEC = iota
FOU_ATTR_PORT
FOU_ATTR_AF
FOU_ATTR_IPPROTO
FOU_ATTR_TYPE
FOU_ATTR_REMCSUM_NOPARTIAL
FOU_ATTR_MAX = FOU_ATTR_REMCSUM_NOPARTIAL
)

const (
FOU_ENCAP_UNSPEC = iota
FOU_ENCAP_DIRECT
FOU_ENCAP_GUE
FOU_ENCAP_MAX = FOU_ENCAP_GUE
)

var fouFamilyId int

func FouFamilyId() (int, error) {
if fouFamilyId != 0 {
return fouFamilyId, nil
}

fam, err := GenlFamilyGet(FOU_GENL_NAME)
if err != nil {
return -1, err
}

fouFamilyId = int(fam.ID)
return fouFamilyId, nil
}

func FouAdd(f Fou) error {
return pkgHandle.FouAdd(f)
}

func (h *Handle) FouAdd(f Fou) error {
fam_id, err := FouFamilyId()
if err != nil {
return err
}

// setting ip protocol conflicts with encapsulation type GUE
if f.EncapType == FOU_ENCAP_GUE && f.Protocol != 0 {
return errors.New("GUE encapsulation doesn't specify an IP protocol")
}

req := h.newNetlinkRequest(fam_id, unix.NLM_F_ACK)

// int to byte for port
bp := make([]byte, 2)
binary.BigEndian.PutUint16(bp[0:2], uint16(f.Port))

attrs := []*nl.RtAttr{
nl.NewRtAttr(FOU_ATTR_PORT, bp),
nl.NewRtAttr(FOU_ATTR_TYPE, []byte{uint8(f.EncapType)}),
nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(f.Family)}),
nl.NewRtAttr(FOU_ATTR_IPPROTO, []byte{uint8(f.Protocol)}),
}
raw := []byte{FOU_CMD_ADD, 1, 0, 0}
for _, a := range attrs {
raw = append(raw, a.Serialize()...)
}

req.AddRawData(raw)

_, err = req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return err
}

return nil
}

func FouDel(f Fou) error {
return pkgHandle.FouDel(f)
}

func (h *Handle) FouDel(f Fou) error {
fam_id, err := FouFamilyId()
if err != nil {
return err
}

req := h.newNetlinkRequest(fam_id, unix.NLM_F_ACK)

// int to byte for port
bp := make([]byte, 2)
binary.BigEndian.PutUint16(bp[0:2], uint16(f.Port))

attrs := []*nl.RtAttr{
nl.NewRtAttr(FOU_ATTR_PORT, bp),
nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(f.Family)}),
}
raw := []byte{FOU_CMD_DEL, 1, 0, 0}
for _, a := range attrs {
raw = append(raw, a.Serialize()...)
}

req.AddRawData(raw)

_, err = req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return err
}

return nil
}

func FouList(fam int) ([]Fou, error) {
return pkgHandle.FouList(fam)
}

func (h *Handle) FouList(fam int) ([]Fou, error) {
fam_id, err := FouFamilyId()
if err != nil {
return nil, err
}

req := h.newNetlinkRequest(fam_id, unix.NLM_F_DUMP)

attrs := []*nl.RtAttr{
nl.NewRtAttr(FOU_ATTR_AF, []byte{uint8(fam)}),
}
raw := []byte{FOU_CMD_GET, 1, 0, 0}
for _, a := range attrs {
raw = append(raw, a.Serialize()...)
}

req.AddRawData(raw)

msgs, err := req.Execute(unix.NETLINK_GENERIC, 0)
if err != nil {
return nil, err
}

fous := make([]Fou, 0, len(msgs))
for _, m := range msgs {
f, err := deserializeFouMsg(m)
if err != nil {
return fous, err
}

fous = append(fous, f)
}

return fous, nil
}
107 changes: 107 additions & 0 deletions fou_test.go
@@ -0,0 +1,107 @@
package netlink

import (
"testing"
)

func TestFouDeserializeMsg(t *testing.T) {
var msg []byte

// deserialize a valid message
msg = []byte{3, 1, 0, 0, 5, 0, 2, 0, 2, 0, 0, 0, 6, 0, 1, 0, 21, 179, 0, 0, 5, 0, 3, 0, 4, 0, 0, 0, 5, 0, 4, 0, 1, 0, 0, 0}
if fou, err := deserializeFouMsg(msg); err != nil {
t.Error(err.Error())
} else {

// check if message was deserialized correctly
if fou.Family != FAMILY_V4 {
t.Errorf("expected family %d, got %d", FAMILY_V4, fou.Family)
}

if fou.Port != 5555 {
t.Errorf("expected port 5555, got %d", fou.Port)
}

if fou.Protocol != 4 { // ipip
t.Errorf("expected protocol 4, got %d", fou.Protocol)
}

if fou.EncapType != FOU_ENCAP_DIRECT {
t.Errorf("expected encap type %d, got %d", FOU_ENCAP_DIRECT, fou.EncapType)
}
}

// deserialize truncated attribute header
msg = []byte{3, 1, 0, 0, 5, 0}
if _, err := deserializeFouMsg(msg); err == nil {
t.Error("expected attribute header truncated error")
} else if err != ErrAttrHeaderTruncated {
t.Errorf("unexpected error: %s", err.Error())
}

// deserialize truncated attribute header
msg = []byte{3, 1, 0, 0, 5, 0, 2, 0, 2, 0, 0}
if _, err := deserializeFouMsg(msg); err == nil {
t.Error("expected attribute body truncated error")
} else if err != ErrAttrBodyTruncated {
t.Errorf("unexpected error: %s", err.Error())
}
}

func TestFouAddDel(t *testing.T) {
// foo-over-udp was merged in 3.18 so skip these tests if the kernel is too old
minKernelRequired(t, 3, 18)

// the fou module is usually not compiled in the kernel so we'll load it
tearDown := setUpNetlinkTestWithKModule(t, "fou")
defer tearDown()

fou := Fou{
Port: 5555,
Family: FAMILY_V4,
Protocol: 4, // ipip
EncapType: FOU_ENCAP_DIRECT,
}

if err := FouAdd(fou); err != nil {
t.Fatal(err)
}

list, err := FouList(FAMILY_V4)
if err != nil {
t.Fatal(err)
}

if len(list) != 1 {
t.Fatalf("expected 1 fou, got %d", len(list))
}

if list[0].Port != fou.Port {
t.Errorf("expected port %d, got %d", fou.Port, list[0].Port)
}

if list[0].Family != fou.Family {
t.Errorf("expected family %d, got %d", fou.Family, list[0].Family)
}

if list[0].Protocol != fou.Protocol {
t.Errorf("expected protocol %d, got %d", fou.Protocol, list[0].Protocol)
}

if list[0].EncapType != fou.EncapType {
t.Errorf("expected encaptype %d, got %d", fou.EncapType, list[0].EncapType)
}

if err := FouDel(Fou{Port: fou.Port, Family: fou.Family}); err != nil {
t.Fatal(err)
}

list, err = FouList(FAMILY_V4)
if err != nil {
t.Fatal(err)
}

if len(list) != 0 {
t.Fatalf("expected 0 fou, got %d", len(list))
}
}
15 changes: 15 additions & 0 deletions fou_unspecified.go
@@ -0,0 +1,15 @@
// +build !linux

package netlink

func FouAdd(f Fou) error {
return ErrNotImplemented
}

func FouDel(f Fou) error {
return ErrNotImplemented
}

func FouList(fam int) ([]Fou, error) {
return nil, ErrNotImplemented
}

0 comments on commit bdf753e

Please sign in to comment.