diff --git a/driver/driver.go b/driver/driver.go index 6f7f2cb..0566274 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -13,6 +13,8 @@ func init() { for _, drv := range []rtnetlink.LinkDriver{ &Bond{}, &BondSlave{}, + &Netkit{}, + &Veth{}, } { _ = rtnetlink.RegisterDriver(drv) } diff --git a/driver/netkit.go b/driver/netkit.go new file mode 100644 index 0000000..1323257 --- /dev/null +++ b/driver/netkit.go @@ -0,0 +1,119 @@ +package driver + +import ( + "errors" + "fmt" + + "github.com/jsimonetti/rtnetlink" + "github.com/jsimonetti/rtnetlink/internal/unix" + "github.com/mdlayher/netlink" +) + +// NetkitMode specifies netkit operation mode +type NetkitMode uint32 + +func (n NetkitMode) String() string { + switch n { + case NetkitModeL2: + return "layer2" + case NetkitModeL3: + return "layer3" + default: + return fmt.Sprintf("unknown NetkitMode value (%d)", n) + } +} + +const ( + // Netkit operates on layer2 + NetkitModeL2 NetkitMode = unix.NETKIT_L2 + + // Netkit operates on layer3, this is the default mode + NetkitModeL3 NetkitMode = unix.NETKIT_L3 +) + +// NetkitPolicy specifies default packet policy when no eBPF program is attached +type NetkitPolicy int32 + +func (n NetkitPolicy) String() string { + switch n { + case NetkitPolicyPass: + return "forward" + case NetkitPolicyDrop: + return "blackhole" + default: + return fmt.Sprintf("unknown NetkitPolicy value (%d)", n) + } +} + +const ( + // Default policy to forwards packets + NetkitPolicyPass NetkitPolicy = unix.NETKIT_PASS + + // Default policy to drops packets + NetkitPolicyDrop NetkitPolicy = unix.NETKIT_DROP +) + +// Netkit implements LinkDriverVerifier for the netkit driver +type Netkit struct { + Mode *NetkitMode // Specifies driver operation mode + Policy *NetkitPolicy // Specifies default policy + PeerPolicy *NetkitPolicy // Specifies default peer policy + Primary bool // Shows primary link + PeerInfo *rtnetlink.LinkMessage // Specifies peer link information +} + +var _ rtnetlink.LinkDriverVerifier = &Netkit{} + +func (n *Netkit) New() rtnetlink.LinkDriver { + return &Netkit{} +} + +func (n *Netkit) Verify(msg *rtnetlink.LinkMessage) error { + if msg.Attributes.Address != nil || (n.PeerInfo != nil && n.PeerInfo.Attributes != nil && n.PeerInfo.Attributes.Address != nil) { + return errors.New("netkit does not support setting Ethernet address") + } + return nil +} + +func (n *Netkit) Decode(ad *netlink.AttributeDecoder) error { + for ad.Next() { + switch ad.Type() { + case unix.IFLA_NETKIT_MODE: + v := NetkitMode(ad.Uint32()) + n.Mode = &v + case unix.IFLA_NETKIT_POLICY: + v := NetkitPolicy(ad.Int32()) + n.Policy = &v + case unix.IFLA_NETKIT_PEER_POLICY: + v := NetkitPolicy(ad.Int32()) + n.PeerPolicy = &v + case unix.IFLA_NETKIT_PRIMARY: + n.Primary = ad.Uint8() != 0 + } + } + return nil +} + +func (n *Netkit) Encode(ae *netlink.AttributeEncoder) error { + if n.Mode != nil { + ae.Uint32(unix.IFLA_NETKIT_MODE, uint32(*n.Mode)) + } + if n.Policy != nil { + ae.Int32(unix.IFLA_NETKIT_POLICY, int32(*n.Policy)) + } + if n.PeerPolicy != nil { + ae.Int32(unix.IFLA_NETKIT_PEER_POLICY, int32(*n.PeerPolicy)) + } + if n.PeerInfo != nil { + b, err := n.PeerInfo.MarshalBinary() + if err != nil { + return err + } + ae.Bytes(unix.IFLA_NETKIT_PEER_INFO, b) + } + return nil +} + +func (n *Netkit) Kind() string { + return "netkit" +} diff --git a/driver/netkit_live_test.go b/driver/netkit_live_test.go new file mode 100644 index 0000000..fcdc8e4 --- /dev/null +++ b/driver/netkit_live_test.go @@ -0,0 +1,155 @@ +//go:build integration +// +build integration + +package driver + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/jsimonetti/rtnetlink" + "github.com/mdlayher/netlink" +) + +func TestNetkit(t *testing.T) { + kernelMinReq(t, 6, 7) + + // establish a netlink connection + conn, err := rtnetlink.Dial(nil) + if err != nil { + t.Fatalf("failed to establish netlink socket: %v", err) + } + defer conn.Close() + + // create netns + nkns, clean, err := createNS("nkns1") + if err != nil { + t.Fatal(err) + } + defer clean() + + // establish a netlink connection with netns + connNS, err := rtnetlink.Dial(&netlink.Config{NetNS: int(nkns.Value())}) + if err != nil { + t.Fatalf("failed to establish netlink socket to ns nkns: %v", err) + } + defer connNS.Close() + + const ( + ifIndex = 1011 + ifPeerIndex = 1012 + ) + + modeL2 := NetkitModeL2 + modeL3 := NetkitModeL3 + polPass := NetkitPolicyPass + polDrop := NetkitPolicyDrop + + tests := []struct { + name string + linkName string + pconn *rtnetlink.Conn + driver *Netkit + primary *Netkit + peer *Netkit + }{ + { + name: "with empty link names both in default ns", + linkName: "", + pconn: conn, + driver: &Netkit{ + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + }, + }, + primary: &Netkit{ + Mode: &modeL3, + Policy: &polPass, + PeerPolicy: &polPass, + Primary: true, + }, + peer: &Netkit{ + Mode: &modeL3, + Policy: &polPass, + PeerPolicy: &polPass, + }, + }, + { + name: "with names both in default ns", + linkName: "nkp", + pconn: conn, + driver: &Netkit{ + Mode: &modeL2, + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + Attributes: &rtnetlink.LinkAttributes{ + Name: "nke", + }, + }, + }, + primary: &Netkit{ + Mode: &modeL2, + Policy: &polPass, + PeerPolicy: &polPass, + Primary: true, + }, + peer: &Netkit{ + Mode: &modeL2, + Policy: &polPass, + PeerPolicy: &polPass, + }, + }, + { + name: "with one is in other ns", + linkName: "nkp", + pconn: connNS, + driver: &Netkit{ + Policy: &polPass, + PeerPolicy: &polDrop, + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + Attributes: &rtnetlink.LinkAttributes{ + Name: "nke", + NetNS: nkns, + }, + }, + }, + primary: &Netkit{ + Mode: &modeL3, + Policy: &polPass, + PeerPolicy: &polDrop, + Primary: true, + }, + peer: &Netkit{ + Mode: &modeL3, + Policy: &polDrop, + PeerPolicy: &polPass, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := setupInterface(conn, tt.linkName, ifIndex, 0, tt.driver); err != nil { + t.Fatalf("failed to setup netkit interface: %v", err) + } + defer conn.Link.Delete(ifIndex) + + msg, err := getInterface(conn, ifIndex) + if err != nil { + t.Fatalf("failed to get primary netkit interface: %v", err) + } + if diff := cmp.Diff(tt.primary, msg.Attributes.Info.Data); diff != "" { + t.Error(diff) + } + + msg, err = getInterface(tt.pconn, ifPeerIndex) + if err != nil { + t.Fatalf("failed to get peer netkit interface: %v", err) + } + if diff := cmp.Diff(tt.peer, msg.Attributes.Info.Data); diff != "" { + t.Error(diff) + } + }) + } +} diff --git a/driver/veth.go b/driver/veth.go new file mode 100644 index 0000000..0fa4e6b --- /dev/null +++ b/driver/veth.go @@ -0,0 +1,51 @@ +package driver + +import ( + "fmt" + + "github.com/jsimonetti/rtnetlink" + "github.com/mdlayher/netlink" +) + +const veth_info_peer = 0x1 + +// Veth implements LinkDriverVerifier for the veth driver +type Veth struct { + PeerInfo *rtnetlink.LinkMessage // Specifies peer link information +} + +var _ rtnetlink.LinkDriver = &Veth{} + +func (v *Veth) New() rtnetlink.LinkDriver { + return &Veth{} +} + +func (v *Veth) Encode(ae *netlink.AttributeEncoder) error { + b, err := v.PeerInfo.MarshalBinary() + if err != nil { + return err + } + ae.Bytes(veth_info_peer, b) + + return nil +} + +func (v *Veth) Decode(ad *netlink.AttributeDecoder) error { + return nil +} + +func (*Veth) Kind() string { + return "veth" +} + +const ( + eth_min_mtu = 68 // Min IPv4 MTU per RFC791 + eth_max_mtu = 65535 // 65535, same as IP_MAX_MTU +) + +func (v *Veth) Verify(msg *rtnetlink.LinkMessage) error { + if msg.Attributes != nil && msg.Attributes.MTU > 0 && (msg.Attributes.MTU < eth_min_mtu || msg.Attributes.MTU > eth_max_mtu) { + return fmt.Errorf("invalid MTU value %d, must be between %d %d", msg.Attributes.MTU, eth_min_mtu, eth_max_mtu) + } + return nil +} diff --git a/driver/veth_live_test.go b/driver/veth_live_test.go new file mode 100644 index 0000000..09dbb61 --- /dev/null +++ b/driver/veth_live_test.go @@ -0,0 +1,102 @@ +//go:build integration +// +build integration + +package driver + +import ( + "testing" + + "github.com/jsimonetti/rtnetlink" + "github.com/mdlayher/netlink" +) + +func TestVeth(t *testing.T) { + // establish a netlink connection + conn, err := rtnetlink.Dial(nil) + if err != nil { + t.Fatalf("failed to establish netlink socket: %v", err) + } + defer conn.Close() + + // create netns + vtns, clean, err := createNS("vtns1") + if err != nil { + t.Fatal(err) + } + defer clean() + + // establish a netlink connection with netns + connNS, err := rtnetlink.Dial(&netlink.Config{NetNS: int(vtns.Value())}) + if err != nil { + t.Fatalf("failed to establish netlink socket to ns vtns1: %v", err) + } + defer connNS.Close() + + const ( + ifIndex = 1021 + ifPeerIndex = 1022 + ) + + tests := []struct { + name string + linkName string + pconn *rtnetlink.Conn + driver *Veth + }{ + { + name: "with empty names both in default ns", + pconn: conn, + driver: &Veth{ + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + }, + }, + }, + { + name: "with names both in default ns", + linkName: "vtp", + pconn: conn, + driver: &Veth{ + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + Attributes: &rtnetlink.LinkAttributes{ + Name: "vte", + }, + }, + }, + }, + { + name: "with names one in other ns", + linkName: "vtp", + pconn: connNS, + driver: &Veth{ + PeerInfo: &rtnetlink.LinkMessage{ + Index: ifPeerIndex, + Attributes: &rtnetlink.LinkAttributes{ + Name: "vte", + NetNS: vtns, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := setupInterface(conn, tt.linkName, ifIndex, 0, tt.driver); err != nil { + t.Fatalf("failed to setup veth interface: %v", err) + } + defer conn.Link.Delete(ifIndex) + + _, err = getInterface(conn, ifIndex) + if err != nil { + t.Fatalf("failed to get primary veth interface: %v", err) + } + + _, err = getInterface(tt.pconn, ifPeerIndex) + if err != nil { + t.Fatalf("failed to get peer veth interface: %v", err) + } + }) + } +} diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go index 1555b00..075b219 100644 --- a/internal/unix/types_linux.go +++ b/internal/unix/types_linux.go @@ -121,6 +121,12 @@ const ( IFLA_INFO_SLAVE_DATA = linux.IFLA_INFO_SLAVE_DATA IFLA_NET_NS_PID = linux.IFLA_NET_NS_PID IFLA_NET_NS_FD = linux.IFLA_NET_NS_FD + IFLA_NETKIT_UNSPEC = linux.IFLA_NETKIT_UNSPEC + IFLA_NETKIT_PEER_INFO = linux.IFLA_NETKIT_PEER_INFO + IFLA_NETKIT_PRIMARY = linux.IFLA_NETKIT_PRIMARY + IFLA_NETKIT_POLICY = linux.IFLA_NETKIT_POLICY + IFLA_NETKIT_PEER_POLICY = linux.IFLA_NETKIT_PEER_POLICY + IFLA_NETKIT_MODE = linux.IFLA_NETKIT_MODE IFLA_XDP = linux.IFLA_XDP IFLA_XDP_FD = linux.IFLA_XDP_FD IFLA_XDP_ATTACHED = linux.IFLA_XDP_ATTACHED @@ -197,4 +203,10 @@ const ( FRA_IP_PROTO = linux.FRA_IP_PROTO FRA_SPORT_RANGE = linux.FRA_SPORT_RANGE FRA_DPORT_RANGE = linux.FRA_DPORT_RANGE + NETKIT_NEXT = linux.NETKIT_NEXT + NETKIT_PASS = linux.NETKIT_PASS + NETKIT_DROP = linux.NETKIT_DROP + NETKIT_REDIRECT = linux.NETKIT_REDIRECT + NETKIT_L2 = linux.NETKIT_L2 + NETKIT_L3 = linux.NETKIT_L3 ) diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go index de794c2..e0aa11a 100644 --- a/internal/unix/types_other.go +++ b/internal/unix/types_other.go @@ -117,6 +117,12 @@ const ( IFLA_INFO_SLAVE_DATA = 0x5 IFLA_NET_NS_PID = 0x13 IFLA_NET_NS_FD = 0x1c + IFLA_NETKIT_UNSPEC = 0x0 + IFLA_NETKIT_PEER_INFO = 0x1 + IFLA_NETKIT_PRIMARY = 0x2 + IFLA_NETKIT_POLICY = 0x3 + IFLA_NETKIT_PEER_POLICY = 0x4 + IFLA_NETKIT_MODE = 0x5 IFLA_XDP = 0x2b IFLA_XDP_FD = 0x1 IFLA_XDP_ATTACHED = 0x2 @@ -193,4 +199,10 @@ const ( FRA_IP_PROTO = 0x16 FRA_SPORT_RANGE = 0x17 FRA_DPORT_RANGE = 0x18 + NETKIT_NEXT = -0x1 + NETKIT_PASS = 0x0 + NETKIT_DROP = 0x2 + NETKIT_REDIRECT = 0x7 + NETKIT_L2 = 0x0 + NETKIT_L3 = 0x1 )