chore(netlink): define own types with minimal fields

- Allow to swap `github.com/vishvananda/netlink`
- Allow to add build tags for each platform
- One step closer to development on non-Linux platforms
This commit is contained in:
Quentin McGaw
2023-05-29 06:44:58 +00:00
parent 163ac48ce4
commit 38ddcfa756
34 changed files with 828 additions and 493 deletions

View File

@@ -1,14 +1,40 @@
package netlink
import "github.com/vishvananda/netlink"
import (
"net/netip"
type Addr = netlink.Addr
"github.com/vishvananda/netlink"
)
type Addr struct {
Network netip.Prefix
}
func (a Addr) String() string {
return a.Network.String()
}
func (n *NetLink) AddrList(link Link, family int) (
addresses []Addr, err error) {
return netlink.AddrList(link, family)
netlinkLink := linkToNetlinkLink(&link)
netlinkAddresses, err := netlink.AddrList(netlinkLink, family)
if err != nil {
return nil, err
}
addresses = make([]Addr, len(netlinkAddresses))
for i := range netlinkAddresses {
addresses[i].Network = netIPNetToNetipPrefix(netlinkAddresses[i].IPNet)
}
return addresses, nil
}
func (n *NetLink) AddrReplace(link Link, addr *Addr) error {
return netlink.AddrReplace(link, addr)
func (n *NetLink) AddrReplace(link Link, addr Addr) error {
netlinkLink := linkToNetlinkLink(&link)
netlinkAddress := netlink.Addr{
IPNet: netipPrefixToIPNet(addr.Network),
}
return netlink.AddrReplace(netlinkLink, &netlinkAddress)
}

View File

@@ -0,0 +1,62 @@
package netlink
import (
"fmt"
"net"
"net/netip"
)
func netipPrefixToIPNet(prefix netip.Prefix) (ipNet *net.IPNet) {
if !prefix.IsValid() {
return nil
}
prefixAddr := prefix.Addr().Unmap()
ipMask := net.CIDRMask(prefix.Bits(), prefixAddr.BitLen())
ip := netipAddrToNetIP(prefixAddr)
return &net.IPNet{
IP: ip,
Mask: ipMask,
}
}
func netIPNetToNetipPrefix(ipNet *net.IPNet) (prefix netip.Prefix) {
if ipNet == nil || (len(ipNet.IP) != net.IPv4len && len(ipNet.IP) != net.IPv6len) {
return prefix
}
var ip netip.Addr
if ipv4 := ipNet.IP.To4(); ipv4 != nil {
ip = netip.AddrFrom4([4]byte(ipv4))
} else {
ip = netip.AddrFrom16([16]byte(ipNet.IP))
}
bits, _ := ipNet.Mask.Size()
return netip.PrefixFrom(ip, bits)
}
func netipAddrToNetIP(address netip.Addr) (ip net.IP) {
switch {
case !address.IsValid():
return nil
case address.Is4() || address.Is4In6():
bytes := address.As4()
return net.IP(bytes[:])
default:
bytes := address.As16()
return net.IP(bytes[:])
}
}
func netIPToNetipAddress(ip net.IP) (address netip.Addr) {
if len(ip) != net.IPv4len && len(ip) != net.IPv6len {
return address // invalid
}
address, ok := netip.AddrFromSlice(ip)
if !ok {
panic(fmt.Sprintf("converting %#v to netip.Addr failed", ip))
}
return address.Unmap()
}

View File

@@ -0,0 +1,146 @@
package netlink
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_netipPrefixToIPNet(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
prefix netip.Prefix
ipNet *net.IPNet
}{
"empty_prefix": {},
"IPv4_prefix": {
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPv4Mask(255, 255, 255, 0),
},
},
"IPv4-in-IPv6_prefix": {
prefix: netip.PrefixFrom(netip.AddrFrom16(
[16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4}),
24),
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPv4Mask(255, 255, 255, 0),
},
},
"IPv6_prefix": {
prefix: netip.PrefixFrom(netip.IPv6Loopback(), 8),
ipNet: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.IPMask{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ipNet := netipPrefixToIPNet(testCase.prefix)
assert.Equal(t, testCase.ipNet, ipNet)
})
}
}
func Test_netIPNetToNetipPrefix(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
ipNet *net.IPNet
prefix netip.Prefix
}{
"empty ipnet": {},
"custom sized IP in ipnet": {
ipNet: &net.IPNet{
IP: net.IP{1},
},
},
"IPv4 ipnet": {
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPMask{255, 255, 255, 0},
},
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
},
"IPv4-in-IPv6 ipnet": {
ipNet: &net.IPNet{
IP: net.IPv4(1, 2, 3, 4),
Mask: net.IPMask{255, 255, 255, 0},
},
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
},
"IPv6 ipnet": {
ipNet: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.IPMask{0xff},
},
prefix: netip.PrefixFrom(netip.IPv6Loopback(), 8),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
prefix := netIPNetToNetipPrefix(testCase.ipNet)
assert.Equal(t, testCase.prefix, prefix)
})
}
}
func Test_netIPToNetipAddress(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
ip net.IP
address netip.Addr
panicMessage string
}{
"nil_ip": {},
"ip_not_ipv4_or_ipv6": {
ip: net.IP{1},
},
"IPv4": {
ip: net.IPv4(1, 2, 3, 4),
address: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
},
"IPv6": {
ip: net.IPv6zero,
address: netip.AddrFrom16([16]byte{}),
},
"IPv4 prefixed with 0xffff": {
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4},
address: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
if testCase.panicMessage != "" {
assert.PanicsWithValue(t, testCase.panicMessage, func() {
netIPToNetipAddress(testCase.ip)
})
return
}
address := netIPToNetipAddress(testCase.ip)
assert.Equal(t, testCase.address, address)
})
}
}

View File

@@ -6,20 +6,19 @@ import (
"github.com/vishvananda/netlink"
)
//nolint:revive
const (
FAMILY_ALL = netlink.FAMILY_ALL
FAMILY_V4 = netlink.FAMILY_V4
FAMILY_V6 = netlink.FAMILY_V6
FamilyAll = 0
FamilyV4 = 2
FamilyV6 = 10
)
func FamilyToString(family int) string {
switch family {
case FAMILY_ALL:
return "all"
case FAMILY_V4:
case FamilyAll:
return "all" //nolint:goconst
case FamilyV4:
return "v4"
case FAMILY_V6:
case FamilyV6:
return "v6"
default:
return fmt.Sprint(family)

View File

@@ -14,20 +14,21 @@ func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
var totalRoutes uint
for _, link := range links {
routes, err := n.RouteList(link, netlink.FAMILY_V6)
link := link
routes, err := n.RouteList(&link, netlink.FAMILY_V6)
if err != nil {
return false, fmt.Errorf("listing IPv6 routes for link %s: %w",
link.Attrs().Name, err)
link.Name, err)
}
// Check each route for IPv6 due to Podman bug listing IPv4 routes
// as IPv6 routes at container start, see:
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
for _, route := range routes {
sourceIsIPv6 := route.Src != nil && route.Src.To4() == nil
destinationIsIPv6 := route.Dst != nil && route.Dst.IP.To4() == nil
sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
if sourceIsIPv6 || destinationIsIPv6 {
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Attrs().Name)
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
return true, nil
}
totalRoutes++

View File

@@ -2,36 +2,117 @@ package netlink
import "github.com/vishvananda/netlink"
type (
Link = netlink.Link
Bridge = netlink.Bridge
Wireguard = netlink.Wireguard
)
type Link struct {
Type string
Name string
Index int
EncapType string
MTU uint16
NetNsID int
TxQLen int
}
func (n *NetLink) LinkList() (links []Link, err error) {
return netlink.LinkList()
netlinkLinks, err := netlink.LinkList()
if err != nil {
return nil, err
}
links = make([]Link, len(netlinkLinks))
for i := range netlinkLinks {
links[i] = netlinkLinkToLink(netlinkLinks[i])
}
return links, nil
}
func (n *NetLink) LinkByName(name string) (link Link, err error) {
return netlink.LinkByName(name)
netlinkLink, err := netlink.LinkByName(name)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
}
func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
return netlink.LinkByIndex(index)
netlinkLink, err := netlink.LinkByIndex(index)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
}
func (n *NetLink) LinkAdd(link Link) (err error) {
return netlink.LinkAdd(link)
func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkAdd(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
}
func (n *NetLink) LinkDel(link Link) (err error) {
return netlink.LinkDel(link)
return netlink.LinkDel(linkToNetlinkLink(&link))
}
func (n *NetLink) LinkSetUp(link Link) (err error) {
return netlink.LinkSetUp(link)
func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkSetUp(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
}
func (n *NetLink) LinkSetDown(link Link) (err error) {
return netlink.LinkSetDown(link)
return netlink.LinkSetDown(linkToNetlinkLink(&link))
}
type netlinkLinkImpl struct {
attrs *netlink.LinkAttrs
linkType string
}
func (n *netlinkLinkImpl) Attrs() *netlink.LinkAttrs {
return n.attrs
}
func (n *netlinkLinkImpl) Type() string {
return n.linkType
}
func netlinkLinkToLink(netlinkLink netlink.Link) Link {
attributes := netlinkLink.Attrs()
return Link{
Type: netlinkLink.Type(),
Name: attributes.Name,
Index: attributes.Index,
EncapType: attributes.EncapType,
MTU: uint16(attributes.MTU),
NetNsID: attributes.NetNsID,
TxQLen: attributes.TxQLen,
}
}
// Warning: we must return `netlink.Link` and not `netlinkLinkImpl`
// so that the vishvananda/netlink package can compare the returned
// value against an untyped nil.
func linkToNetlinkLink(link *Link) netlink.Link {
if link == nil {
return nil
}
return &netlinkLinkImpl{
linkType: link.Type,
attrs: &netlink.LinkAttrs{ // TODO get all original attributes
Name: link.Name,
Index: link.Index,
EncapType: link.EncapType,
MTU: int(link.MTU),
NetNsID: link.NetNsID,
TxQLen: link.TxQLen,
},
}
}

View File

@@ -1,9 +0,0 @@
package netlink
import "github.com/vishvananda/netlink"
type LinkAttrs = netlink.LinkAttrs
func NewLinkAttrs() LinkAttrs {
return netlink.NewLinkAttrs()
}

View File

@@ -1,22 +1,74 @@
package netlink
import "github.com/vishvananda/netlink"
import (
"net/netip"
type Route = netlink.Route
"github.com/vishvananda/netlink"
)
func (n *NetLink) RouteList(link Link, family int) (
type Route struct {
LinkIndex int
Dst netip.Prefix
Src netip.Addr
Gw netip.Addr
Priority int
Family int
Table int
Type int
}
func (n *NetLink) RouteList(link *Link, family int) (
routes []Route, err error) {
return netlink.RouteList(link, family)
netlinkLink := linkToNetlinkLink(link)
netlinkRoutes, err := netlink.RouteList(netlinkLink, family)
if err != nil {
return nil, err
}
routes = make([]Route, len(netlinkRoutes))
for i := range netlinkRoutes {
routes[i] = netlinkRouteToRoute(netlinkRoutes[i])
}
return routes, nil
}
func (n *NetLink) RouteAdd(route *Route) error {
return netlink.RouteAdd(route)
func (n *NetLink) RouteAdd(route Route) error {
netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteAdd(&netlinkRoute)
}
func (n *NetLink) RouteDel(route *Route) error {
return netlink.RouteDel(route)
func (n *NetLink) RouteDel(route Route) error {
netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteDel(&netlinkRoute)
}
func (n *NetLink) RouteReplace(route *Route) error {
return netlink.RouteReplace(route)
func (n *NetLink) RouteReplace(route Route) error {
netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteReplace(&netlinkRoute)
}
func netlinkRouteToRoute(netlinkRoute netlink.Route) (route Route) {
return Route{
LinkIndex: netlinkRoute.LinkIndex,
Dst: netIPNetToNetipPrefix(netlinkRoute.Dst),
Src: netIPToNetipAddress(netlinkRoute.Src),
Gw: netIPToNetipAddress(netlinkRoute.Gw),
Priority: netlinkRoute.Priority,
Family: netlinkRoute.Family,
Table: netlinkRoute.Table,
Type: netlinkRoute.Type,
}
}
func routeToNetlinkRoute(route Route) (netlinkRoute netlink.Route) {
return netlink.Route{
LinkIndex: route.LinkIndex,
Dst: netipPrefixToIPNet(route.Dst),
Src: netipAddrToNetIP(route.Src),
Gw: netipAddrToNetIP(route.Gw),
Priority: route.Priority,
Family: route.Family,
Table: route.Table,
Type: route.Type,
}
}

View File

@@ -1,21 +1,90 @@
package netlink
import "github.com/vishvananda/netlink"
import (
"fmt"
"net/netip"
type Rule = netlink.Rule
"github.com/vishvananda/netlink"
)
func NewRule() *Rule {
return netlink.NewRule()
type Rule struct {
Priority int
Family int
Table int
Mark int
Src netip.Prefix
Dst netip.Prefix
Invert bool
}
func (r Rule) String() string {
from := "all"
if r.Src.IsValid() {
from = r.Src.String()
}
to := "all"
if r.Dst.IsValid() {
to = r.Dst.String()
}
return fmt.Sprintf("ip rule %d: from %s to %s table %d",
r.Priority, from, to, r.Table)
}
func NewRule() Rule {
// defaults found from netlink.NewRule() for fields we use,
// the rest of the defaults is set when converting from a `Rule`
// to a `netlink.Rule`
return Rule{
Priority: -1,
Mark: -1,
}
}
func (n *NetLink) RuleList(family int) (rules []Rule, err error) {
return netlink.RuleList(family)
netlinkRules, err := netlink.RuleList(family)
if err != nil {
return nil, err
}
rules = make([]Rule, len(netlinkRules))
for i := range netlinkRules {
rules[i] = netlinkRuleToRule(netlinkRules[i])
}
return rules, nil
}
func (n *NetLink) RuleAdd(rule *Rule) error {
return netlink.RuleAdd(rule)
func (n *NetLink) RuleAdd(rule Rule) error {
netlinkRule := ruleToNetlinkRule(rule)
return netlink.RuleAdd(&netlinkRule)
}
func (n *NetLink) RuleDel(rule *Rule) error {
return netlink.RuleDel(rule)
func (n *NetLink) RuleDel(rule Rule) error {
netlinkRule := ruleToNetlinkRule(rule)
return netlink.RuleDel(&netlinkRule)
}
func ruleToNetlinkRule(rule Rule) (netlinkRule netlink.Rule) {
netlinkRule = *netlink.NewRule()
netlinkRule.Priority = rule.Priority
netlinkRule.Family = rule.Family
netlinkRule.Table = rule.Table
netlinkRule.Mark = rule.Mark
netlinkRule.Src = netipPrefixToIPNet(rule.Src)
netlinkRule.Dst = netipPrefixToIPNet(rule.Dst)
netlinkRule.Invert = rule.Invert
return netlinkRule
}
func netlinkRuleToRule(netlinkRule netlink.Rule) (rule Rule) {
return Rule{
Priority: netlinkRule.Priority,
Family: netlinkRule.Family,
Table: netlinkRule.Table,
Mark: netlinkRule.Mark,
Src: netIPNetToNetipPrefix(netlinkRule.Src),
Dst: netIPNetToNetipPrefix(netlinkRule.Dst),
Invert: netlinkRule.Invert,
}
}