-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LinkDriver interface and Driver package (#221)
* Add attr validation This commit removes unix.IFLA_UNSPEC and introduces checks for the interface name, the link type and the queue disc fields. Interface name validation is necessary to prevent an 'invalid argument' error when creating a link with an empty name. Other checks were added to be consistent with the ip tools. Signed-off-by: Birol Bilgin <birolbilgin@gmail.com> * Add network namespace type This commit introduces the NetNS struct and integrates network namespace capabilities into link attributes. This enhancement facilitates the creation of links, such as veth pairs, across different network namespaces without the need to execute directly within those namespaces. Signed-off-by: Birol Bilgin <birolbilgin@gmail.com> * Add LinkDriver interface This commit introduces a Driver interface and changes Data and SlaveData fields within the LinkInfo struct as LinkDriver to accommodate driver-specific data encoding, addressing the limitation where LinkInfo.Data and SlaveData fields were merely byte slices without support for specific data encoding. Drivers are registered globally with the RegisterDriver function. For un-registered drivers, the default LinkData driver is used. Signed-off-by: Birol Bilgin <birolbilgin@gmail.com> * Add Driver Package This commit introduces the 'driver' package, which contains specific implementations of the LinkDriver interface. It also includes the implementation of the Linux bond driver as LinkDriver and the bond slave driver as LinkSlaveDriver. Signed-off-by: Birol Bilgin <birolbilgin@gmail.com> * Add Netkit and Veth drivers This commit adds Netkit and Veth drivers. Signed-off-by: Birol Bilgin <birolbilgin@gmail.com> --------- Signed-off-by: Birol Bilgin <birolbilgin@gmail.com>
- Loading branch information
Showing
13 changed files
with
2,209 additions
and
309 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
//go:build integration | ||
// +build integration | ||
|
||
package driver | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/jsimonetti/rtnetlink" | ||
"github.com/mdlayher/netlink" | ||
) | ||
|
||
func bondT(d rtnetlink.LinkDriver) *Bond { | ||
b := d.(*Bond) | ||
return &Bond{ | ||
Mode: b.Mode, | ||
Miimon: b.Miimon, | ||
ArpInterval: b.ArpInterval, | ||
ArpIpTargets: b.ArpIpTargets, | ||
NsIP6Targets: b.NsIP6Targets, | ||
} | ||
} | ||
|
||
func bondSlaveT(d rtnetlink.LinkDriver) *BondSlave { | ||
b := d.(*BondSlave) | ||
return &BondSlave{ | ||
State: b.State, | ||
MiiStatus: b.MiiStatus, | ||
Priority: b.Priority, | ||
} | ||
} | ||
|
||
func TestBond(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() | ||
|
||
bns, clean, err := createNS("bns1") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer clean() | ||
|
||
// use ns for testing arp ip targets | ||
connNS, err := rtnetlink.Dial(&netlink.Config{NetNS: int(bns.Value())}) | ||
if err != nil { | ||
t.Fatalf("failed to establish netlink socket to ns nkns: %v", err) | ||
} | ||
defer connNS.Close() | ||
|
||
var ( | ||
ssa = BondStateActive | ||
ssb = BondStateBackup | ||
miiup = BondLinkUp | ||
u325 uint32 = 5 | ||
u32100 uint32 = 100 | ||
u32 uint32 | ||
i321 int32 = 1 | ||
i32 int32 | ||
) | ||
|
||
tests := []struct { | ||
name string | ||
conn *rtnetlink.Conn | ||
driver *Bond | ||
bond *Bond | ||
setSlave bool | ||
dummy []BondSlave | ||
}{ | ||
{ | ||
name: "with default mode and miion is set", | ||
conn: conn, | ||
driver: &Bond{ | ||
Miimon: &u32100, | ||
}, | ||
bond: &Bond{ | ||
Mode: BondModeBalanceRR, | ||
Miimon: &u32100, | ||
ArpInterval: &u32, | ||
}, | ||
dummy: []BondSlave{ | ||
{ | ||
State: &ssa, | ||
MiiStatus: &miiup, | ||
Priority: &i32, | ||
}, | ||
{ | ||
State: &ssa, | ||
MiiStatus: &miiup, | ||
Priority: &i32, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "with active backup, and arp ip targets list", | ||
conn: connNS, | ||
driver: &Bond{ | ||
Mode: BondModeActiveBackup, | ||
ArpInterval: &u325, | ||
ArpIpTargets: []net.IP{{192, 168, 222, 2}, {192, 168, 222, 3}}, | ||
}, | ||
bond: &Bond{ | ||
Mode: BondModeActiveBackup, | ||
Miimon: &u32, | ||
ArpInterval: &u325, | ||
ArpIpTargets: []net.IP{{192, 168, 222, 2}, {192, 168, 222, 3}}, | ||
}, | ||
setSlave: true, | ||
dummy: []BondSlave{ | ||
{ | ||
State: &ssb, | ||
MiiStatus: &miiup, | ||
Priority: &i32, | ||
}, | ||
{ | ||
State: &ssa, | ||
MiiStatus: &miiup, | ||
Priority: &i321, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "with balanced xor, and arp ns ipv6 list", | ||
conn: connNS, | ||
driver: &Bond{ | ||
Mode: BondModeBalanceXOR, | ||
ArpInterval: &u325, | ||
NsIP6Targets: []net.IP{ | ||
{0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, | ||
{0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, | ||
}, | ||
}, | ||
bond: &Bond{ | ||
Mode: BondModeBalanceXOR, | ||
Miimon: &u32, | ||
ArpInterval: &u325, | ||
NsIP6Targets: []net.IP{ | ||
{0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, | ||
{0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, | ||
}, | ||
}, | ||
dummy: []BondSlave{ | ||
{ | ||
State: &ssa, | ||
MiiStatus: &miiup, | ||
Priority: &i32, | ||
}, | ||
{ | ||
State: &ssa, | ||
MiiStatus: &miiup, | ||
Priority: &i32, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for i, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
bondID := 1100 + uint32(i*10) | ||
if err := setupInterface(tt.conn, fmt.Sprintf("b%d", bondID), bondID, 0, tt.driver); err != nil { | ||
t.Fatalf("failed to setup bond interface: %v", err) | ||
} | ||
defer tt.conn.Link.Delete(bondID) | ||
|
||
msg, err := getInterface(tt.conn, bondID) | ||
if err != nil { | ||
t.Fatalf("failed to get primary netkit interface: %v", err) | ||
} | ||
if diff := cmp.Diff(tt.bond, bondT(msg.Attributes.Info.Data)); diff != "" { | ||
t.Error(diff) | ||
} | ||
|
||
slave1ID := 1101 + uint32(i*10) | ||
if err := setupInterface(tt.conn, fmt.Sprintf("d%d", slave1ID), slave1ID, bondID, &rtnetlink.LinkData{Name: "dummy"}); err != nil { | ||
t.Fatalf("failed to setup d%d interface: %v", slave1ID, err) | ||
} | ||
defer tt.conn.Link.Delete(slave1ID) | ||
|
||
slave2ID := 1102 + uint32(i*10) | ||
if err := setupInterface(tt.conn, fmt.Sprintf("d%d", slave2ID), slave2ID, bondID, &rtnetlink.LinkData{Name: "dummy"}); err != nil { | ||
t.Fatalf("failed to setup d1%d interface: %v", slave2ID, err) | ||
} | ||
defer tt.conn.Link.Delete(slave2ID) | ||
|
||
if tt.setSlave { | ||
tt.conn.Link.Set(&rtnetlink.LinkMessage{ | ||
Index: slave2ID, | ||
Attributes: &rtnetlink.LinkAttributes{ | ||
Info: &rtnetlink.LinkInfo{ | ||
SlaveKind: "bond", | ||
SlaveData: &BondSlave{ | ||
Priority: &i321, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
for i, id := range []uint32{slave1ID, slave2ID} { | ||
msg, err = getInterface(tt.conn, id) | ||
if err != nil { | ||
t.Fatalf("failed to get peer netkit interface: %v", err) | ||
} | ||
if diff := cmp.Diff(&tt.dummy[i], bondSlaveT(msg.Attributes.Info.SlaveData)); diff != "" { | ||
t.Errorf("slave %d %s", i, diff) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package driver | ||
|
||
import ( | ||
"github.com/jsimonetti/rtnetlink" | ||
) | ||
|
||
// init registers predefined drivers with the rtnetlink package. | ||
// | ||
// Currently, registering driver implementations that conflict with existing ones isn't supported. | ||
// Since most users don't need this feature, we'll keep it as is. | ||
// If required, we could consider implementing rtnetlink.UnregisterDriver to address this. | ||
func init() { | ||
for _, drv := range []rtnetlink.LinkDriver{ | ||
&Bond{}, | ||
&BondSlave{}, | ||
&Netkit{}, | ||
&Veth{}, | ||
} { | ||
_ = rtnetlink.RegisterDriver(drv) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
//go:build integration | ||
// +build integration | ||
|
||
package driver | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"os/exec" | ||
"testing" | ||
|
||
"github.com/jsimonetti/rtnetlink" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func getKernelVersion() (kernel, major, minor int, err error) { | ||
var uname unix.Utsname | ||
if err := unix.Uname(&uname); err != nil { | ||
return 0, 0, 0, err | ||
} | ||
|
||
end := bytes.IndexByte(uname.Release[:], 0) | ||
versionStr := uname.Release[:end] | ||
|
||
if count, _ := fmt.Sscanf(string(versionStr), "%d.%d.%d", &kernel, &major, &minor); count < 2 { | ||
err = fmt.Errorf("failed to parse kernel version from: %q", string(versionStr)) | ||
} | ||
return | ||
} | ||
|
||
// kernelMinReq checks if the runtime kernel is sufficient | ||
// for the test | ||
func kernelMinReq(t *testing.T, kernel, major int) { | ||
k, m, _, err := getKernelVersion() | ||
if err != nil { | ||
t.Fatalf("failed to get host kernel version: %v", err) | ||
} | ||
if k < kernel || k == kernel && m < major { | ||
t.Skipf("host kernel (%d.%d) does not meet test's minimum required version: (%d.%d)", | ||
k, m, kernel, major) | ||
} | ||
} | ||
|
||
// setupInterface create a interface for testing | ||
func setupInterface(conn *rtnetlink.Conn, name string, index, master uint32, driver rtnetlink.LinkDriver) error { | ||
attrs := &rtnetlink.LinkAttributes{ | ||
Name: name, | ||
Info: &rtnetlink.LinkInfo{Kind: driver.Kind(), Data: driver}, | ||
} | ||
flag := uint32(unix.IFF_UP) | ||
if master > 0 { | ||
attrs.Master = &master | ||
flag = 0 | ||
} | ||
// construct an interface to test drivers | ||
err := conn.Link.New(&rtnetlink.LinkMessage{ | ||
Family: unix.AF_UNSPEC, | ||
Index: index, | ||
Flags: flag, | ||
Change: flag, | ||
Attributes: attrs, | ||
}) | ||
if err != nil { | ||
conn.Link.Delete(index) | ||
} | ||
return err | ||
} | ||
|
||
func getInterface(conn *rtnetlink.Conn, index uint32) (*rtnetlink.LinkMessage, error) { | ||
interf, err := conn.Link.Get(index) | ||
if err != nil { | ||
conn.Link.Delete(interf.Index) | ||
return nil, err | ||
} | ||
return &interf, err | ||
} | ||
|
||
// creates a network namespace by utilizing ip commandline tool | ||
// returns NetNS and clean function | ||
func createNS(name string) (*rtnetlink.NetNS, func(), error) { | ||
cmdPath, err := exec.LookPath("ip") | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("getting ip command path failed, %w", err) | ||
} | ||
_, err = exec.Command(cmdPath, "netns", "add", name).Output() | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("ip netns add %s, failed: %w", name, err) | ||
} | ||
|
||
ns, err := rtnetlink.NewNetNS(name) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("reading ns %s, failed: %w", name, err) | ||
} | ||
return ns, func() { | ||
if err := ns.Close(); err != nil { | ||
fmt.Printf("closing ns file failed: %v", err) | ||
} | ||
_, err := exec.Command(cmdPath, "netns", "del", name).Output() | ||
if err != nil { | ||
fmt.Printf("removing netns %s failed, %v", name, err) | ||
} | ||
}, nil | ||
} |
Oops, something went wrong.