diff --git a/link.go b/link.go index 04d4b2f..e28f1bd 100644 --- a/link.go +++ b/link.go @@ -851,6 +851,30 @@ func (b *BondSlave) SlaveType() string { return "bond" } +// Geneve devices must specify RemoteIP and ID (VNI) on create +// https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/drivers/net/geneve.c#L1209-L1223 +type Geneve struct { + LinkAttrs + ID uint32 // vni + Remote net.IP + Ttl uint8 + Tos uint8 + Dport uint16 + UdpCsum uint8 + UdpZeroCsum6Tx uint8 + UdpZeroCsum6Rx uint8 + Link uint32 + FlowBased bool +} + +func (geneve *Geneve) Attrs() *LinkAttrs { + return &geneve.LinkAttrs +} + +func (geneve *Geneve) Type() string { + return "geneve" +} + // Gretap devices must specify LocalIP and RemoteIP on create type Gretap struct { LinkAttrs diff --git a/link_linux.go b/link_linux.go index b911412..8506821 100644 --- a/link_linux.go +++ b/link_linux.go @@ -1404,6 +1404,8 @@ func (h *Handle) linkModify(link Link, flags int) error { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) data.AddRtAttr(nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[link.Mode])) } + case *Geneve: + addGeneveAttrs(link, linkInfo) case *Gretap: addGretapAttrs(link, linkInfo) case *Iptun: @@ -1667,6 +1669,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { link = &Macvlan{} case "macvtap": link = &Macvtap{} + case "geneve": + link = &Geneve{} case "gretap": link = &Gretap{} case "ip6gretap": @@ -1716,6 +1720,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) { parseMacvlanData(link, data) case "macvtap": parseMacvtapData(link, data) + case "geneve": + parseGeneveData(link, data) case "gretap": parseGretapData(link, data) case "ip6gretap": @@ -2452,6 +2458,56 @@ func linkFlags(rawFlags uint32) net.Flags { return f } +func addGeneveAttrs(geneve *Geneve, linkInfo *nl.RtAttr) { + data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) + + if geneve.FlowBased { + // In flow based mode, no other attributes need to be configured + linkInfo.AddRtAttr(nl.IFLA_GENEVE_COLLECT_METADATA, boolAttr(geneve.FlowBased)) + return + } + + if ip := geneve.Remote; ip != nil { + if ip4 := ip.To4(); ip4 != nil { + data.AddRtAttr(nl.IFLA_GENEVE_REMOTE, []byte(ip)) + } else { + data.AddRtAttr(nl.IFLA_GENEVE_REMOTE6, []byte(ip)) + } + } + + if geneve.ID != 0 { + data.AddRtAttr(nl.IFLA_GENEVE_ID, htonl(geneve.ID)) + } + + if geneve.Dport != 0 { + data.AddRtAttr(nl.IFLA_GENEVE_PORT, htons(geneve.Dport)) + } + + if geneve.Ttl != 0 { + data.AddRtAttr(nl.IFLA_GENEVE_TTL, nl.Uint8Attr(geneve.Ttl)) + } + + if geneve.Tos != 0 { + data.AddRtAttr(nl.IFLA_GENEVE_TOS, nl.Uint8Attr(geneve.Tos)) + } +} + +func parseGeneveData(link Link, data []syscall.NetlinkRouteAttr) { + geneve := link.(*Geneve) + for _, datum := range data { + switch datum.Attr.Type { + case nl.IFLA_GENEVE_ID: + geneve.ID = ntohl(datum.Value[0:4]) + case nl.IFLA_GENEVE_PORT: + geneve.Dport = ntohs(datum.Value[0:2]) + case nl.IFLA_GENEVE_TTL: + geneve.Ttl = uint8(datum.Value[0]) + case nl.IFLA_GENEVE_TOS: + geneve.Tos = uint8(datum.Value[0]) + } + } +} + func addGretapAttrs(gretap *Gretap, linkInfo *nl.RtAttr) { data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) diff --git a/link_test.go b/link_test.go index 43d83ed..ae1c9c0 100644 --- a/link_test.go +++ b/link_test.go @@ -245,6 +245,14 @@ func testLinkAddDel(t *testing.T, link Link) { } } + if geneve, ok := link.(*Geneve); ok { + other, ok := result.(*Geneve) + if !ok { + t.Fatal("Result of create is not a Geneve") + } + compareGeneve(t, geneve, other) + } + if gretap, ok := link.(*Gretap); ok { other, ok := result.(*Gretap) if !ok { @@ -293,6 +301,31 @@ func testLinkAddDel(t *testing.T, link Link) { } } +func compareGeneve(t *testing.T, expected, actual *Geneve) { + if actual.ID != expected.ID { + t.Fatalf("Geneve.ID doesn't match: %d %d", actual.ID, expected.ID) + } + + // set the Dport to 6081 (the linux default) if it wasn't specified at creation + if expected.Dport == 0 { + expected.Dport = 6081 + } + + if actual.Dport != expected.Dport { + t.Fatal("Geneve.Dport doesn't match") + } + + if actual.Ttl != expected.Ttl { + t.Fatal("Geneve.Ttl doesn't match") + } + + if actual.Tos != expected.Tos { + t.Fatal("Geneve.Tos doesn't match") + } + + // TODO: we should implement the rest of the geneve methods +} + func compareGretap(t *testing.T, expected, actual *Gretap) { if actual.IKey != expected.IKey { t.Fatal("Gretap.IKey doesn't match") @@ -575,6 +608,21 @@ func TestLinkAddDelBridge(t *testing.T) { testLinkAddDel(t, &Bridge{LinkAttrs: LinkAttrs{Name: "foo", MTU: 1400}}) } +func TestLinkAddDelGeneve(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + testLinkAddDel(t, &Geneve{ + LinkAttrs: LinkAttrs{Name: "foo4", EncapType: "geneve"}, + ID: 0x1000, + Remote: net.IPv4(127, 0, 0, 1)}) + + testLinkAddDel(t, &Geneve{ + LinkAttrs: LinkAttrs{Name: "foo6", EncapType: "geneve"}, + ID: 0x1000, + Remote: net.ParseIP("2001:db8:ef33::2")}) +} + func TestLinkAddDelGretap(t *testing.T) { tearDown := setUpNetlinkTest(t) defer tearDown() diff --git a/nl/link_linux.go b/nl/link_linux.go index 51e67ff..c72cc43 100644 --- a/nl/link_linux.go +++ b/nl/link_linux.go @@ -173,6 +173,22 @@ const ( IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE ) +const ( + IFLA_GENEVE_UNSPEC = iota + IFLA_GENEVE_ID // vni + IFLA_GENEVE_REMOTE + IFLA_GENEVE_TTL + IFLA_GENEVE_TOS + IFLA_GENEVE_PORT // destination port + IFLA_GENEVE_COLLECT_METADATA + IFLA_GENEVE_REMOTE6 + IFLA_GENEVE_UDP_CSUM + IFLA_GENEVE_UDP_ZERO_CSUM6_TX + IFLA_GENEVE_UDP_ZERO_CSUM6_RX + IFLA_GENEVE_LABEL + IFLA_GENEVE_MAX = IFLA_GENEVE_LABEL +) + const ( IFLA_GRE_UNSPEC = iota IFLA_GRE_LINK