From 9acfe3e5c2889c511d28447f1660911c4ce17cc5 Mon Sep 17 00:00:00 2001
From: vegbir <yangjiaqi16@huawei.com>
Date: Thu, 10 Aug 2023 02:44:07 +0000
Subject: [PATCH] support ipv6

Signed-off-by: vegbir <yangjiaqi16@huawei.com>
---
 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 := &ethDriver{
+				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 := &ethDriver{
+				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

