brief introduction
In the previous article, we completed an IP management and allocation tool class, which can automatically allocate and recycle the IP of the subnet. This article will continue to the previous article and start the creation of the network
Source code description
At the same time, it is put on Gitee and Github, which can be obtained
The version label corresponding to this chapter is: 6.2. To prevent too many codes from being difficult to view, you can switch to the label version for viewing
code implementation
The main ideas are as follows:
- Create a network: read the subnet entered by the user, how to take the first as the gateway ip, and create a virtual network (or network card); After the creation is successful, the network information is serialized into JSON and saved locally for subsequent reading
- Delete network: delete the corresponding network according to the entered virtual network name
The following is the specific implementation:
network command added
First, add relevant command operations:
Add network command operation
func main() { ...... app.Commands = []cli.Command{ command.InitCommand, command.RunCommand, command.CommitCommand, command.ListCommand, command.LogCommand, command.ExecCommand, command.StopCommand, command.RemoveCommand, command.NetworkCommand, } ...... }
network related detailed commands
var NetworkCommand = cli.Command{ Name: "network", Usage: "container network commands", Subcommands: []cli.Command{ { Name: "create", Usage: "create a container network", Flags: []cli.Flag{ cli.StringFlag{ Name: "driver", Usage: "network driver", }, cli.StringFlag{ Name: "subnet", Usage: "subnet cidr", }, }, Action: func(context *cli.Context) error { if len(context.Args()) < 1 { return fmt.Errorf("missing network name") } err := network.Init() if err != nil { return err } driver := context.String("driver") subnet := context.String("subnet") name := context.Args()[0] if err := network.CreateNetwork(driver, subnet, name); err != nil { return err } return nil }, }, { Name: "list", Usage: "list container network", Action: func(context *cli.Context) error { if err := network.Init(); err != nil { return err } return network.ListNetwork() }, }, { Name: "remove", Usage: "remove container network", Action: func(context *cli.Context) error { if len(context.Args()) < 1 { return fmt.Errorf("missing network name") } if err := network.Init(); err != nil { return err } if err := network.DeleteNetwork(context.Args()[0]); err != nil { return err } return nil }, }, }, }
As above, the operations of creating new network, viewing network list and deleting network are added
Create a new network
When the command starts, pass in the subnet and virtual network name, and we create the virtual network according to these
func CreateNetwork(driver, subnet, name string) error { nw, err := drivers[driver].Create(subnet, name) if err != nil { return err } log.Infof("create network success") return nw.dump(defaultNetworkPath) }
The following Network information: Network, some minor modifications have been made, GatewayIP (gateway IP) and Subnet (Subnet segment information) have been added, and they have been saved. Because the IpRange field is used, our IP recycling operation cannot be used normally
func (b *BridgeNetworkDriver) Create(subnet string, name string) (*NetWork, error) { _, ipRange, _ := net.ParseCIDR(subnet) // Get the first IP of the network segment as the gateway IP ip, err := ipAllocator.Allocate(ipRange) if err != nil { return nil, err } ipRange.IP = ip n := &NetWork{ Name: name, IpRange: ipRange, Driver: b.Name(), GatewayIP: ip, Subnet: subnet, } log.Infof("BridgeNetworkDriver creat network subnet: %s, gateway ip: %s", ipRange.String(), ip.String()) // Initialize virtual network return n, b.initBridge(n) }
The following is the specific virtual network creation, which basically uses system functions (bloggers don't know much about this at present, so they can't say anything. Copy it first...)
func (b *BridgeNetworkDriver) initBridge(n *NetWork) error { // try to get bridge by name, if it already exists then just exit bridgeName := n.Name if err := createBridgeInterface(bridgeName); err != nil { return err } log.Infof("createBridgeInterface success") // set bridge ip gatewayIp := *n.IpRange gatewayIp.IP = n.IpRange.IP if err := setInterfaceIp(bridgeName, gatewayIp.String()); err != nil { return err } log.Infof("setInterfaceIp success") if err := setInterfaceUp(bridgeName); err != nil { return err } log.Infof("crsetInterfaceUp success") if err := setupIpTables(bridgeName, n.IpRange); err != nil { return err } log.Infof("setInterfaceUp success") return nil }
func createBridgeInterface(bridgeName string) error { _, err := net.InterfaceByName(bridgeName) if err == nil || !strings.Contains(err.Error(), "no such network interface") { return err } // create *netlink.Bridge object la := netlink.NewLinkAttrs() la.Name = bridgeName br := &netlink.Bridge{LinkAttrs: la} if err := netlink.LinkAdd(br); err != nil { return fmt.Errorf("bridge creating failed for bridge %s, err: %w", bridgeName, err) } return nil } func setInterfaceUp(interfaceName string) error { iface, err := netlink.LinkByName(interfaceName) if err != nil { return fmt.Errorf("error retrieving a link named [ %s ], err: %w", iface.Attrs().Name, err) } if err := netlink.LinkSetUp(iface); err != nil { return fmt.Errorf("error enabling interface for %s, err: %w", interfaceName, err) } return nil } func setInterfaceIp(name string, rawIp string) error { retries := 2 var iface netlink.Link var err error for i := 0; i < retries; i++ { iface, err = netlink.LinkByName(name) if err == nil { break } log.Debugf("error retrieving new bridge netlink link [ %s ]... retrying", name) time.Sleep(2 * time.Second) } if err != nil { return fmt.Errorf("abandoning retrieving the new bridge ink from netlink, run [ip link] to troubleshoot the err: %w", err) } ipNet, err := netlink.ParseIPNet(rawIp) if err != nil { return fmt.Errorf("netlink.ParseIPNet err: %w", err) } addr := &netlink.Addr{ IPNet: ipNet, Peer: ipNet, Label: "", Flags: 0, Scope: 0, Broadcast: nil, } return netlink.AddrAdd(iface, addr) } func setupIpTables(bridgeName string, subnet *net.IPNet) error { iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", subnet.String(), bridgeName) cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...) output, err := cmd.Output() if err != nil { return fmt.Errorf("iptablse err %v, %w", output, err) } return nil }
The above is the code related to the creation of virtual network
View existing virtual networks
When viewing, you need to initialize the network configuration information and Init function
We have written a fixed location of network configuration information. All network configuration files will be stored below with their names, and the network driver and configuration will also be loaded into memory
var ( // Default storage location of network configuration defaultNetworkPath = "/var/run/mydocker/network/network/" // Network drivers for different networks drivers = map[string]NetworkDriver{} // Network configuration of different networks networks = map[string]*NetWork{} )
The basic principle of network information initialization is to read the default storage folder of network configuration and load all network configurations:
func Init() error { var bridgeDriver = BridgeNetworkDriver{} drivers[bridgeDriver.Name()] = &bridgeDriver if _, err := os.Stat(defaultNetworkPath); err != nil { if os.IsNotExist(err) { _ = os.MkdirAll(defaultNetworkPath, 0644) } else { return err } } _ = filepath.Walk(defaultNetworkPath, func(nwPath string, info os.FileInfo, err error) error { if strings.HasSuffix(nwPath, "/") { return nil } _, nwName := path.Split(nwPath) nw := &NetWork{ Name: nwName, } if err := nw.load(nwPath); err != nil { log.Errorf("error load network: %v", err) } networks[nwName] = nw return nil }) return nil }
It's easy to view the network configuration. Just traverse and print:
func ListNetwork() error { w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0) _, _ = fmt.Fprint(w, "NAME\tIpRange\tDriver\n") for _, nw := range networks { fmt.Fprintf(w, "%s\t%s\t\t%s\n", nw.Name, nw.IpRange.String(), nw.Driver, ) } if err := w.Flush(); err != nil { return fmt.Errorf("flush error: %w", err) } return nil }
Delete network
Deleting a network is to delete the relevant virtual network and configuration file
func DeleteNetwork(networkName string) error { nw, ok := networks[networkName] if !ok { return fmt.Errorf("no such network: %s", networkName) } // Release gateway IP _, ipNet, _ := net.ParseCIDR(nw.Subnet) if err := ipAllocator.Release(ipNet, &nw.GatewayIP); err != nil { return fmt.Errorf("remove network gateway ip err: %w", err) } // Delete virtual network if err := drivers[nw.Driver].Delete(*nw); err != nil { return fmt.Errorf("remove network driver err: %w", err) } // Delete profile return nw.remove(defaultNetworkPath) }
The network configuration can be deleted by using the system function
func (b *BridgeNetworkDriver) Delete(network NetWork) error { bridgeName := network.Name br, err := netlink.LinkByName(bridgeName) if err != nil { return err } return netlink.LinkDel(br) }
Deletion of network profile:
func (nw *NetWork) remove(dumpPath string) error { if _, err := os.Stat(path.Join(dumpPath, nw.Name)); err != nil { if os.IsNotExist(err) { return nil } else { return fmt.Errorf("remvove path err: %w", err) } } else { return os.Remove(path.Join(dumpPath, nw.Name)) } }
Run test
Create and view the corresponding network as shown in the following command
- Compiler, create network
- View network conditions
- Delete network and confirm
root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo go build mydocker/main.go ✔ ⚡ 651 11:00:01 root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ./main network create --driver bridge --subnet 192.168.10.1/24 testbridge ✔ ⚡ 652 11:00:06 {"level":"info","msg":"load ipam file from: /var/run/mydocker/network/ipam/subnet.json","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"allocate subnet: 192.168.10.1/24, ip: 192.168.10.1","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"dump ipam file from: /var/run/mydocker/network/ipam/","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"BridgeNetworkDriver creat network subnet: 192.168.10.1/24, gateway ip: 192.168.10.1","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"createBridgeInterface success","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"setInterfaceIp success","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"crsetInterfaceUp success","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"setInterfaceUp success","time":"2022-04-16T11:00:11+08:00"} {"level":"info","msg":"create network success","time":"2022-04-16T11:00:11+08:00"} root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ./main network list ✔ ⚡ 653 11:00:16 NAME IpRange Driver testbridge 192.168.10.1/24 bridge root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ip link show dev testbridge ✔ ⚡ 654 11:00:19 7: testbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ether 6a:a8:a1:a4:74:b4 brd ff:ff:ff:ff:ff:ff root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ip addr show dev testbridge ✔ ⚡ 655 11:00:24 7: testbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether 6a:a8:a1:a4:74:b4 brd ff:ff:ff:ff:ff:ff inet 192.168.10.1/24 brd 192.168.10.255 scope global testbridge valid_lft forever preferred_lft forever inet6 fe80::68a8:a1ff:fea4:74b4/64 scope link valid_lft forever preferred_lft forever root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo iptables -t nat -vnL POSTROUTING ✔ ⚡ 656 11:00:29 Chain POSTROUTING (policy ACCEPT 44 packets, 4188 bytes) pkts bytes target prot opt in out source destination 0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0 0 0 MASQUERADE all -- * !testbridge 192.168.10.0/24 0.0.0.0/0 0 0 MASQUERADE all -- * !testbridge 192.168.10.0/24 0.0.0.0/0 0 0 MASQUERADE all -- * !testbridge 192.168.10.0/24 0.0.0.0/0 0 0 MASQUERADE all -- * !testbridge 192.168.10.0/24 0.0.0.0/0 0 0 MASQUERADE all -- * !testbridge 192.168.10.0/24 0.0.0.0/0 root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ./main network remove testbridge ✔ ⚡ 657 11:00:35 {"level":"info","msg":"release subnet: 192.168.10.0/24, ip: 192.168.10.1","time":"2022-04-16T11:00:40+08:00"} {"level":"info","msg":"load ipam file from: /var/run/mydocker/network/ipam/subnet.json","time":"2022-04-16T11:00:40+08:00"} {"level":"info","msg":"release index: 0","time":"2022-04-16T11:00:40+08:00"} {"level":"info","msg":"dump ipam file from: /var/run/mydocker/network/ipam/","time":"2022-04-16T11:00:40+08:00"} root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ./main network list ✔ ⚡ 658 11:00:42 NAME IpRange Driver root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ip addr show dev testbridge ✔ ⚡ 659 11:00:47 Device "testbridge" does not exist.
Overall, it feels like creating a bridge network card? And when using VMware, a virtual network card will be generated. If you use the following command to view it when it is not deleted, you will get
root@lw-Code-01-Series-PF5NU1G ~/code/go/dockerDemo ifconfig ✔ ⚡ 661 11:04:37 testbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255 inet6 fe80::fc2b:95ff:fe8d:5c98 prefixlen 64 scopeid 0x20<link> ether fe:2b:95:8d:5c:98 txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 33 bytes 4700 (4.7 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Generally speaking, the code in the book is a little messy. It can't be copied down and run. It's mainly the problem of ip distribution and release. Therefore, some small modifications have been made in this article to initially meet the expectations