From 5f04db47be61bfa7dcb3023eeb69e8b2f02a5a84 Mon Sep 17 00:00:00 2001 From: buty4649 Date: Wed, 20 Mar 2024 23:57:02 +0900 Subject: [PATCH] Refactor network configuration code for better structure --- cmd/apply.go | 102 +++++++++++++++-------- cmd/destroy.go | 8 +- cmd/root.go | 2 +- iproute2/addresses.go | 66 --------------- iproute2/base.go | 190 ++++++++++++++++++++++++++++++++++++++++++ iproute2/iproute2.go | 155 +++++++++------------------------- iproute2/links.go | 56 ------------- 7 files changed, 302 insertions(+), 277 deletions(-) delete mode 100644 iproute2/addresses.go create mode 100644 iproute2/base.go delete mode 100644 iproute2/links.go diff --git a/cmd/apply.go b/cmd/apply.go index 0aaeae0..9eb0e19 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -35,7 +35,7 @@ var applyCmd = &cobra.Command{ Long: "Apply netns networks configuration to running system", RunE: func(cmd *cobra.Command, args []string) error { for netns, values := range cfg.Netns { - if ip.NetnsExists(netns) { + if ip.ExistsNetns(netns) { slog.Warn("netns is already exists", "name", netns) } else { slog.Info("create netns", "name", netns) @@ -69,13 +69,25 @@ var applyCmd = &cobra.Command{ }, } -func SetupDevice(name string, addresses []string, routes []config.Route) error { - err := SetLinkUp(name) +type IpCommand interface { + SetLinkUp(name string) error + AddAddress(name, address string) error + AddRoute(name, to, via string) error + InNetns() bool + Netns() string +} + +func SetupDevice(ip IpCommand, name string, addresses []string, routes []config.Route) error { + err := SetLinkUp(ip, name) if err != nil { return err } - slog.Info("add addresses", "name", name, "addresses", addresses) + if ip.InNetns() { + slog.Info("add addresses", "name", name, "addresses", addresses, "netns", ip.Netns()) + } else { + slog.Info("add addresses", "name", name, "addresses", addresses) + } for _, address := range addresses { err := ip.AddAddress(name, address) if err != nil { @@ -84,8 +96,12 @@ func SetupDevice(name string, addresses []string, routes []config.Route) error { } for _, route := range routes { - slog.Info("add route", "name", name, "to", route.To, "via", route.Via) - err := ip.AddRoute(route.To, route.Via, name) + if ip.InNetns() { + slog.Info("add route", "name", name, "to", route.To, "via", route.Via, "netns", ip.Netns()) + } else { + slog.Info("add route", "name", name, "to", route.To, "via", route.Via) + } + err := ip.AddRoute(name, route.To, route.Via) if err != nil { return err } @@ -93,44 +109,54 @@ func SetupDevice(name string, addresses []string, routes []config.Route) error { return nil } -func SetLinkUp(name string) error { - slog.Info("link up", "name", name, "netns", ip.Netns()) +func SetLinkUp(ip IpCommand, name string) error { + if ip.InNetns() { + slog.Info("link up", "name", name, "netns", ip.Netns()) + } else { + slog.Info("link up", "name", name) + } return ip.SetLinkUp(name) } func SetupLoopback(netns string) error { - return ip.IntoNetns(netns, func() error { - return SetLinkUp("lo") - }) + return SetLinkUp(ip.IntoNetns(netns), "lo") +} + +func SetNetns(name string, netns string) error { + slog.Info("set netns", "name", name, "netns", netns) + return ip.SetNetns(name, netns) } func SetupEthernets(netns string, ethernets map[string]config.Ethernet) error { for name, values := range ethernets { - slog.Info("set netns", "name", name, "netns", netns) - err := ip.SetNetns(name, netns) + err := SetNetns(name, netns) if err != nil { return err } - ip.IntoNetns(netns, func() error { - return SetupDevice(name, values.Addresses, values.Routes) - }) + err = SetupDevice(ip.IntoNetns(netns), name, values.Addresses, values.Routes) + if err != nil { + return err + } } return nil } func SetupDummyDevices(netns string, devices map[string]config.Ethernet) error { for name, values := range devices { - ip.IntoNetns(netns, func() error { - slog.Info("add dummy device", "name", name, "netns", netns) - err := ip.AddDummyDevice(name) - if err != nil { - return err - } + n := ip.IntoNetns(netns) - return SetupDevice(name, values.Addresses, values.Routes) - }) + slog.Info("add dummy device", "name", name, "netns", netns) + err := n.AddDummyDevice(name) + if err != nil { + return err + } + + err = SetupDevice(n, name, values.Addresses, values.Routes) + if err != nil { + return err + } } return nil } @@ -140,30 +166,38 @@ func SetupVethDevices(netns string, devices map[string]config.VethDevice) error peerName := values.Peer.Name peerNetns := values.Peer.Netns - slog.Info("add veth device", "name", name, "netns", netns, "peer name", peerName, "peer netns", peerNetns) + slog.Info("add veth device", "name", name, "peer name", peerName) err := ip.AddVethDevice(name, peerName) if err != nil { return err } - err = ip.SetNetns(name, netns) + err = SetNetns(name, netns) + if err != nil { + return err + } + + n := ip.IntoNetns(netns) + err = SetupDevice(n, name, values.Addresses, values.Routes) if err != nil { return err } - ip.IntoNetns(netns, func() error { - return SetupDevice(name, values.Addresses, values.Routes) - }) if peerNetns != "" { - err = ip.SetNetns(peerName, peerNetns) + err = SetNetns(peerName, peerNetns) + if err != nil { + return err + } + n := ip.IntoNetns(peerNetns) + err = SetupDevice(n, peerName, values.Peer.Addresses, values.Peer.Routes) if err != nil { return err } - ip.IntoNetns(netns, func() error { - return SetupDevice(peerName, values.Peer.Addresses, values.Peer.Routes) - }) } else { - SetupDevice(peerName, values.Peer.Addresses, values.Peer.Routes) + err = SetupDevice(ip, peerName, values.Peer.Addresses, values.Peer.Routes) + if err != nil { + return err + } } } return nil diff --git a/cmd/destroy.go b/cmd/destroy.go index 6ca9e42..c102cba 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -29,12 +29,12 @@ import ( // destroyCmd represents the destroy command var destroyCmd = &cobra.Command{ - Use: "destroy", - Short: "Destroy netns networks configuration from running system", - Long: "Destroy netns networks configuration from running system", + Use: "destroy", + Short: "Destroy netns networks configuration from running system", + Long: "Destroy netns networks configuration from running system", RunE: func(cmd *cobra.Command, args []string) error { for n := range cfg.Netns { - if ip.NetnsExists(n) { + if ip.ExistsNetns(n) { slog.Info("delete netns", "name", n) err := ip.DelNetns(n) if err != nil { diff --git a/cmd/root.go b/cmd/root.go index 961daa0..7ca507e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -43,7 +43,7 @@ type Flags struct { var flags Flags var cfg *config.Config -var ip *iproute2.Iproute2 +var ip *iproute2.IpCmd var rootCmd = &cobra.Command{ Use: "netnsplan", diff --git a/iproute2/addresses.go b/iproute2/addresses.go deleted file mode 100644 index 8250b10..0000000 --- a/iproute2/addresses.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright © 2024 buty4649 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -package iproute2 - -import "encoding/json" - -type AddressInfo struct { - Family string `json:"family"` - Local string `json:"local"` - Prefixlen int `json:"prefixlen"` - Scope string `json:"scope"` - Label string `json:"label"` - ValidLifeTime uint64 `json:"valid_life_time"` - PreferredLifeTime uint64 `json:"preferred_life_time"` -} - -type InterfaceInfo struct { - Ifindex int `json:"ifindex"` - Ifname string `json:"ifname"` - Flags []string `json:"flags"` - Mtu int `json:"mtu"` - Qdisc string `json:"qdisc"` - Operstate string `json:"operstate"` - Group string `json:"group"` - Txqlen int `json:"txqlen"` - LinkType string `json:"link_type"` - Address string `json:"address"` - Broadcast string `json:"broadcast"` - AddrInfo []AddressInfo `json:"addr_info"` -} - -type Addresses []InterfaceInfo - -func (i *Iproute2) ListAddresses() (*Addresses, error) { - data, err := i.executeWithStdout("-json", "address", "show") - if err != nil { - return nil, err - } - - var addresses Addresses - err = json.Unmarshal([]byte(data), &addresses) - if err != nil { - return nil, err - } - - return &addresses, nil -} diff --git a/iproute2/base.go b/iproute2/base.go new file mode 100644 index 0000000..4121959 --- /dev/null +++ b/iproute2/base.go @@ -0,0 +1,190 @@ +/* +Copyright © 2024 buty4649 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package iproute2 + +import ( + "encoding/json" + "fmt" + "os/exec" + "strings" + "syscall" +) + +type BaseCommand struct { + path string + prependArgs []string +} + +type Error struct { + ExitStatus int + Message string +} + +func (b *BaseCommand) execute(args ...string) error { + _, err := b.executeWithOutput(args...) + return err +} + +func (e *Error) Error() string { + msg := strings.TrimRight(e.Message, "\n") + return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) +} + +func (b *BaseCommand) executeWithOutput(args ...string) (string, error) { + cmdArgs := append(b.prependArgs, args...) + + if logger != nil { + logger.Debug("exec", "cmd", b.path, "args", cmdArgs) + } + + cmd := exec.Command(b.path, cmdArgs...) + stdout, err := cmd.Output() + if err != nil { + exitErr, _ := err.(*exec.ExitError) + status, _ := exitErr.Sys().(syscall.WaitStatus) + stderr := string(exitErr.Stderr) + return "", &Error{ + ExitStatus: status.ExitStatus(), + Message: stderr, + } + } + return string(stdout), nil +} + +func (b *BaseCommand) AddLink(name string, linkType string, options ...string) error { + args := append([]string{"link", "add", name, "type", linkType}, options...) + return b.execute(args...) +} + +func (b *BaseCommand) DelLink(name string) error { + return b.execute("link", "del", name) +} + +func (b *BaseCommand) AddDummyDevice(name string) error { + return b.AddLink(name, "dummy") +} + +func (b *BaseCommand) AddVethDevice(name string, peerName string) error { + return b.AddLink(name, "veth", "peer", "name", peerName) +} + +func (b *BaseCommand) SetLinkUp(name string) error { + return b.execute("link", "set", "dev", name, "up") +} + +func (b *BaseCommand) AddAddress(name string, address string) error { + return b.execute("address", "add", address, "dev", name) +} + +func (b *BaseCommand) DelAddress(name string, address string) error { + return b.execute("address", "del", address, "dev", name) +} + +func (b *BaseCommand) AddRoute(name string, to string, via string) error { + return b.execute("route", "add", to, "via", via, "dev", name) +} + +func (b *BaseCommand) DelRoute(name string, to string, via string) error { + return b.execute("route", "del", to, "via", via, "dev", name) +} + +func (i *IpCmd) InNetns() bool { + return false +} + +func (i *IpCmd) Netns() string { + return "" +} + +type AddressInfo struct { + Family string `json:"family"` + Local string `json:"local"` + Prefixlen int `json:"prefixlen"` + Scope string `json:"scope"` + Label string `json:"label"` + ValidLifeTime uint64 `json:"valid_life_time"` + PreferredLifeTime uint64 `json:"preferred_life_time"` +} + +type InterfaceInfo struct { + Ifindex int `json:"ifindex"` + Ifname string `json:"ifname"` + Flags []string `json:"flags"` + Mtu int `json:"mtu"` + Qdisc string `json:"qdisc"` + Operstate string `json:"operstate"` + Group string `json:"group"` + Txqlen int `json:"txqlen"` + LinkType string `json:"link_type"` + Address string `json:"address"` + Broadcast string `json:"broadcast"` + AddrInfo []AddressInfo `json:"addr_info"` +} + +type Addresses []InterfaceInfo + +func (b *BaseCommand) ListAddresses() (*Addresses, error) { + data, err := b.executeWithOutput("-json", "address", "show") + if err != nil { + return nil, err + } + + var addresses Addresses + err = json.Unmarshal([]byte(data), &addresses) + if err != nil { + return nil, err + } + + return &addresses, nil +} + +type Link struct { + Ifindex int `json:"ifindex"` + Ifname string `json:"ifname"` + Flags []string `json:"flags"` + Mtu int `json:"mtu"` + Qdisc string `json:"qdisc"` + Operstate string `json:"operstate"` + Linkmode string `json:"linkmode"` + Group string `json:"group"` + Txqlen int `json:"txqlen"` + LinkType string `json:"link_type"` + Address string `json:"address"` + Broadcast string `json:"broadcast"` +} + +type Links []Link + +func (b *BaseCommand) ListLinks() (*Links, error) { + data, err := b.executeWithOutput("-json", "link", "show") + if err != nil { + return nil, err + } + + var links Links + err = json.Unmarshal([]byte(data), &links) + if err != nil { + return nil, err + } + + return &links, nil +} diff --git a/iproute2/iproute2.go b/iproute2/iproute2.go index b5bfc31..236204f 100644 --- a/iproute2/iproute2.go +++ b/iproute2/iproute2.go @@ -22,85 +22,35 @@ SOFTWARE. package iproute2 import ( - "bytes" "fmt" "log/slog" - "os/exec" "slices" "strconv" "strings" - "syscall" ) -type Iproute2 struct { - path string - netns string - useNetns bool -} - -type Error struct { - ExitStatus int - Message string -} - var logger *slog.Logger -func New(path string) *Iproute2 { - return &Iproute2{ - path: path, - netns: "", - useNetns: false, - } -} - func SetLogger(l *slog.Logger) { logger = l } -func (i *Iproute2) AddLink(name string, linkType string, options ...string) error { - args := []string{"link", "add", "name", name, "type", linkType} - args = append(args, options...) - return i.execute(args...) -} - -func (i *Iproute2) DelLink(name string) error { - return i.execute("link", "del", "name", name) -} - -func (i *Iproute2) AddDummyDevice(name string) error { - return i.AddLink(name, "dummy") -} - -func (i *Iproute2) AddVethDevice(name string, peerName string) error { - return i.AddLink(name, "veth", "peer", "name", peerName) -} - -func (i *Iproute2) SetLinkUp(name string) error { - return i.execute("link", "set", "dev", name, "up") -} - -func (i *Iproute2) AddAddress(name string, address string) error { - return i.execute("address", "add", address, "dev", name) -} - -func (i *Iproute2) DelAddress(name string, address string) error { - return i.execute("address", "del", address, "dev", name) -} - -func (i *Iproute2) AddRoute(name string, to string, via string) error { - return i.execute("route", "add", to, "via", via, "dev", name) +type IpCmd struct { + BaseCommand } -func (i *Iproute2) DelRoute(name string, to string, via string) error { - return i.execute("route", "del", to, "via", via, "dev", name) +func New(path string) *IpCmd { + return &IpCmd{ + BaseCommand: BaseCommand{path: path}, + } } -func (i *Iproute2) AddNetns(name string) error { +func (i *IpCmd) AddNetns(name string) error { return i.execute("netns", "add", name) } -func (i *Iproute2) DelNetns(name string) error { - pids, err := i.NetnsPid(name) +func (i *IpCmd) DelNetns(name string) error { + pids, err := i.ListNetnsProcesses(name) if err != nil { return err } @@ -116,8 +66,8 @@ func (i *Iproute2) DelNetns(name string) error { return i.execute("netns", "del", name) } -func (i *Iproute2) ListNetns() []string { - data, _ := i.executeWithStdout("netns", "list") +func (i *IpCmd) ListNetns() []string { + data, _ := i.executeWithOutput("netns", "list") var netns []string for _, line := range strings.Split(data, "\n") { @@ -128,30 +78,12 @@ func (i *Iproute2) ListNetns() []string { return netns } -func (i *Iproute2) SetNetns(name string, netns string) error { +func (i *IpCmd) SetNetns(name string, netns string) error { return i.execute("link", "set", name, "netns", netns) } -func (i *Iproute2) IntoNetns(netns string, fn func() error) error { - i.netns = netns - i.useNetns = true - - err := fn() - - i.netns = "" - i.useNetns = false - return err -} - -func (i *Iproute2) Netns() string { - if i.useNetns { - return i.netns - } - return "" -} - -func (i *Iproute2) NetnsPid(netns string) ([]int, error) { - out, err := i.executeWithStdout("netns", "pids", netns) +func (i *IpCmd) ListNetnsProcesses(netns string) ([]int, error) { + out, err := i.executeWithOutput("netns", "pids", netns) if err != nil { return nil, err } @@ -172,47 +104,38 @@ func (i *Iproute2) NetnsPid(netns string) ([]int, error) { return pids, nil } -func (i *Iproute2) NetnsExists(name string) bool { +func (i *IpCmd) ExistsNetns(name string) bool { return slices.Contains(i.ListNetns(), name) } -func (e *Error) Error() string { - msg := strings.TrimRight(e.Message, "\n") - return fmt.Sprintf("%s (exit status: %d)", msg, e.ExitStatus) -} - -func (i *Iproute2) execute(args ...string) error { - _, err := i.executeWithStdout(args...) - return err -} - -func (i *Iproute2) executeWithStdout(args ...string) (string, error) { - var cmdArgs []string - - if i.useNetns { - cmdArgs = append(cmdArgs, "netns", "exec", i.netns) - cmdArgs = append(cmdArgs, i.path) +func (i *IpCmd) IntoNetns(netns string) *IpCmdWithNetns { + if netns == "" { + return nil } - cmdArgs = append(cmdArgs, args...) - if logger != nil { - logger.Debug("exec command", "path", i.path, "args", strings.Join(cmdArgs, " ")) + ip := IpCmdWithNetns{ + netns: netns, + BaseCommand: BaseCommand{ + path: i.path, + prependArgs: []string{"netns", "exec", netns, i.path}, + }, } + return &ip +} - cmd := exec.Command(i.path, cmdArgs...) - var stdoutBuf, stderrBuf bytes.Buffer - cmd.Stdout = &stdoutBuf - cmd.Stderr = &stderrBuf - err := cmd.Run() +type IpCmdWithNetns struct { + netns string + BaseCommand +} - if err != nil { - exitErr, _ := err.(*exec.ExitError) - status, _ := exitErr.Sys().(syscall.WaitStatus) - return "", &Error{ - ExitStatus: status.ExitStatus(), - Message: stderrBuf.String(), - } - } +func (i *IpCmdWithNetns) InNetns() bool { + return true +} + +func (i *IpCmdWithNetns) Netns() string { + return i.netns +} - return stdoutBuf.String(), nil +func (i *IpCmdWithNetns) ExecuteCommand(args ...string) (string, error) { + return i.executeWithOutput(args...) } diff --git a/iproute2/links.go b/iproute2/links.go deleted file mode 100644 index deaec11..0000000 --- a/iproute2/links.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright © 2024 buty4649 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -package iproute2 - -import "encoding/json" - -type Link struct { - Ifindex int `json:"ifindex"` - Ifname string `json:"ifname"` - Flags []string `json:"flags"` - Mtu int `json:"mtu"` - Qdisc string `json:"qdisc"` - Operstate string `json:"operstate"` - Linkmode string `json:"linkmode"` - Group string `json:"group"` - Txqlen int `json:"txqlen"` - LinkType string `json:"link_type"` - Address string `json:"address"` - Broadcast string `json:"broadcast"` -} - -type Links []Link - -func (i *Iproute2) ListLinks() (*Links, error) { - data, err := i.executeWithStdout("-json", "link", "show") - if err != nil { - return nil, err - } - - var links Links - err = json.Unmarshal([]byte(data), &links) - if err != nil { - return nil, err - } - - return &links, nil -}