diff --git a/0007-support-ipv6.patch b/0007-support-ipv6.patch new file mode 100644 index 0000000..64af5ad --- /dev/null +++ b/0007-support-ipv6.patch @@ -0,0 +1,2085 @@ +From 9acfe3e5c2889c511d28447f1660911c4ce17cc5 Mon Sep 17 00:00:00 2001 +From: vegbir +Date: Thu, 10 Aug 2023 02:44:07 +0000 +Subject: [PATCH] support ipv6 + +Signed-off-by: vegbir +--- + Makefile | 2 +- + config/config_network.go | 3 +- + libnetwork/drivers/common/driver.go | 11 + + libnetwork/drivers/common/driver_test.go | 68 +++ + libnetwork/drivers/driver.go | 30 ++ + libnetwork/drivers/driver_test.go | 147 +++++++ + libnetwork/drivers/eth/driver.go | 59 ++- + libnetwork/drivers/eth/driver_test.go | 397 +++++++++++++++++ + libnetwork/interfaces.go | 37 +- + libnetwork/interfaces_test.go | 255 +++++++++++ + libnetwork/route.go | 12 - + libnetwork/route_test.go | 143 +++++++ + network.go | 14 +- + types/network.go | 49 ++- + types/network_test.go | 514 +++++++++++++++++++++++ + 15 files changed, 1699 insertions(+), 42 deletions(-) + create mode 100644 libnetwork/drivers/common/driver_test.go + create mode 100644 libnetwork/drivers/driver_test.go + create mode 100644 libnetwork/drivers/eth/driver_test.go + create mode 100644 libnetwork/interfaces_test.go + create mode 100644 libnetwork/route_test.go + create mode 100644 types/network_test.go + +diff --git a/Makefile b/Makefile +index 556382c..ede386b 100644 +--- a/Makefile ++++ b/Makefile +@@ -48,7 +48,7 @@ syscontainer-hooks: $(SOURCES) | $(DEPS_LINK) + @echo "Done!" + + localtest: +- ${ENV} go test -mod=vendor -tags ${TAGS} -ldflags ${GO_LDFLAGS} -v ./... ++ go test -mod=vendor -v ./... + + clean: + rm -rf build +diff --git a/config/config_network.go b/config/config_network.go +index 4e0da46..1bb9c7f 100644 +--- a/config/config_network.go ++++ b/config/config_network.go +@@ -15,8 +15,9 @@ package config + + import ( + "fmt" +- "isula.org/syscontainer-tools/types" + "path/filepath" ++ ++ "isula.org/syscontainer-tools/types" + ) + + var ( +diff --git a/libnetwork/drivers/common/driver.go b/libnetwork/drivers/common/driver.go +index c2dadd7..a2158a1 100644 +--- a/libnetwork/drivers/common/driver.go ++++ b/libnetwork/drivers/common/driver.go +@@ -27,6 +27,7 @@ type Driver struct { + hostName string + mac *net.HardwareAddr + ip *net.IPNet ++ ip6 *net.IPNet + bridge string + bridgeDriver api.BridgeDriver + mtu int +@@ -73,6 +74,16 @@ func (d *Driver) GetIP() *net.IPNet { + return d.ip + } + ++// SetIP6 will set the network interface ip6 ++func (d *Driver) SetIP6(addr *net.IPNet) { ++ d.ip6 = addr ++} ++ ++// GetIP6 will get the network interface ip6 ++func (d *Driver) GetIP6() *net.IPNet { ++ return d.ip6 ++} ++ + // SetMac will set the network interface mac + func (d *Driver) SetMac(mac *net.HardwareAddr) { + d.mac = mac +diff --git a/libnetwork/drivers/common/driver_test.go b/libnetwork/drivers/common/driver_test.go +new file mode 100644 +index 0000000..7247232 +--- /dev/null ++++ b/libnetwork/drivers/common/driver_test.go +@@ -0,0 +1,68 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. ++// syscontainer-tools licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Author: Jiaqi Yang ++// Date: 2023-05-03 ++// Description: This file is used for test common.driver package ++ ++// package common is common network driver implementation ++package common ++ ++import ( ++ "net" ++ "testing" ++) ++ ++// TestDriver_GetAndSet tests setter & getter methods ++func TestDriver_GetAndSet(t *testing.T) { ++ type fields struct { ++ nsPath string ++ ctrName string ++ hostName string ++ mac *net.HardwareAddr ++ ip *net.IPNet ++ ip6 *net.IPNet ++ bridge string ++ mtu int ++ qlen int ++ } ++ tests := []struct { ++ name string ++ fields fields ++ }{ ++ { ++ name: "TC1-sety & get sucessfully", ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := &Driver{} ++ d.SetIP6(tt.fields.ip6) ++ d.SetIP(tt.fields.ip) ++ d.SetBridge(tt.fields.bridge) ++ d.SetCtrNicName(tt.fields.ctrName) ++ d.SetHostNicName(tt.fields.hostName) ++ d.SetMac(tt.fields.mac) ++ d.SetMtu(tt.fields.mtu) ++ d.SetQlen(tt.fields.qlen) ++ d.SetNsPath(tt.fields.nsPath) ++ ++ d.GetBridge() ++ d.GetCtrNicName() ++ d.GetHostNicName() ++ d.GetIP() ++ d.GetIP6() ++ d.GetMac() ++ d.GetMtu() ++ d.GetQlen() ++ d.GetNsPath() ++ d.GetBridgeDriver() ++ }) ++ } ++} +diff --git a/libnetwork/drivers/driver.go b/libnetwork/drivers/driver.go +index a87831d..86cac7a 100644 +--- a/libnetwork/drivers/driver.go ++++ b/libnetwork/drivers/driver.go +@@ -111,16 +111,46 @@ func NicOptionHostNicName(name string) DriverOptions { + func NicOptionIP(ip string) DriverOptions { + return func(d *common.Driver) error { + ip = strings.TrimSpace(ip) ++ if len(ip) == 0 { ++ return nil ++ } ++ + ipnet, err := netlink.ParseIPNet(ip) + if err != nil { + return err + } ++ if ipnet.IP.To4() == nil { ++ // fail to get ip4 ++ return fmt.Errorf("ip only accepts CIDR data in ipv4 format, not %v", ipnet.String()) ++ } + + d.SetIP(ipnet) + return nil + } + } + ++// NicOptionIP6 handles network interface ip6 option ++func NicOptionIP6(ip6 string) DriverOptions { ++ return func(d *common.Driver) error { ++ ip6 = strings.TrimSpace(ip6) ++ if len(ip6) == 0 { ++ return nil ++ } ++ ++ ipnet6, err := netlink.ParseIPNet(ip6) ++ if err != nil { ++ return err ++ } ++ if ipnet6.IP.To4() != nil { ++ // can get ip4 ++ return fmt.Errorf("ip6 only accepts CIDR data in ipv6 format, not %v", ipnet6.String()) ++ } ++ ++ d.SetIP6(ipnet6) ++ return nil ++ } ++} ++ + // NicOptionMac handles network interface mac option + func NicOptionMac(mac string) DriverOptions { + return func(d *common.Driver) error { +diff --git a/libnetwork/drivers/driver_test.go b/libnetwork/drivers/driver_test.go +new file mode 100644 +index 0000000..5640130 +--- /dev/null ++++ b/libnetwork/drivers/driver_test.go +@@ -0,0 +1,147 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface driver ++// Author: Jiaqi Yang ++// Create: 2023-04-04 ++ ++package drivers ++ ++import ( ++ "fmt" ++ "testing" ++ ++ "isula.org/syscontainer-tools/types" ++) ++ ++// TestNew tests New ++func TestNew(t *testing.T) { ++ const ( ++ ctrNicName = "eth0" ++ hostNicName = "eth0" ++ nsPath = "/proc/1/ns/net" ++ ip = types.CIDRIpExample1 ++ ip6 = types.CIDRIp6Example1 ++ mac = "aa:bb:cc:dd:ee:aa" ++ mtu = 1500 ++ qlen = 1000 ++ bridge = "" ++ ) ++ type args struct { ++ driverType string ++ options []DriverOptions ++ } ++ tests := []struct { ++ name string ++ args args ++ want Driver ++ wantErr bool ++ }{ ++ { ++ name: "TC1-new eth driver success", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(ip), ++ NicOptionIP6(ip6), ++ NicOptionMac(mac), ++ NicOptionMtu(mtu), ++ NicOptionQlen(qlen), ++ NicOptionBridge(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-ip6 is not CIDR address", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP6(mac), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-ip6 is empty", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP6(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4-ip is not CIDR address", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(mac), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-ip is empty", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC6.1-set ip6 to ip", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionIP(ip6), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC6.2-set ip to ip6", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionIP6(ip), ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ _, err := New(tt.args.driverType, tt.args.options...) ++ fmt.Printf("%v\n", err) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ }) ++ } ++} +diff --git a/libnetwork/drivers/eth/driver.go b/libnetwork/drivers/eth/driver.go +index cd70ddf..901ec9c 100644 +--- a/libnetwork/drivers/eth/driver.go ++++ b/libnetwork/drivers/eth/driver.go +@@ -15,6 +15,7 @@ package eth + + import ( + "fmt" ++ "net" + "os" + "strings" + +@@ -178,6 +179,44 @@ func (d *ethDriver) DeleteIf() (rErr error) { + return err + } + ++func (d *ethDriver) setNicIP(nic netlink.Link, ipType int) error { ++ if nic == nil { ++ return fmt.Errorf("nic is nil") ++ } ++ var ( ++ ipNet *net.IPNet ++ typ string ++ ) ++ switch ipType { ++ case netlink.FAMILY_V4: ++ ipNet = d.GetIP() ++ typ = "ip" ++ case netlink.FAMILY_V6: ++ ipNet = d.GetIP6() ++ typ = "ip6" ++ default: ++ return fmt.Errorf("unsupported IP type") ++ } ++ ++ // delete original ip/ip6 address ++ oldAddr, err := netlink.AddrList(nic, ipType) ++ if err != nil { ++ logrus.Infof("Fail to get origin %v addr: %v", typ, err) ++ } ++ if len(oldAddr) > 0 { ++ // we only have an IP set for the interface ++ if err := netlink.AddrDel(nic, &oldAddr[0]); err != nil { ++ return fmt.Errorf("failed to delete old %v address %v: %v", typ, oldAddr[0].IP, err) ++ } ++ } ++ // set new ipv4/ipv6 address ++ ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""} ++ if err := netlink.AddrAdd(nic, ipAddr); err != nil { ++ return fmt.Errorf("failed to configure %v address %v: %v", typ, ipAddr, err) ++ } ++ return nil ++} ++ + func (d *ethDriver) setNicConfigure(nic netlink.Link) (rErr error) { + // set MAC + if d.GetMac() != nil { +@@ -197,18 +236,18 @@ func (d *ethDriver) setNicConfigure(nic netlink.Link) (rErr error) { + return fmt.Errorf("failed to set qlen(%d) for nic(%s)", d.GetQlen(), d.GetCtrNicName()) + } + +- // set ipv4 address (TODO: ipv6 support?) +- oldAddr, _ := netlink.AddrList(nic, netlink.FAMILY_V4) +- if oldAddr != nil { +- // we only have on IP set for the interface +- if err := netlink.AddrDel(nic, &oldAddr[0]); err != nil { +- return fmt.Errorf("failed to delete old ip address: %v", err) ++ // set ipv4 ++ if d.GetIP() != nil { ++ if err := d.setNicIP(nic, netlink.FAMILY_V4); err != nil { ++ return err + } + } +- // set ipv4 address (TODO: ipv6 support?) +- ipAddr := &netlink.Addr{IPNet: d.GetIP(), Label: ""} +- if err := netlink.AddrAdd(nic, ipAddr); err != nil { +- return fmt.Errorf("failed to configure ip address: %v", err) ++ ++ // set ipv6 ++ if d.GetIP6() != nil { ++ if err := d.setNicIP(nic, netlink.FAMILY_V6); err != nil { ++ return err ++ } + } + + return nil +diff --git a/libnetwork/drivers/eth/driver_test.go b/libnetwork/drivers/eth/driver_test.go +new file mode 100644 +index 0000000..9596a9a +--- /dev/null ++++ b/libnetwork/drivers/eth/driver_test.go +@@ -0,0 +1,397 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: ethetic network driver ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++package eth ++ ++import ( ++ "fmt" ++ "io/ioutil" ++ "net" ++ "os" ++ "testing" ++ ++ "github.com/vishvananda/netlink" ++ ++ "isula.org/syscontainer-tools/libnetwork/drivers/common" ++ "isula.org/syscontainer-tools/types" ++) ++ ++func getPhysicalNics() (map[string]struct{}, error) { ++ const ( ++ allNicDir = "/sys/class/net" ++ virtualNicDir = "/sys/devices/virtual/net" ++ ) ++ getFileName := func(dirName string) (map[string]struct{}, error) { ++ fis, err := ioutil.ReadDir(dirName) ++ if err != nil { ++ return nil, err ++ } ++ var fileNames = make(map[string]struct{}) ++ for _, fi := range fis { ++ fileNames[fi.Name()] = struct{}{} ++ } ++ return fileNames, nil ++ } ++ allNics, err := getFileName(allNicDir) ++ if err != nil { ++ return nil, err ++ } ++ virtualNics, err := getFileName(virtualNicDir) ++ if err != nil { ++ return nil, err ++ } ++ var res = make(map[string]struct{}) ++ for name := range allNics { ++ if _, ok := virtualNics[name]; !ok { ++ res[name] = struct{}{} ++ } ++ } ++ return res, nil ++} ++ ++func getUnusedNic() (string, error) { ++ nicNames, err := getPhysicalNics() ++ if err != nil { ++ return "", err ++ } ++ inters, err := net.Interfaces() ++ if err != nil { ++ return "", err ++ } ++ for _, i := range inters { ++ if _, ok := nicNames[i.Name]; !ok { ++ continue ++ } ++ addrs, err := i.Addrs() ++ if err != nil { ++ fmt.Printf("%v can not get its addr: %v\n", i.Name, err) ++ continue ++ } ++ if len(addrs) > 0 { ++ continue ++ } ++ return i.Name, nil ++ } ++ return "", fmt.Errorf("can not find any unused card") ++} ++ ++func getIPAddr(l netlink.Link, t int) (string, error) { ++ var typ string = "ip4" ++ if t == netlink.FAMILY_V6 { ++ typ = "ip6" ++ } ++ addrs, err := netlink.AddrList(l, t) ++ if err != nil { ++ return "", fmt.Errorf("fail to get %v: %v", typ, err) ++ } ++ if len(addrs) > 0 { ++ if typ == "ip6" { ++ return addrs[0].IP.To16().String(), nil ++ } ++ return addrs[0].IP.To4().String(), nil ++ } ++ return "", fmt.Errorf("empty %v", typ) ++} ++ ++func getIp6(l netlink.Link) (string, error) { ++ return getIPAddr(l, netlink.FAMILY_V6) ++} ++ ++func getIp(l netlink.Link) (string, error) { ++ return getIPAddr(l, netlink.FAMILY_V4) ++} ++ ++func removeAllAddrOfNic(nicName string) { ++ rmNic := func(ip string, nic netlink.Link) { ++ ipNet, err := netlink.ParseIPNet(ip) ++ if err != nil { ++ fmt.Printf("fail to create ip: %v\n", err) ++ return ++ } ++ ipaddr := &netlink.Addr{IPNet: ipNet} ++ err = netlink.AddrDel(nic, ipaddr) ++ if err != nil { ++ fmt.Printf("fail to del ip: %v\n", err) ++ } ++ } ++ ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ fmt.Printf("should find nic: %v", err) ++ return ++ } ++ ip, _ := getIp(nic) ++ if ip != "" { ++ rmNic(ip+"/16", nic) ++ } ++ ip6, _ := getIp6(nic) ++ if ip6 != "" { ++ rmNic(ip6+"/64", nic) ++ } ++} ++ ++func hasPerm() error { ++ filePath := "/proc/1/ns/net" ++ ++ // is existed ++ fileInfo, err := os.Stat(filePath) ++ if err != nil { ++ return err ++ } ++ ++ // get file info ++ fileMode := fileInfo.Mode() ++ ++ // Check if the current user has permission to access the file ++ if fileMode.Perm()&(1<<(uint(7))) == 0 { ++ return fmt.Errorf("no permission to access the file") ++ } ++ return nil ++} ++ ++// Test_ethDriver_setNicConfigure tests setNicConfigure of ethDriver ++func Test_ethDriver_setNicConfigure(t *testing.T) { ++ const ( ++ expIP = types.CIDRIpExample1 ++ expIP6 = types.CIDRIp6Example1 ++ expMTU = 1500 ++ expQlen = 1000 ++ invalidMtu = -1 ++ invalidMtu2 = 65536 ++ invalidIP = "xxx" ++ ) ++ ++ var ( ++ invalidBytes = []byte("ABCDEFG") ++ invalidMac = net.HardwareAddr(invalidBytes) ++ expIPNet, _ = netlink.ParseIPNet(expIP) ++ expIP6Net, _ = netlink.ParseIPNet(expIP6) ++ invalidIPNet = net.IPNet{IP: invalidBytes} ++ ) ++ if hasPerm() != nil { ++ return ++ } ++ ++ nicName, err := getUnusedNic() ++ if err != nil { ++ fmt.Printf("skip this tests: %v\n", err) ++ return ++ } ++ fmt.Printf("use nics: %v\n", nicName) ++ ++ type fields struct { ++ ip *net.IPNet ++ ip6 *net.IPNet ++ mtu int ++ qlen int ++ mac *net.HardwareAddr ++ } ++ tests := []struct { ++ name string ++ fields fields ++ wantErr bool ++ post func(t *testing.T, nic netlink.Link) ++ }{ ++ { ++ name: "TC1-success", ++ fields: fields{ ++ ip: expIPNet, ++ ip6: expIP6Net, ++ mtu: expMTU, ++ qlen: expQlen, ++ }, ++ post: func(t *testing.T, nic netlink.Link) { ++ ip, err := getIp(nic) ++ if err != nil { ++ t.Errorf("fail to get ip: %v", err) ++ } ++ ip6, err := getIp6(nic) ++ if err != nil { ++ t.Errorf("fail to get ip6: %v", err) ++ } ++ if ip+"/24" != expIP && ip6+"/64" != expIP6 { ++ t.Errorf("not same") ++ } ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-invalid mac addr", ++ fields: fields{ ++ mac: &invalidMac, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-invalid MTU", ++ fields: fields{ ++ mtu: invalidMtu, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.1-invalid MTU", ++ fields: fields{ ++ mtu: invalidMtu2, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4-invalid IP", ++ fields: fields{ ++ mtu: expMTU, ++ qlen: expQlen, ++ ip: &invalidIPNet, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-invalid IP6", ++ fields: fields{ ++ mtu: expMTU, ++ qlen: expQlen, ++ ip6: &invalidIPNet, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := ðDriver{ ++ Driver: &common.Driver{}, ++ } ++ if tt.fields.ip != nil { ++ d.SetIP(tt.fields.ip) ++ fmt.Printf("ip %v\n", tt.fields.ip) ++ } ++ if tt.fields.ip6 != nil { ++ d.SetIP6(tt.fields.ip6) ++ fmt.Printf("ip6 %v\n", tt.fields.ip6) ++ } ++ ++ d.SetMtu(tt.fields.mtu) ++ d.SetQlen(tt.fields.qlen) ++ d.SetMac(tt.fields.mac) ++ ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ t.Errorf("should find nic: %v", err) ++ return ++ } ++ defer removeAllAddrOfNic(nicName) ++ ++ if err := d.setNicConfigure(nic); (err != nil) != tt.wantErr { ++ t.Errorf("ethDriver.setNicConfigure() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ if tt.post != nil { ++ tt.post(t, nic) ++ } ++ }) ++ } ++} ++ ++// Test_ethDriver_setNicIP tests setNicIP ++func Test_ethDriver_setNicIP(t *testing.T) { ++ const ( ++ originIP = types.CIDRIpExample1 ++ curIP = types.CIDRIpExample2 ++ invalidTyp = 111111 ++ ) ++ var ( ++ originIPNet, _ = netlink.ParseIPNet(originIP) ++ curIPNet, _ = netlink.ParseIPNet(curIP) ++ ) ++ ++ if hasPerm() != nil { ++ return ++ } ++ ++ type fields struct { ++ ip *net.IPNet ++ } ++ type args struct { ++ nic netlink.Link ++ ipType int ++ } ++ ++ nicName, err := getUnusedNic() ++ if err != nil { ++ fmt.Printf("skip this tests: %v\n", err) ++ return ++ } ++ fmt.Printf("use nics: %v\n", nicName) ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ t.Errorf("should find nic: %v", err) ++ return ++ } ++ defer removeAllAddrOfNic(nicName) ++ ++ tests := []struct { ++ name string ++ args args ++ fields fields ++ pre func(t *testing.T) ++ wantErr bool ++ }{ ++ { ++ name: "TC1-invalid IPType", ++ args: args{ ++ ipType: invalidTyp, ++ nic: nic, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid nic", ++ args: args{ ++ ipType: netlink.FAMILY_V4, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-delete origin ip", ++ fields: fields{ ++ ip: curIPNet, ++ }, ++ args: args{ ++ ipType: netlink.FAMILY_V4, ++ nic: nic, ++ }, ++ pre: func(t *testing.T) { ++ ipAddr := &netlink.Addr{IPNet: originIPNet, Label: ""} ++ if err := netlink.AddrAdd(nic, ipAddr); err != nil { ++ t.Errorf("failed to configure ip4 address %v: %v", ipAddr, err) ++ } ++ }, ++ wantErr: false, ++ }, ++ } ++ ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := ðDriver{ ++ Driver: &common.Driver{}, ++ } ++ if tt.fields.ip != nil { ++ d.SetIP(tt.fields.ip) ++ } ++ if tt.pre != nil { ++ tt.pre(t) ++ } ++ if err := d.setNicIP(tt.args.nic, tt.args.ipType); (err != nil) != tt.wantErr { ++ t.Errorf("ethDriver.setNicIP() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +diff --git a/libnetwork/interfaces.go b/libnetwork/interfaces.go +index 46a51aa..121243a 100644 +--- a/libnetwork/interfaces.go ++++ b/libnetwork/interfaces.go +@@ -71,6 +71,7 @@ func AddNicToContainer(nsPath string, config *types.InterfaceConf) (rErr error) + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionQlen(config.Qlen), +@@ -94,6 +95,7 @@ func UpdateNicInContainer(nsPath string, config *types.InterfaceConf) (rErr erro + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionQlen(config.Qlen), +@@ -151,6 +153,7 @@ func DelNicFromContainer(nsPath string, config *types.InterfaceConf) error { + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionBridge(config.Bridge)) +@@ -176,21 +179,29 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + var tmpConfig = new(types.InterfaceConf) + tmpConfig.CtrNicName = config.CtrNicName + +- var newConfig *types.InterfaceConf +- if newConfig = hConfig.FindInterfaceByName(tmpConfig); newConfig == nil { ++ var oldConfig *types.InterfaceConf ++ if oldConfig = hConfig.FindInterfaceByName(tmpConfig); oldConfig == nil { + return fmt.Errorf("Network interface %s,%s with type %s not exist in container %s", config.HostNicName, config.CtrNicName, config.Type, ctr.Name()) + } + + if config.IP == "" { +- tmpConfig.IP = newConfig.IP ++ tmpConfig.IP = oldConfig.IP + } else { + tmpConfig.IP = config.IP + msg := fmt.Sprintf("Update IP address for network interface (%s,%v) done", config.CtrNicName, config.IP) + fmt.Fprintln(os.Stdout, msg) + logrus.Info(msg) + } ++ if config.IP6 == "" { ++ tmpConfig.IP6 = oldConfig.IP6 ++ } else { ++ tmpConfig.IP6 = config.IP6 ++ msg := fmt.Sprintf("Update IP6 address for network interface (%s,%v) done", config.CtrNicName, config.IP6) ++ fmt.Fprintln(os.Stdout, msg) ++ logrus.Info(msg) ++ } + if config.Mac == "" { +- tmpConfig.Mac = newConfig.Mac ++ tmpConfig.Mac = oldConfig.Mac + } else { + tmpConfig.Mac = config.Mac + msg := fmt.Sprintf("Update MAC address for network interface (%s,%v) done", config.CtrNicName, config.Mac) +@@ -199,7 +210,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + } + if config.Bridge == "" { +- tmpConfig.Bridge = newConfig.Bridge ++ tmpConfig.Bridge = oldConfig.Bridge + } else { + tmpConfig.Bridge = config.Bridge + msg := fmt.Sprintf("Update Bridge for network interface (%s,%v) done", config.CtrNicName, config.Bridge) +@@ -208,7 +219,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + } + if config.Mtu == 0 { +- tmpConfig.Mtu = newConfig.Mtu ++ tmpConfig.Mtu = oldConfig.Mtu + } else { + tmpConfig.Mtu = config.Mtu + msg := fmt.Sprintf("Update Mtu for network interface (%s,%v) done", config.CtrNicName, config.Mtu) +@@ -218,26 +229,28 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + } + // we use qlen < 0 to check if the user has set parameter qlen or not + if config.Qlen < 0 { +- tmpConfig.Qlen = newConfig.Qlen ++ tmpConfig.Qlen = oldConfig.Qlen + } else { + tmpConfig.Qlen = config.Qlen + msg := fmt.Sprintf("Update Qlen for network interface (%s,%v)", config.CtrNicName, config.Qlen) + fmt.Fprintln(os.Stdout, msg) + logrus.Info(msg) + } +- tmpConfig.Type = newConfig.Type +- tmpConfig.HostNicName = newConfig.HostNicName ++ tmpConfig.Type = oldConfig.Type ++ tmpConfig.HostNicName = oldConfig.HostNicName + + if hConfig.IsSameInterface(tmpConfig) { + logrus.Infof("Network interface in container (%s, %s): Identical setting, nothing to change", + config.CtrNicName, ctr.Name()) + return nil + } +- if err := hConfig.UpdateNetworkInterface(newConfig, false); err != nil { ++ ++ if err := hConfig.UpdateNetworkInterface(oldConfig, false); err != nil { + return err + } ++ + if err := hConfig.IsConflictInterface(tmpConfig); err != nil { +- if err := hConfig.UpdateNetworkInterface(newConfig, true); err != nil { ++ if err := hConfig.UpdateNetworkInterface(oldConfig, true); err != nil { + return err + } + return err +@@ -245,7 +258,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + if !updateConfigOnly && ctr.Pid() > 0 && ctr.CheckPidExist() { + if err := UpdateNicInContainer(ctr.NetNsPath(), tmpConfig); err != nil { +- if err := hConfig.UpdateNetworkInterface(newConfig, true); err != nil { ++ if err := hConfig.UpdateNetworkInterface(oldConfig, true); err != nil { + return err + } + return err +diff --git a/libnetwork/interfaces_test.go b/libnetwork/interfaces_test.go +new file mode 100644 +index 0000000..88b8e68 +--- /dev/null ++++ b/libnetwork/interfaces_test.go +@@ -0,0 +1,255 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface operation ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++// package libnetwork is network library ++package libnetwork ++ ++import ( ++ "fmt" ++ "os" ++ "os/exec" ++ "path/filepath" ++ "testing" ++ ++ "isula.org/syscontainer-tools/container" ++ "isula.org/syscontainer-tools/types" ++) ++ ++const ( ++ ctrNicName = "ctr" ++ hostNicName = "host" ++ ip6 = types.CIDRIp6Example1 ++ mtu = 1500 ++ procOneNsPath = "/proc/1/ns/net" ++ nonExistBridge = "test" ++) ++ ++// TestAddNicToContainer tests AddNicToContainer ++func TestAddNicToContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-fail to add nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid IP", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := AddNicToContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("AddNicToContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestUpdateNicInContainer test UpdateNicInContainer ++func TestUpdateNicInContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-fail to update nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := UpdateNicInContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("UpdateNicInContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestDelNicFromContainer tests DelNicFromContainer ++func TestDelNicFromContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-delete non-existed nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := DelNicFromContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("DelNicFromContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++const ctrName = "ctrName" ++ ++func createContainer() error { ++ const ( ++ imageName = "rnd-dockerhub.huawei.com/official/ubuntu" ++ ctrCmd = "bash" ++ ) ++ // cmdValue := "isula run -tid -name " + ctrName + " " + imageName + " " ++ cmd := exec.Command("isula", "run", "-tid", "--name", "ctrName", imageName, ctrCmd) ++ out, err := cmd.CombinedOutput() ++ if err != nil { ++ return fmt.Errorf("%s: %v", string(out), err) ++ } ++ return nil ++} ++ ++func removeContainer() { ++ cmd := exec.Command("bash", "-c", "isula rm -f `isula ps -aq`") ++ out, err := cmd.CombinedOutput() ++ if err != nil { ++ fmt.Printf("%s: %v\n", string(out), err) ++ } ++} ++ ++// TestUpdateNic tests UpdateNic ++func TestUpdateNic(t *testing.T) { ++ type args struct { ++ ctr *container.Container ++ config *types.InterfaceConf ++ updateConfigOnly bool ++ data string ++ } ++ if err := createContainer(); err != nil { ++ fmt.Printf("skip this usecase: %v\n", err) ++ return ++ } ++ ctr, err := container.New(ctrName) ++ if err != nil { ++ return ++ } ++ defer removeContainer() ++ ++ const ( ++ defaultConfigFile = "device_hook.json" ++ isuladPath = "/var/lib/isulad/engines/lcr" ++ filePerm = 0750 ++ invalidQlen = -1 ++ ip6 = "abc" ++ ) ++ var ( ++ data1 = "{\"networkInterfaces\":[{\"Ip\":\"\",\"Ip6\":\"\",\"Mac\":\"\",\"Mtu\":1,\"Qlen\":1," + ++ "\"Type\":\"eth\",\"Bridge\":\"\",\"HostNicName\":\"host\",\"CtrNicName\":\"ctr\"}]}" ++ ) ++ ctrID := ctr.ContainerID() ++ ++ if err := os.WriteFile(filepath.Join(isuladPath, ctrID, defaultConfigFile), []byte(data1), filePerm); err != nil { ++ fmt.Printf("write fail: %v\n", err) ++ return ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-same config", ++ args: args{ ++ ctr: ctr, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ Qlen: invalidQlen, ++ }, ++ data: data1, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-ip6 changed", ++ args: args{ ++ ctr: ctr, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ }, ++ data: data1, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := os.WriteFile(filepath.Join(isuladPath, ctrID, defaultConfigFile), []byte(tt.args.data), filePerm); err != nil { ++ fmt.Printf("write fail: %v\n", err) ++ return ++ } ++ if err := UpdateNic(tt.args.ctr, tt.args.config, tt.args.updateConfigOnly); (err != nil) != tt.wantErr { ++ t.Errorf("UpdateNic() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +diff --git a/libnetwork/route.go b/libnetwork/route.go +index 0be5a76..2b72c3e 100644 +--- a/libnetwork/route.go ++++ b/libnetwork/route.go +@@ -92,16 +92,10 @@ func AddRouteToContainer(nsPath string, route *types.Route) error { + + if len(src) != 0 { + rule.Src = net.ParseIP(src) +- if err != nil { +- return fmt.Errorf("failed to parse src ip") +- } + } + + if len(gw) != 0 { + rule.Gw = net.ParseIP(gw) +- if err != nil { +- return fmt.Errorf("failed to parse gw ip") +- } + } + + return nsutils.NsInvoke(nsPath, +@@ -195,16 +189,10 @@ func DelRouteFromContainer(nsPath string, route *types.Route) error { + + if len(src) != 0 { + rule.Src = net.ParseIP(src) +- if err != nil { +- return fmt.Errorf("failed to parse src ip") +- } + } + + if len(gw) != 0 { + rule.Gw = net.ParseIP(gw) +- if err != nil { +- return fmt.Errorf("failed to parse gw ip") +- } + } + + return nsutils.NsInvoke(nsPath, +diff --git a/libnetwork/route_test.go b/libnetwork/route_test.go +new file mode 100644 +index 0000000..2f8ab38 +--- /dev/null ++++ b/libnetwork/route_test.go +@@ -0,0 +1,143 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network routes operation ++// Author: zhangwei ++// Create: 2018-01-18 ++ ++// package libnetwork is network library ++package libnetwork ++ ++import ( ++ "fmt" ++ "os" ++ "testing" ++ ++ "isula.org/syscontainer-tools/types" ++) ++ ++func hasPerm() error { ++ filePath := "/proc/1/ns/net" ++ ++ // is existed ++ fileInfo, err := os.Stat(filePath) ++ if err != nil { ++ return err ++ } ++ ++ // get file info ++ fileMode := fileInfo.Mode() ++ ++ // Check if the current user has permission to access the file ++ if fileMode.Perm()&(1<<(uint(7))) == 0 { ++ return fmt.Errorf("no permission to access the file") ++ } ++ return nil ++} ++ ++// TestAddRouteToContainer tests AddRouteToContainer and DelRouteToContainer ++func TestAddDelRouteToContainer(t *testing.T) { ++ const ( ++ normalIP = types.IPExample1 ++ cidrIP = normalIP + "/16" ++ dev = "netDev" ++ ) ++ ++ if hasPerm() != nil { ++ return ++ } ++ ++ type args struct { ++ nsPath string ++ route *types.Route ++ } ++ tests := []struct { ++ name string ++ args args ++ addWantErr bool ++ delWantErr bool ++ }{ ++ { ++ name: "TC1.1-fail to parse invalaid src", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Src: cidrIP, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.2-fail to parse invalaid gw", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Gw: cidrIP, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.3-fail to parse invalid dest", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dest: normalIP, ++ Dev: dev, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.4-lack of dev, gw & src", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{}, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.5-non existed dev", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dev: dev, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC2-default dest", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dest: "default", ++ Gw: normalIP, ++ }, ++ }, ++ addWantErr: false, ++ delWantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := AddRouteToContainer(tt.args.nsPath, tt.args.route); (err != nil) != tt.addWantErr { ++ t.Errorf("AddRouteToContainer() error = %v, wantErr %v", err, tt.addWantErr) ++ } ++ if err := DelRouteFromContainer(tt.args.nsPath, tt.args.route); (err != nil) != tt.delWantErr { ++ t.Errorf("DelRouteFromContainer() error = %v, wantErr %v", err, tt.addWantErr) ++ } ++ }) ++ } ++} +diff --git a/network.go b/network.go +index e8eaa68..6e32dc4 100644 +--- a/network.go ++++ b/network.go +@@ -47,7 +47,11 @@ and configure it as you wanted, then attach to specified bridge. + }, + cli.StringFlag{ + Name: "ip", +- Usage: "set ip address. E.g. 172.17.28.2/24", ++ Usage: "set ip address. E.g. " + types.CIDRIpExample1, ++ }, ++ cli.StringFlag{ ++ Name: "ip6", ++ Usage: "set ipv6 address. E.g. " + types.CIDRIp6Example1, + }, + cli.StringFlag{ + Name: "mac", +@@ -101,6 +105,7 @@ and configure it as you wanted, then attach to specified bridge. + + nicConf := &types.InterfaceConf{ + IP: context.String("ip"), ++ IP6: context.String("ip6"), + Mac: context.String("mac"), + Mtu: context.Int("mtu"), + Type: context.String("type"), +@@ -207,7 +212,11 @@ var updateNicCommand = cli.Command{ + }, + cli.StringFlag{ + Name: "ip", +- Usage: "set ip address. E.g. 172.17.28.2/24", ++ Usage: "set ip address. E.g. " + types.CIDRIpExample1, ++ }, ++ cli.StringFlag{ ++ Name: "ip6", ++ Usage: "set ipv6 address. E.g. " + types.CIDRIp6Example1, + }, + cli.StringFlag{ + Name: "mac", +@@ -264,6 +273,7 @@ var updateNicCommand = cli.Command{ + + nicConf := &types.InterfaceConf{ + IP: context.String("ip"), ++ IP6: context.String("ip6"), + Mac: context.String("mac"), + Mtu: context.Int("mtu"), + Bridge: context.String("bridge"), +diff --git a/types/network.go b/types/network.go +index 74231f9..524e1d5 100644 +--- a/types/network.go ++++ b/types/network.go +@@ -21,6 +21,26 @@ import ( + "github.com/vishvananda/netlink" + ) + ++const ( ++ // example1 of ip address ++ IPExample1 = "172.17.28.2" ++ // example2 of ip address ++ IPExample2 = "172.17.28.3" ++ // example1 of cidr format ip address (with mask) ++ CIDRIpExample1 = IPExample1 + "/24" ++ // example2 of cidr format ip address (with mask) ++ CIDRIpExample2 = IPExample2 + "/16" ++ ++ // example1 of ip6 address ++ IP6Example1 = "2001:0db8:0:f101::1" ++ // example2 of ip6 address ++ IP6Example2 = "fe80::2aee:d4ef:fe:b890" ++ // example1 of cidr format ip6 address (with mask) ++ CIDRIp6Example1 = IP6Example1 + "/64" ++ // example2 of cidr format ip6 address (with mask) ++ CIDRIp6Example2 = IP6Example2 + "/64" ++) ++ + // NamespacePath namespace paths + type NamespacePath struct { + Pid string `json:"pid,omitempty"` +@@ -34,6 +54,7 @@ type NamespacePath struct { + // InterfaceConf is the network interface config + type InterfaceConf struct { + IP string `json:"Ip"` ++ IP6 string `json:"Ip6"` + Mac string `json:"Mac"` + Mtu int `json:"Mtu"` + Qlen int `json:"Qlen"` +@@ -72,9 +93,12 @@ func IsConflictNic(nic1, nic2 *InterfaceConf) error { + if nic1.Mac != "" && (nic1.Mac == nic2.Mac) { + return fmt.Errorf("interface mac conflict: %s", nic1.Mac) + } +- if nic1.IP == nic2.IP { ++ if nic1.IP != "" && nic1.IP == nic2.IP { + return fmt.Errorf("interface ip conflict: %s", nic1.IP) + } ++ if nic1.IP6 != "" && nic1.IP6 == nic2.IP6 { ++ return fmt.Errorf("interface ip6 conflict: %s", nic1.IP6) ++ } + return nil + } + +@@ -83,6 +107,9 @@ func IsSameNic(obj, src *InterfaceConf) bool { + if obj.IP != src.IP && obj.IP != "" { + return false + } ++ if obj.IP6 != src.IP6 && obj.IP6 != "" { ++ return false ++ } + if obj.Mac != src.Mac && obj.Mac != "" { + return false + } +@@ -134,10 +161,24 @@ func IsSameRoute(obj, src *Route) bool { + + // ValidNetworkConfig validate network config + func ValidNetworkConfig(conf *InterfaceConf) error { +- // check IP here + conf.IP = strings.TrimSpace(conf.IP) +- if _, err := netlink.ParseIPNet(conf.IP); err != nil { +- return err ++ conf.IP6 = strings.TrimSpace(conf.IP6) ++ if len(conf.IP) == 0 && len(conf.IP6) == 0 { ++ return fmt.Errorf("either ip or ipv6 must be specified") ++ } ++ ++ // check IP here ++ if len(conf.IP) != 0 { ++ if _, err := netlink.ParseIPNet(conf.IP); err != nil { ++ return err ++ } ++ } ++ ++ // check IP6 here ++ if len(conf.IP6) != 0 { ++ if _, err := netlink.ParseIPNet(conf.IP6); err != nil { ++ return err ++ } + } + + // Check mac here +diff --git a/types/network_test.go b/types/network_test.go +new file mode 100644 +index 0000000..fecf8b6 +--- /dev/null ++++ b/types/network_test.go +@@ -0,0 +1,514 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface type ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++// package types defines type used by libnetwork ++package types ++ ++import ( ++ "testing" ++) ++ ++// TestIsConflictNic tests IsConflictNic ++func TestIsConflictNic(t *testing.T) { ++ const ( ++ testCtrNicName1 = "ctr1" ++ testCtrNicName2 = "ctr2" ++ testHostNicName1 = "host1" ++ testHostNicName2 = "host2" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testMTU = 1500 ++ ) ++ type args struct { ++ nic1 *InterfaceConf ++ nic2 *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-same ctrName(ctrName can not be empty)", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-same hostName(hostName can not be empty)", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.1-same mac", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ Mac: testMac1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ Mac: testMac1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.2-diffrent mac address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ Mac: testMac1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC3.3-both not have a mac address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4.1-smae IP4", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP: testIP41, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP: testIP41, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4.2-different IP4 & not set IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP: testIP41, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP: testIP42, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4.3-both not have a IP4 address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC5.1-smae IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP6: testIP61, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP6: testIP61, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5.2-different IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP6: testIP61, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP6: testIP62, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC5.3-both not have a IP6 address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := IsConflictNic(tt.args.nic1, tt.args.nic2); (err != nil) != tt.wantErr { ++ t.Errorf("IsConflictNic() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestIsSameNic tests IsSameNic ++func TestIsSameNic(t *testing.T) { ++ const ( ++ testCtrNicName1 = "ctr1" ++ testCtrNicName2 = "ctr2" ++ testHostNicName1 = "host1" ++ testHostNicName2 = "host2" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ testMac2 = "ff:ee:dd:cc:bb:aa" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testMTU1 = 1500 ++ testMTU2 = 1200 ++ testQlen1 = 500 ++ testQlen2 = 1000 ++ testBridge1 = "test1" ++ testBridge2 = "test2" ++ testType1 = "eth" ++ testType2 = "veth" ++ ) ++ type args struct { ++ obj *InterfaceConf ++ src *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ want bool ++ }{ ++ { ++ name: "TC1-all data is empty", ++ args: args{ ++ obj: &InterfaceConf{}, ++ src: &InterfaceConf{}, ++ }, ++ want: true, ++ }, ++ { ++ name: "TC2-different IP", ++ args: args{ ++ obj: &InterfaceConf{ ++ IP: testIP41, ++ }, ++ src: &InterfaceConf{ ++ IP: testIP42, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC3-different IP6", ++ args: args{ ++ obj: &InterfaceConf{ ++ IP6: testIP61, ++ }, ++ src: &InterfaceConf{ ++ IP6: testIP62, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC4-different Mac", ++ args: args{ ++ obj: &InterfaceConf{ ++ Mac: testMac1, ++ }, ++ src: &InterfaceConf{ ++ Mac: testMac2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC5-different Mtu", ++ args: args{ ++ obj: &InterfaceConf{ ++ Mtu: testMTU1, ++ }, ++ src: &InterfaceConf{ ++ Mtu: testMTU2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC6-different Qlen", ++ args: args{ ++ obj: &InterfaceConf{ ++ Qlen: testQlen1, ++ }, ++ src: &InterfaceConf{ ++ Qlen: testQlen2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC7-different Type", ++ args: args{ ++ obj: &InterfaceConf{ ++ Type: testType1, ++ }, ++ src: &InterfaceConf{ ++ Type: testType2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC8-different Bridge", ++ args: args{ ++ obj: &InterfaceConf{ ++ Bridge: testBridge1, ++ }, ++ src: &InterfaceConf{ ++ Bridge: testBridge2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC9-different host Name", ++ args: args{ ++ obj: &InterfaceConf{ ++ HostNicName: testHostNicName1, ++ }, ++ src: &InterfaceConf{ ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC10-different Ctr Name", ++ args: args{ ++ obj: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ src: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ }, ++ }, ++ want: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if got := IsSameNic(tt.args.obj, tt.args.src); got != tt.want { ++ t.Errorf("IsSameNic() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++// TestValidNetworkConfig tests ValidNetworkConfig ++func TestValidNetworkConfig(t *testing.T) { ++ const ( ++ testCtrNicName = "ctr1" ++ testHostNicName = "host1" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ invalidMac = "ABCDEFG" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testIP4 = CIDRIpExample1 ++ invalidIP4 = IPExample1 ++ testIP6 = CIDRIp6Example1 ++ invalidIP6 = IP6Example1 ++ testBridge = "test1" ++ ethType = "eth" ++ vethType = "veth" ++ invalidType = "fake" ++ ) ++ type args struct { ++ conf *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-No IP & IP6", ++ args: args{ ++ conf: &InterfaceConf{}, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid ip", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: invalidIP4, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-invalid ip6", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP6: invalidIP6, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4-invalid Mac", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ Mac: invalidMac, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-No such eth nic", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC6-veth:No host nic name is not existed", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Type: vethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7-eth:empty host nic name", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7.1-eth:empty bridge", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7.2-eth can not find eth", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Bridge: testBridge, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC8-invalid type", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ Type: invalidType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := ValidNetworkConfig(tt.args.conf); (err != nil) != tt.wantErr { ++ t.Errorf("ValidNetworkConfig() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +-- +2.41.0 + diff --git a/syscontainer-tools.spec b/syscontainer-tools.spec index 1808b0f..91c84b3 100644 --- a/syscontainer-tools.spec +++ b/syscontainer-tools.spec @@ -1,7 +1,7 @@ #Basic Information Name: syscontainer-tools Version: 0.9 -Release: 54 +Release: 55 Summary: syscontainer tools for IT, work with iSulad License: Mulan PSL v2 URL: https://gitee.com/openeuler/syscontainer-tools @@ -14,6 +14,7 @@ Patch3: 0003-enable-external-linkmode-for-cgo-build.patch Patch4: 0004-add-dt-test.patch Patch5: 0005-add-riscv64-to-syscall-build.patch Patch6: 0006-syscontainer-tools-Add-sw64-architecture.patch +Patch7: 0007-support-ipv6.patch #Dependency BuildRequires: glibc-static @@ -115,6 +116,12 @@ chmod 0640 ${HOOK_SPEC}/hookspec.json rm -rfv %{buildroot} %changelog +* Thu Aug 10 2023 yangjiaqi - 0.9-55 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:support ip6 + * Wed Aug 09 2023 yangjiaqi - 0.9-54 - isolate sw patch by code to avoid rolling back patch