netconfig: implement bridge configuration
fixes https://github.com/rtr7/router7/issues/65
This commit is contained in:
parent
d0f963def3
commit
cffd872346
@ -458,6 +458,131 @@ peer: AVU3LodtnFaFnJmMyNNW7cUk4462lqnVULTFkjWYvRo=
|
||||
})
|
||||
}
|
||||
|
||||
const goldenInterfacesBridges = `
|
||||
{
|
||||
"bridges":[
|
||||
{
|
||||
"name": "lan0",
|
||||
"interface_hardware_addrs": ["02:73:53:00:b0:0c"]
|
||||
}
|
||||
],
|
||||
"interfaces":[
|
||||
{
|
||||
"hardware_addr": "02:73:53:00:ca:fe",
|
||||
"name": "uplink0"
|
||||
},
|
||||
{
|
||||
"spoof_hardware_addr": "02:73:53:00:b0:aa",
|
||||
"name": "lan0",
|
||||
"addr": "192.168.42.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
func TestNetconfigBridges(t *testing.T) {
|
||||
if os.Getenv("HELPER_PROCESS") == "1" {
|
||||
tmp, err := ioutil.TempDir("", "router7")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
for _, golden := range []struct {
|
||||
filename, content string
|
||||
}{
|
||||
{"interfaces.json", goldenInterfacesBridges},
|
||||
} {
|
||||
if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(golden.filename)), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(tmp, golden.filename), []byte(golden.content), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(tmp, "root", "etc"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(tmp, "root", "tmp"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
netconfig.DefaultCounterObj = &nftables.CounterObj{Packets: 23, Bytes: 42}
|
||||
if err := netconfig.Apply(tmp, filepath.Join(tmp, "root")); err != nil {
|
||||
t.Fatalf("netconfig.Apply: %v", err)
|
||||
}
|
||||
|
||||
// Apply twice to ensure the absence of errors when dealing with
|
||||
// already-configured interfaces, addresses, routes, … (and ensure
|
||||
// nftables rules are replaced, not appendend to).
|
||||
netconfig.DefaultCounterObj = &nftables.CounterObj{Packets: 0, Bytes: 0}
|
||||
if err := netconfig.Apply(tmp, filepath.Join(tmp, "root")); err != nil {
|
||||
t.Fatalf("netconfig.Apply: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
const ns = "ns6" // name of the network namespace to use for this test
|
||||
|
||||
add := exec.Command("ip", "netns", "add", ns)
|
||||
add.Stderr = os.Stderr
|
||||
if err := add.Run(); err != nil {
|
||||
t.Fatalf("%v: %v", add.Args, err)
|
||||
}
|
||||
defer exec.Command("ip", "netns", "delete", ns).Run()
|
||||
|
||||
nsSetup := []*exec.Cmd{
|
||||
exec.Command("ip", "-netns", ns, "link", "add", "dummy0", "type", "dummy"),
|
||||
exec.Command("ip", "-netns", ns, "link", "add", "eth0", "type", "dummy"),
|
||||
exec.Command("ip", "-netns", ns, "link", "set", "dummy0", "address", "02:73:53:00:ca:fe"),
|
||||
exec.Command("ip", "-netns", ns, "link", "set", "eth0", "address", "02:73:53:00:b0:0c"),
|
||||
}
|
||||
|
||||
for _, cmd := range nsSetup {
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("%v: %v", cmd.Args, err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("ip", "netns", "exec", ns, os.Args[0], "-test.run=^TestNetconfigBridges")
|
||||
cmd.Env = append(os.Environ(), "HELPER_PROCESS=1")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("VerifyAddresses", func(t *testing.T) {
|
||||
link, err := exec.Command("ip", "-netns", ns, "link", "show", "dev", "lan0", "type", "bridge").Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(link), "link/ether 02:73:53:00:b0:aa") {
|
||||
t.Errorf("lan0 MAC address is not 02:73:53:00:b0:aa")
|
||||
}
|
||||
|
||||
addrs, err := exec.Command("ip", "-netns", ns, "address", "show", "dev", "lan0").Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
addrRe := regexp.MustCompile(`(?m)^\s*inet 192.168.42.1/24 brd 192.168.42.255 scope global lan0`)
|
||||
if !addrRe.MatchString(string(addrs)) {
|
||||
t.Fatalf("regexp %s does not match %s", addrRe, string(addrs))
|
||||
}
|
||||
|
||||
bridgeLinks, err := exec.Command("ip", "-netns", ns, "link", "show", "master", "lan0").Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(bridgeLinks), ": eth0: ") {
|
||||
t.Errorf("lan0 bridge does not contain eth0 interface")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func ipLines(args ...string) ([]string, error) {
|
||||
cmd := exec.Command("ip", args...)
|
||||
out, err := cmd.Output()
|
||||
|
@ -198,8 +198,14 @@ type InterfaceDetails struct {
|
||||
Addr string `json:"addr"` // e.g. 192.168.42.1/24
|
||||
}
|
||||
|
||||
type BridgeDetails struct {
|
||||
Name string `json:"name"` // e.g. br0 or lan0
|
||||
InterfaceHardwareAddrs []string `json:"interface_hardware_addrs"`
|
||||
}
|
||||
|
||||
type InterfaceConfig struct {
|
||||
Interfaces []InterfaceDetails `json:"interfaces"`
|
||||
Bridges []BridgeDetails `json:"bridges"`
|
||||
}
|
||||
|
||||
// Interface returns the InterfaceDetails configured for interface ifname in
|
||||
@ -255,6 +261,56 @@ func applyInterfaces(dir, root string) error {
|
||||
}
|
||||
byName[details.Name] = details
|
||||
}
|
||||
|
||||
for _, bridge := range cfg.Bridges {
|
||||
if _, err := netlink.LinkByName(bridge.Name); err != nil {
|
||||
link := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridge.Name}}
|
||||
if err := netlink.LinkAdd(link); err != nil {
|
||||
return fmt.Errorf("netlink.LinkAdd: %v", err)
|
||||
}
|
||||
}
|
||||
interfaces := make(map[string]bool)
|
||||
for _, hwaddr := range bridge.InterfaceHardwareAddrs {
|
||||
interfaces[hwaddr] = true
|
||||
}
|
||||
bridgeLink, err := netlink.LinkByName(bridge.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("LinkByName(%s): %v", bridge.Name, err)
|
||||
}
|
||||
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range links {
|
||||
attr := l.Attrs()
|
||||
addr := attr.HardwareAddr.String()
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
if !interfaces[addr] {
|
||||
continue
|
||||
}
|
||||
log.Printf("adding interface %s to bridge %s", attr.Name, bridge.Name)
|
||||
if err := netlink.LinkSetMaster(l, bridgeLink); err != nil {
|
||||
return fmt.Errorf("LinkSetMaster(%s): %v", attr.Name, err)
|
||||
}
|
||||
if attr.OperState != netlink.OperUp {
|
||||
log.Printf("setting interface %s up", attr.Name)
|
||||
if err := netlink.LinkSetUp(l); err != nil {
|
||||
return fmt.Errorf("LinkSetUp(%s): %v", attr.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if attr := bridgeLink.Attrs(); attr.OperState != netlink.OperUp {
|
||||
log.Printf("setting interface %s up", attr.Name)
|
||||
if err := netlink.LinkSetUp(bridgeLink); err != nil {
|
||||
return fmt.Errorf("LinkSetUp(%s): %v", attr.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -276,6 +332,9 @@ func applyInterfaces(dir, root string) error {
|
||||
}
|
||||
} else {
|
||||
details, ok = byHardwareAddr[addr]
|
||||
if !ok {
|
||||
details, ok = byName[attr.Name]
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
log.Printf("no config for interface %s/%s", attr.Name, addr)
|
||||
|
Loading…
x
Reference in New Issue
Block a user