Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions nexthop.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,38 @@ import (
"strings"
)

const (
// NEXTHOP_GRP_TYPE_MPATH is default multi-path hash threshold
NEXTHOP_GRP_TYPE_MPATH uint16 = iota

// NEXTHOP_GRP_TYPE_RES is Resilient nexthop group
NEXTHOP_GRP_TYPE_RES
)

// NexthopGroupMpath represents one member of a nexthtop group
type NexthopGroupMpath struct {
// ID of an existing nexthop to include in the group
ID uint16
// Relative weight, 1-256. Zero is treated as 1
Weight uint16
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

type NexthopResGroup struct {
Buckets uint16
IdleTimer uint32
UnbalancedTimer uint32
UnbalancedTime uint64
}

type Nexthop struct {
ID uint32
Blackhole bool
OIF uint32
Gateway net.IP
Protocol RouteProtocol
Group []NexthopGroupMpath
GroupType uint16
ResGroup *NexthopResGroup
}

func (h *Nexthop) String() string {
Expand Down
127 changes: 126 additions & 1 deletion nexthop_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
"golang.org/x/sys/unix"
)

const (
// sizeofNexthopGrp is the size of struct nexthop_grp from
// Linux uapi/linux/nexthop.h: u32 id, u8 weight, u8 resvd1, u16 resvd2.
sizeofNexthopGrp = 8

// nexthopResGroupUserHZ converts resilient group timer values between
// seconds (used in NexthopResGroup) and the clock_t ticks expected on
// the wire. USER_HZ is fixed at 100 in the Linux userspace ABI.
nexthopResGroupUserHZ = 100
)

// NexthopAdd will add a nexthop to the system.
// Equivalent to: `ip nexthop add $nexthop`
func NexthopAdd(nh *Nexthop) error {
Expand Down Expand Up @@ -158,6 +169,107 @@
}
nh.OIF = native.Uint32(attr.Data[0:4])
},
},
unix.NHA_GROUP: {
encode: func(nh *Nexthop) *nl.RtAttr {
if len(nh.Group) == 0 {
return nil
}
b := make([]byte, sizeofNexthopGrp*len(nh.Group))
for i, entry := range nh.Group {
// The kernel interprets the on-wire weight as
// "actual weight - 1", valid range 1-256.
w := entry.Weight
if w == 0 {
w = 1
} else if w > 256 {
w = 256
}
off := i * sizeofNexthopGrp
native.PutUint32(b[off:off+4], entry.ID)

Check failure on line 189 in nexthop_linux.go

View workflow job for this annotation

GitHub Actions / build

cannot use entry.ID (variable of type uint16) as uint32 value in argument to native.PutUint32
b[off+4] = uint8(w - 1)
}
return nl.NewRtAttr(unix.NHA_GROUP, b)
},
decode: func(nh *Nexthop, attr *nl.RtAttr) {
nh.Group = nil
for off := 0; off+sizeofNexthopGrp <= len(attr.Data); off += sizeofNexthopGrp {
nh.Group = append(nh.Group, NexthopGroupMpath{
ID: native.Uint32(attr.Data[off : off+4]),

Check failure on line 198 in nexthop_linux.go

View workflow job for this annotation

GitHub Actions / build

cannot use native.Uint32(attr.Data[off:off + 4]) (value of type uint32) as uint16 value in struct literal
Weight: uint16(attr.Data[off+4]) + 1,
})
}
},
},
unix.NHA_GROUP_TYPE: {
encode: func(nh *Nexthop) *nl.RtAttr {
if nh.GroupType != NEXTHOP_GRP_TYPE_MPATH {
b := make([]byte, 2)
native.PutUint16(b, nh.GroupType)
return nl.NewRtAttr(unix.NHA_GROUP_TYPE, b)
}
return nil
},
decode: func(nh *Nexthop, attr *nl.RtAttr) {
if len(attr.Data) < 2 {
return
}
nh.GroupType = native.Uint16(attr.Data[0:2])
},
},
nl.NHA_RES_GROUP: {
encode: func(nh *Nexthop) *nl.RtAttr {
if nh.ResGroup == nil {
return nil
}
// Strict netlink validation requires the NLA_F_NESTED
// flag on nested attributes.
attr := nl.NewRtAttr(nl.NHA_RES_GROUP|int(nl.NLA_F_NESTED), nil)
if nh.ResGroup.Buckets > 0 {
b := make([]byte, 2)
native.PutUint16(b, nh.ResGroup.Buckets)
attr.AddRtAttr(nl.NHA_RES_GROUP_BUCKETS, b)
}
if nh.ResGroup.IdleTimer > 0 {
b := make([]byte, 4)
native.PutUint32(b, nh.ResGroup.IdleTimer*nexthopResGroupUserHZ)
attr.AddRtAttr(nl.NHA_RES_GROUP_IDLE_TIMER, b)
}
if nh.ResGroup.UnbalancedTimer > 0 {
b := make([]byte, 4)
native.PutUint32(b, nh.ResGroup.UnbalancedTimer*nexthopResGroupUserHZ)
attr.AddRtAttr(nl.NHA_RES_GROUP_UNBALANCED_TIMER, b)
}
return attr
},
decode: func(nh *Nexthop, attr *nl.RtAttr) {
nested, err := nl.ParseRouteAttr(attr.Data)
if err != nil {
return
}
res := &NexthopResGroup{}
for _, a := range nested {
switch a.Attr.Type & nl.NLA_TYPE_MASK {
case nl.NHA_RES_GROUP_BUCKETS:
if len(a.Value) >= 2 {
res.Buckets = native.Uint16(a.Value[0:2])
}
case nl.NHA_RES_GROUP_IDLE_TIMER:
if len(a.Value) >= 4 {
res.IdleTimer = native.Uint32(a.Value[0:4]) / nexthopResGroupUserHZ
}
case nl.NHA_RES_GROUP_UNBALANCED_TIMER:
if len(a.Value) >= 4 {
res.UnbalancedTimer = native.Uint32(a.Value[0:4]) / nexthopResGroupUserHZ
}
case nl.NHA_RES_GROUP_UNBALANCED_TIME:
if len(a.Value) >= 8 {
res.UnbalancedTime = native.Uint64(a.Value[0:8]) / nexthopResGroupUserHZ
}
}
}
nh.ResGroup = res
},
},
unix.NHA_GATEWAY: {
encode: func(nh *Nexthop) *nl.RtAttr {
Expand Down Expand Up @@ -220,7 +332,7 @@

rtAttrs := make([]*nl.RtAttr, 0, len(rawAttrs))
for _, rawAttr := range rawAttrs {
rtAttrs = append(rtAttrs, nl.NewRtAttr(int(rawAttr.Attr.Type), rawAttr.Value))
rtAttrs = append(rtAttrs, nl.NewRtAttr(int(rawAttr.Attr.Type&nl.NLA_TYPE_MASK), rawAttr.Value))
}

nh := &Nexthop{
Expand All @@ -233,13 +345,23 @@
}

func deriveFamilyFromNexthop(nh *Nexthop) uint8 {
if len(nh.Group) > 0 {
return uint8(FAMILY_ALL)
}
if nh.Gateway == nil || nh.Gateway.To4() != nil {
return FAMILY_V4
}
return FAMILY_V6
}

func prepareNewNexthop(nh *Nexthop, req *nl.NetlinkRequest, msg *nl.Nhmsg) error {
if nh.ResGroup != nil && nh.GroupType != NEXTHOP_GRP_TYPE_RES {
return fmt.Errorf("nexthop: ResGroup requires GroupType to be NEXTHOP_GRP_TYPE_RES")

Check failure on line 359 in nexthop_linux.go

View workflow job for this annotation

GitHub Actions / build

undefined: fmt
}
if nh.GroupType != NEXTHOP_GRP_TYPE_MPATH && len(nh.Group) == 0 {
return fmt.Errorf("nexthop: GroupType is set but Group is empty")

Check failure on line 362 in nexthop_linux.go

View workflow job for this annotation

GitHub Actions / build

undefined: fmt
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
var rtAttrs []*nl.RtAttr

// We can find the supported attributes from the kernel source code:
Expand All @@ -255,6 +377,9 @@
unix.NHA_BLACKHOLE,
unix.NHA_OIF,
unix.NHA_GATEWAY,
unix.NHA_GROUP,
unix.NHA_GROUP_TYPE,
nl.NHA_RES_GROUP,
})...)

msg.Family = deriveFamilyFromNexthop(nh)
Expand Down
15 changes: 15 additions & 0 deletions nl/nl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ const (
SizeofCnMsgOp = 0x18
)

const (
NHA_RES_GROUP = 12 // Nest containing attributes specific to resilient groups
NHA_RES_BUCKET = 13 // Nest containing attributes specific to buckets
)

const (
NHA_RES_GROUP_UNSPEC = iota
NHA_RES_GROUP_BUCKETS // u16: Number of buckets in the hash table
NHA_RES_GROUP_IDLE_TIMER // u32: Idle timer in units of clock_t
NHA_RES_GROUP_UNBALANCED_TIMER // u32: Unbalanced timer in units of clock_t
NHA_RES_GROUP_UNBALANCED_TIME // u64: Time out of balance (read-only for dumps)
)

const NHA_RES_GROUP_PAD = NHA_RES_GROUP_UNSPEC

// SupportedNlFamilies contains the list of netlink families this netlink package supports
var SupportedNlFamilies = []int{unix.NETLINK_ROUTE, unix.NETLINK_XFRM, unix.NETLINK_NETFILTER}

Expand Down
Loading