rtnetlink/driver/vxlan.go
Jeroen Simonetti 5c41262525
Implement additional drivers (#280)
Add drivers for bridge, macvlan, vlan, vxlan and add helpers to
LinkService to use them (SetMaster, RemoveMaster)

Signed-off-by: Jeroen Simonetti <jeroen@simonetti.nl>
2025-10-30 22:55:44 +01:00

434 lines
9.2 KiB
Go

package driver
import (
"encoding/binary"
"fmt"
"net"
"github.com/jsimonetti/rtnetlink/v2"
"github.com/jsimonetti/rtnetlink/v2/internal/unix"
"github.com/mdlayher/netlink"
)
// VxlanDFMode specifies how to handle DF flag in outer IPv4 header
type VxlanDFMode uint8
const (
// VxlanDFUnset indicates DF flag is not set (default)
VxlanDFUnset VxlanDFMode = iota
// VxlanDFSet indicates DF flag is set
VxlanDFSet
// VxlanDFInherit indicates DF flag is inherited from inner IPv4 header
VxlanDFInherit
)
func (v VxlanDFMode) String() string {
switch v {
case VxlanDFUnset:
return "unset"
case VxlanDFSet:
return "set"
case VxlanDFInherit:
return "inherit"
default:
return fmt.Sprintf("unknown VxlanDFMode value (%d)", v)
}
}
// VxlanPortRange specifies the range of source UDP ports
type VxlanPortRange struct {
Low uint16
High uint16
}
// Vxlan implements LinkDriver for the vxlan driver
type Vxlan struct {
// VXLAN Network Identifier (or VXLAN Segment ID) - required
ID *uint32
// Multicast group IP address to join (IPv4)
Group net.IP
// Multicast group IP address to join (IPv6)
Group6 net.IP
// Physical device to use for tunnel endpoint communication
Link *uint32
// Source IP address to use in outgoing packets (IPv4)
Local net.IP
// Source IP address to use in outgoing packets (IPv6)
Local6 net.IP
// TTL to use in outgoing packets
TTL *uint8
// TOS to use in outgoing packets
TOS *uint8
// Enable learning of source link addresses
Learning *bool
// Lifetime in seconds of FDB entries learnt by the kernel
Ageing *uint32
// Maximum number of FDB entries
Limit *uint32
// Range of source UDP ports to use
PortRange *VxlanPortRange
// Enable ARP proxy
Proxy *bool
// Enable route short circuit
RSC *bool
// Enable netlink LLADDR miss notifications
L2Miss *bool
// Enable netlink IP address miss notifications
L3Miss *bool
// Destination port for VXLAN traffic (default 4789)
Port *uint16
// Enable UDP checksums on transmit for outer IPv4
UDPCsum *bool
// Enable zero UDP checksums on transmit for outer IPv6
UDPZeroCsum6Tx *bool
// Allow zero UDP checksums on receive for outer IPv6
UDPZeroCsum6Rx *bool
// Enable remote checksum offload on transmit
RemCsumTx *bool
// Enable remote checksum offload on receive
RemCsumRx *bool
// Enable VXLAN Group Policy extension
GBP *bool
// Enable remote checksum offload without partial checksums
RemCsumNoPartial *bool
// Enable metadata collection mode
CollectMetadata *bool
// MPLS label to use for VXLAN encapsulation
Label *uint32
// Enable Generic Protocol Extension (VXLAN-GPE)
GPE *bool
// Inherit TTL from inner packet
TTLInherit *bool
// Specifies how to handle DF flag in outer IPv4 header
DF *VxlanDFMode
// Enable VXLAN VNI filtering
VNIFilter *bool
}
var _ rtnetlink.LinkDriver = &Vxlan{}
func (v *Vxlan) New() rtnetlink.LinkDriver {
return &Vxlan{}
}
func (v *Vxlan) Encode(ae *netlink.AttributeEncoder) error {
if v.ID != nil {
ae.Uint32(unix.IFLA_VXLAN_ID, *v.ID)
}
if v.Group != nil {
ip := v.Group.To4()
if ip == nil {
return fmt.Errorf("group must be an IPv4 address")
}
ae.Bytes(unix.IFLA_VXLAN_GROUP, ip)
}
if v.Group6 != nil {
// Check if it's actually an IPv6 address (not an IPv4 address)
if v.Group6.To4() != nil {
return fmt.Errorf("group6 must be an IPv6 address")
}
ip := v.Group6.To16()
if ip == nil {
return fmt.Errorf("group6 must be an IPv6 address")
}
ae.Bytes(unix.IFLA_VXLAN_GROUP6, ip)
}
if v.Link != nil {
ae.Uint32(unix.IFLA_VXLAN_LINK, *v.Link)
}
if v.Local != nil {
ip := v.Local.To4()
if ip == nil {
return fmt.Errorf("local must be an IPv4 address")
}
ae.Bytes(unix.IFLA_VXLAN_LOCAL, ip)
}
if v.Local6 != nil {
// Check if it's actually an IPv6 address (not an IPv4 address)
if v.Local6.To4() != nil {
return fmt.Errorf("local6 must be an IPv6 address")
}
ip := v.Local6.To16()
if ip == nil {
return fmt.Errorf("local6 must be an IPv6 address")
}
ae.Bytes(unix.IFLA_VXLAN_LOCAL6, ip)
}
if v.TTL != nil {
ae.Uint8(unix.IFLA_VXLAN_TTL, *v.TTL)
}
if v.TOS != nil {
ae.Uint8(unix.IFLA_VXLAN_TOS, *v.TOS)
}
if v.Learning != nil {
var val uint8
if *v.Learning {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_LEARNING, val)
}
if v.Ageing != nil {
ae.Uint32(unix.IFLA_VXLAN_AGEING, *v.Ageing)
}
if v.Limit != nil {
ae.Uint32(unix.IFLA_VXLAN_LIMIT, *v.Limit)
}
if v.PortRange != nil {
buf := make([]byte, 4)
binary.BigEndian.PutUint16(buf[0:2], v.PortRange.Low)
binary.BigEndian.PutUint16(buf[2:4], v.PortRange.High)
ae.Bytes(unix.IFLA_VXLAN_PORT_RANGE, buf)
}
if v.Proxy != nil {
var val uint8
if *v.Proxy {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_PROXY, val)
}
if v.RSC != nil {
var val uint8
if *v.RSC {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_RSC, val)
}
if v.L2Miss != nil {
var val uint8
if *v.L2Miss {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_L2MISS, val)
}
if v.L3Miss != nil {
var val uint8
if *v.L3Miss {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_L3MISS, val)
}
if v.Port != nil {
// Port must be in network byte order (big-endian)
buf := make([]byte, 2)
binary.BigEndian.PutUint16(buf, *v.Port)
ae.Bytes(unix.IFLA_VXLAN_PORT, buf)
}
if v.UDPCsum != nil {
var val uint8
if *v.UDPCsum {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_UDP_CSUM, val)
}
if v.UDPZeroCsum6Tx != nil {
var val uint8
if *v.UDPZeroCsum6Tx {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_UDP_ZERO_CSUM6_TX, val)
}
if v.UDPZeroCsum6Rx != nil {
var val uint8
if *v.UDPZeroCsum6Rx {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_UDP_ZERO_CSUM6_RX, val)
}
if v.RemCsumTx != nil {
var val uint8
if *v.RemCsumTx {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_REMCSUM_TX, val)
}
if v.RemCsumRx != nil {
var val uint8
if *v.RemCsumRx {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_REMCSUM_RX, val)
}
if v.GBP != nil {
if *v.GBP {
ae.Uint32(unix.IFLA_VXLAN_GBP, 0)
}
}
if v.RemCsumNoPartial != nil {
if *v.RemCsumNoPartial {
ae.Uint32(unix.IFLA_VXLAN_REMCSUM_NOPARTIAL, 0)
}
}
if v.CollectMetadata != nil {
var val uint8
if *v.CollectMetadata {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_COLLECT_METADATA, val)
}
if v.Label != nil {
ae.Uint32(unix.IFLA_VXLAN_LABEL, *v.Label)
}
if v.GPE != nil {
if *v.GPE {
ae.Uint32(unix.IFLA_VXLAN_GPE, 0)
}
}
if v.TTLInherit != nil {
var val uint8
if *v.TTLInherit {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_TTL_INHERIT, val)
}
if v.DF != nil {
ae.Uint8(unix.IFLA_VXLAN_DF, uint8(*v.DF))
}
if v.VNIFilter != nil {
var val uint8
if *v.VNIFilter {
val = 1
}
ae.Uint8(unix.IFLA_VXLAN_VNIFILTER, val)
}
return nil
}
func (v *Vxlan) Decode(ad *netlink.AttributeDecoder) error {
for ad.Next() {
switch ad.Type() {
case unix.IFLA_VXLAN_ID:
val := ad.Uint32()
v.ID = &val
case unix.IFLA_VXLAN_GROUP:
v.Group = net.IP(ad.Bytes())
case unix.IFLA_VXLAN_GROUP6:
v.Group6 = net.IP(ad.Bytes())
case unix.IFLA_VXLAN_LINK:
val := ad.Uint32()
v.Link = &val
case unix.IFLA_VXLAN_LOCAL:
v.Local = net.IP(ad.Bytes())
case unix.IFLA_VXLAN_LOCAL6:
v.Local6 = net.IP(ad.Bytes())
case unix.IFLA_VXLAN_TTL:
val := ad.Uint8()
v.TTL = &val
case unix.IFLA_VXLAN_TOS:
val := ad.Uint8()
v.TOS = &val
case unix.IFLA_VXLAN_LEARNING:
val := ad.Uint8() != 0
v.Learning = &val
case unix.IFLA_VXLAN_AGEING:
val := ad.Uint32()
v.Ageing = &val
case unix.IFLA_VXLAN_LIMIT:
val := ad.Uint32()
v.Limit = &val
case unix.IFLA_VXLAN_PORT_RANGE:
buf := ad.Bytes()
if len(buf) >= 4 {
v.PortRange = &VxlanPortRange{
Low: binary.BigEndian.Uint16(buf[0:2]),
High: binary.BigEndian.Uint16(buf[2:4]),
}
}
case unix.IFLA_VXLAN_PROXY:
val := ad.Uint8() != 0
v.Proxy = &val
case unix.IFLA_VXLAN_RSC:
val := ad.Uint8() != 0
v.RSC = &val
case unix.IFLA_VXLAN_L2MISS:
val := ad.Uint8() != 0
v.L2Miss = &val
case unix.IFLA_VXLAN_L3MISS:
val := ad.Uint8() != 0
v.L3Miss = &val
case unix.IFLA_VXLAN_PORT:
// Port is in network byte order (big-endian)
buf := ad.Bytes()
if len(buf) >= 2 {
val := binary.BigEndian.Uint16(buf)
v.Port = &val
}
case unix.IFLA_VXLAN_UDP_CSUM:
val := ad.Uint8() != 0
v.UDPCsum = &val
case unix.IFLA_VXLAN_UDP_ZERO_CSUM6_TX:
val := ad.Uint8() != 0
v.UDPZeroCsum6Tx = &val
case unix.IFLA_VXLAN_UDP_ZERO_CSUM6_RX:
val := ad.Uint8() != 0
v.UDPZeroCsum6Rx = &val
case unix.IFLA_VXLAN_REMCSUM_TX:
val := ad.Uint8() != 0
v.RemCsumTx = &val
case unix.IFLA_VXLAN_REMCSUM_RX:
val := ad.Uint8() != 0
v.RemCsumRx = &val
case unix.IFLA_VXLAN_GBP:
val := true
v.GBP = &val
case unix.IFLA_VXLAN_REMCSUM_NOPARTIAL:
val := true
v.RemCsumNoPartial = &val
case unix.IFLA_VXLAN_COLLECT_METADATA:
val := ad.Uint8() != 0
v.CollectMetadata = &val
case unix.IFLA_VXLAN_LABEL:
val := ad.Uint32()
v.Label = &val
case unix.IFLA_VXLAN_GPE:
val := true
v.GPE = &val
case unix.IFLA_VXLAN_TTL_INHERIT:
val := ad.Uint8() != 0
v.TTLInherit = &val
case unix.IFLA_VXLAN_DF:
val := VxlanDFMode(ad.Uint8())
v.DF = &val
case unix.IFLA_VXLAN_VNIFILTER:
val := ad.Uint8() != 0
v.VNIFilter = &val
}
}
return nil
}
func (*Vxlan) Kind() string {
return "vxlan"
}