mirror of
https://github.com/containers/podman.git
synced 2025-06-25 03:52:15 +08:00
@ -35,8 +35,7 @@ func networkCreateFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&networkCreateOptions.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device")
|
||||
// TODO not supported yet
|
||||
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
|
||||
// TODO enable when IPv6 is working
|
||||
// flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking")
|
||||
flags.BoolVar(&networkCreateOptions.IPv6, "ipv6", false, "enable IPv6 networking")
|
||||
flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format")
|
||||
flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin")
|
||||
}
|
||||
|
@ -49,6 +49,10 @@ Macvlan connection.
|
||||
|
||||
The subnet in CIDR notation.
|
||||
|
||||
**--ipv6**
|
||||
|
||||
Enable IPv6 (Dual Stack) networking. You must pass a IPv6 subnet. The *subnet* option must be used with the *ipv6* option.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
Create a network with no options
|
||||
@ -63,6 +67,13 @@ Create a network named *newnet* that uses *192.5.0.0/16* for its subnet.
|
||||
/etc/cni/net.d/newnet.conflist
|
||||
```
|
||||
|
||||
Create an IPv6 network named *newnetv6*, you must specify the subnet for this network, otherwise the command will fail.
|
||||
For this example, we use *2001:db8::/64* for its subnet.
|
||||
```
|
||||
# podman network create --subnet 2001:db8::/64 --ipv6 newnetv6
|
||||
/etc/cni/net.d/newnetv6.conflist
|
||||
```
|
||||
|
||||
Create a network named *newnet* that uses *192.168.33.0/24* and defines a gateway as *192.168.133.3*
|
||||
```
|
||||
# podman network create --subnet 192.168.33.0/24 --gateway 192.168.33.3 newnet
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Create the CNI network
|
||||
func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtime) (*entities.NetworkCreateReport, error) {
|
||||
var fileName string
|
||||
if err := isSupportedDriver(options.Driver); err != nil {
|
||||
@ -41,60 +42,120 @@ func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtim
|
||||
return &entities.NetworkCreateReport{Filename: fileName}, nil
|
||||
}
|
||||
|
||||
// validateBridgeOptions validate the bridge networking options
|
||||
func validateBridgeOptions(options entities.NetworkCreateOptions) error {
|
||||
subnet := &options.Subnet
|
||||
ipRange := &options.Range
|
||||
gateway := options.Gateway
|
||||
// if IPv6 is set an IPv6 subnet MUST be specified
|
||||
if options.IPv6 && ((subnet.IP == nil) || (subnet.IP != nil && !IsIPv6(subnet.IP))) {
|
||||
return errors.Errorf("ipv6 option requires an IPv6 --subnet to be provided")
|
||||
}
|
||||
// range and gateway depend on subnet
|
||||
if subnet.IP == nil && (ipRange.IP != nil || gateway != nil) {
|
||||
return errors.Errorf("every ip-range or gateway must have a corresponding subnet")
|
||||
}
|
||||
|
||||
// if a range is given, we need to ensure it is "in" the network range.
|
||||
if ipRange.IP != nil {
|
||||
firstIP, err := FirstIPInSubnet(ipRange)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get first IP address from ip-range")
|
||||
}
|
||||
lastIP, err := LastIPInSubnet(ipRange)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get last IP address from ip-range")
|
||||
}
|
||||
if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
|
||||
return errors.Errorf("the ip range %s does not fall within the subnet range %s", ipRange.String(), subnet.String())
|
||||
}
|
||||
}
|
||||
|
||||
// if network is provided and if gateway is provided, make sure it is "in" network
|
||||
if gateway != nil && !subnet.Contains(gateway) {
|
||||
return errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// createBridge creates a CNI network
|
||||
func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
|
||||
isGateway := true
|
||||
ipMasq := true
|
||||
subnet := &options.Subnet
|
||||
ipRange := options.Range
|
||||
runtimeConfig, err := r.GetConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// if range is provided, make sure it is "in" network
|
||||
if subnet.IP != nil {
|
||||
// if network is provided, does it conflict with existing CNI or live networks
|
||||
err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
|
||||
} else {
|
||||
// if no network is provided, figure out network
|
||||
subnet, err = GetFreeNetwork(runtimeConfig)
|
||||
}
|
||||
|
||||
// validate options
|
||||
err = validateBridgeOptions(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// For compatibility with the docker implementation:
|
||||
// if IPv6 is enabled (it really means dual-stack) then an IPv6 subnet has to be provided, and one free network is allocated for IPv4
|
||||
// if IPv6 is not specified the subnet may be specified and can be either IPv4 or IPv6 (podman, unlike docker, allows IPv6 only networks)
|
||||
// If not subnet is specified an IPv4 subnet will be allocated
|
||||
subnet := &options.Subnet
|
||||
ipRange := &options.Range
|
||||
gateway := options.Gateway
|
||||
if gateway == nil {
|
||||
// if no gateway is provided, provide it as first ip of network
|
||||
gateway = CalcGatewayIP(subnet)
|
||||
}
|
||||
// if network is provided and if gateway is provided, make sure it is "in" network
|
||||
if options.Subnet.IP != nil && options.Gateway != nil {
|
||||
if !subnet.Contains(gateway) {
|
||||
return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
|
||||
var ipamRanges [][]IPAMLocalHostRangeConf
|
||||
var routes []IPAMRoute
|
||||
if subnet.IP != nil {
|
||||
// if network is provided, does it conflict with existing CNI or live networks
|
||||
err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// obtain CNI subnet default route
|
||||
defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
routes = append(routes, defaultRoute)
|
||||
// obtain CNI range
|
||||
ipamRange, err := NewIPAMLocalHostRange(subnet, ipRange, gateway)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ipamRanges = append(ipamRanges, ipamRange)
|
||||
}
|
||||
// if no network is provided or IPv6 flag used, figure out the IPv4 network
|
||||
if options.IPv6 || len(routes) == 0 {
|
||||
subnetV4, err := GetFreeNetwork(runtimeConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// obtain IPv4 default route
|
||||
defaultRoute, err := NewIPAMDefaultRoute(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
routes = append(routes, defaultRoute)
|
||||
// the CNI bridge plugin does not need to set
|
||||
// the range or gateway options explicitly
|
||||
ipamRange, err := NewIPAMLocalHostRange(subnetV4, nil, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ipamRanges = append(ipamRanges, ipamRange)
|
||||
}
|
||||
|
||||
// create CNI config
|
||||
ipamConfig, err := NewIPAMHostLocalConf(routes, ipamRanges)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if options.Internal {
|
||||
isGateway = false
|
||||
ipMasq = false
|
||||
}
|
||||
|
||||
// if a range is given, we need to ensure it is "in" the network range.
|
||||
if options.Range.IP != nil {
|
||||
if options.Subnet.IP == nil {
|
||||
return "", errors.New("you must define a subnet range to define an ip-range")
|
||||
}
|
||||
firstIP, err := FirstIPInSubnet(&options.Range)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lastIP, err := LastIPInSubnet(&options.Range)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
|
||||
return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String())
|
||||
}
|
||||
}
|
||||
// obtain host bridge name
|
||||
bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -113,20 +174,9 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate
|
||||
name = bridgeDeviceName
|
||||
}
|
||||
|
||||
// create CNI plugin configuration
|
||||
ncList := NewNcList(name, version.Current())
|
||||
var plugins []CNIPlugins
|
||||
var routes []IPAMRoute
|
||||
|
||||
defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
routes = append(routes, defaultRoute)
|
||||
ipamConfig, err := NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO need to iron out the role of isDefaultGW and IPMasq
|
||||
bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
|
||||
plugins = append(plugins, bridge)
|
||||
|
131
libpod/network/create_test.go
Normal file
131
libpod/network/create_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||
)
|
||||
|
||||
func Test_validateBridgeOptions(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
subnet net.IPNet
|
||||
ipRange net.IPNet
|
||||
gateway net.IP
|
||||
isIPv6 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "IPv4 subnet only",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
},
|
||||
{
|
||||
name: "IPv4 subnet and range",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
},
|
||||
{
|
||||
name: "IPv4 subnet and gateway",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
gateway: net.ParseIP("192.168.0.10"),
|
||||
},
|
||||
{
|
||||
name: "IPv4 subnet, range and gateway",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
gateway: net.ParseIP("192.168.0.10"),
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet only",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet and range",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))},
|
||||
isIPv6: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet and gateway",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
gateway: net.ParseIP("2001:DB8::2"),
|
||||
isIPv6: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet, range and gateway",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))},
|
||||
gateway: net.ParseIP("2001:DB8::2"),
|
||||
isIPv6: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet, range and gateway without IPv6 option (PODMAN SUPPORTS IT UNLIKE DOCKEr)",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))},
|
||||
gateway: net.ParseIP("2001:DB8::2"),
|
||||
isIPv6: false,
|
||||
},
|
||||
{
|
||||
name: "range provided but not subnet",
|
||||
ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "gateway provided but not subnet",
|
||||
gateway: net.ParseIP("192.168.0.10"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IPv4 subnet but IPv6 required",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
gateway: net.ParseIP("192.168.0.10"),
|
||||
isIPv6: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 required but IPv4 options used",
|
||||
subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
gateway: net.ParseIP("192.168.0.10"),
|
||||
isIPv6: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 required but not subnet provided",
|
||||
isIPv6: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "range out of the subnet",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
ipRange: net.IPNet{IP: net.ParseIP("2001:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))},
|
||||
gateway: net.ParseIP("2001:DB8::2"),
|
||||
isIPv6: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "gateway out of the subnet",
|
||||
subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
gateway: net.ParseIP("2001::2"),
|
||||
isIPv6: true,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options := entities.NetworkCreateOptions{
|
||||
Subnet: tt.subnet,
|
||||
Range: tt.ipRange,
|
||||
Gateway: tt.gateway,
|
||||
IPv6: tt.isIPv6,
|
||||
}
|
||||
if err := validateBridgeOptions(options); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateBridgeOptions() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetCNIConfDir get CNI configuration directory
|
||||
func GetCNIConfDir(configArg *config.Config) string {
|
||||
if len(configArg.Network.NetworkConfigDir) < 1 {
|
||||
dc, err := config.DefaultConfig()
|
||||
|
@ -42,8 +42,7 @@ func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamCo
|
||||
}
|
||||
|
||||
// NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration
|
||||
func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPNet, gw net.IP) (IPAMHostLocalConf, error) {
|
||||
var ipamRanges [][]IPAMLocalHostRangeConf
|
||||
func NewIPAMHostLocalConf(routes []IPAMRoute, ipamRanges [][]IPAMLocalHostRangeConf) (IPAMHostLocalConf, error) {
|
||||
ipamConf := IPAMHostLocalConf{
|
||||
PluginType: "host-local",
|
||||
Routes: routes,
|
||||
@ -51,22 +50,19 @@ func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPN
|
||||
//ResolveConf: "",
|
||||
//DataDir: ""
|
||||
}
|
||||
IPAMRange, err := newIPAMLocalHostRange(subnet, &ipRange, &gw)
|
||||
if err != nil {
|
||||
return ipamConf, err
|
||||
}
|
||||
ipamRanges = append(ipamRanges, IPAMRange)
|
||||
|
||||
ipamConf.Ranges = ipamRanges
|
||||
return ipamConf, nil
|
||||
}
|
||||
|
||||
func newIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw *net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer
|
||||
// NewIPAMLocalHostRange create a new IPAM range
|
||||
func NewIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer
|
||||
var ranges []IPAMLocalHostRangeConf
|
||||
hostRange := IPAMLocalHostRangeConf{
|
||||
Subnet: subnet.String(),
|
||||
}
|
||||
// an user provided a range, we add it here
|
||||
if ipRange.IP != nil {
|
||||
if ipRange != nil && ipRange.IP != nil {
|
||||
first, err := FirstIPInSubnet(ipRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,6 +1,7 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@ -36,3 +37,72 @@ func TestNewIPAMDefaultRoute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIPAMLocalHostRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
subnet *net.IPNet
|
||||
ipRange *net.IPNet
|
||||
gw net.IP
|
||||
want []IPAMLocalHostRangeConf
|
||||
}{
|
||||
{
|
||||
name: "IPv4 subnet",
|
||||
subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
want: []IPAMLocalHostRangeConf{
|
||||
{
|
||||
Subnet: "192.168.0.0/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 subnet, range and gateway",
|
||||
subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||
ipRange: &net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)},
|
||||
gw: net.ParseIP("192.168.0.10"),
|
||||
want: []IPAMLocalHostRangeConf{
|
||||
{
|
||||
Subnet: "192.168.0.0/24",
|
||||
RangeStart: "192.168.0.129",
|
||||
RangeEnd: "192.168.0.255",
|
||||
Gateway: "192.168.0.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet",
|
||||
subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
want: []IPAMLocalHostRangeConf{
|
||||
{
|
||||
Subnet: "2001:db8::/48",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv6 subnet, range and gateway",
|
||||
subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))},
|
||||
ipRange: &net.IPNet{IP: net.ParseIP("2001:DB8:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))},
|
||||
gw: net.ParseIP("2001:DB8::2"),
|
||||
want: []IPAMLocalHostRangeConf{
|
||||
{
|
||||
Subnet: "2001:db8::/48",
|
||||
RangeStart: "2001:db8:1:1::1",
|
||||
RangeEnd: "2001:db8:1:1:ffff:ffff:ffff:ffff",
|
||||
Gateway: "2001:db8::2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewIPAMLocalHostRange(tt.subnet, tt.ipRange, tt.gw)
|
||||
if err != nil {
|
||||
t.Errorf("no error expected: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewIPAMLocalHostRange() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ type NetworkCreateOptions struct {
|
||||
MacVLAN string
|
||||
Range net.IPNet
|
||||
Subnet net.IPNet
|
||||
IPv6 bool
|
||||
}
|
||||
|
||||
// NetworkCreateReport describes a created network for the cli
|
||||
|
@ -177,8 +177,7 @@ var _ = Describe("Podman network create", func() {
|
||||
})
|
||||
|
||||
It("podman network create with name and IPv6 subnet", func() {
|
||||
SkipIfRootless("FIXME I believe this should work in rootlessmode")
|
||||
|
||||
SkipIfRootless("FIXME It needs the ip6tables modules loaded")
|
||||
var (
|
||||
results []network.NcList
|
||||
)
|
||||
@ -218,12 +217,72 @@ var _ = Describe("Podman network create", func() {
|
||||
Expect(subnet.Contains(containerIP)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("podman network create with name and IPv6 flag (dual-stack)", func() {
|
||||
SkipIfRootless("FIXME It needs the ip6tables modules loaded")
|
||||
var (
|
||||
results []network.NcList
|
||||
)
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "fd00:4:3:2:1::/64", "--ipv6", "newDualStacknetwork"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc.ExitCode()).To(BeZero())
|
||||
|
||||
defer podmanTest.removeCNINetwork("newDualStacknetwork")
|
||||
|
||||
// Inspect the network configuration
|
||||
inspect := podmanTest.Podman([]string{"network", "inspect", "newDualStacknetwork"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
|
||||
// JSON the network configuration into something usable
|
||||
err := json.Unmarshal([]byte(inspect.OutputToString()), &results)
|
||||
Expect(err).To(BeNil())
|
||||
result := results[0]
|
||||
Expect(result["name"]).To(Equal("newDualStacknetwork"))
|
||||
|
||||
// JSON the bridge info
|
||||
bridgePlugin, err := genericPluginsToBridge(result["plugins"], "bridge")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(bridgePlugin.IPAM.Routes[0].Dest).To(Equal("::/0"))
|
||||
Expect(bridgePlugin.IPAM.Routes[1].Dest).To(Equal("0.0.0.0/0"))
|
||||
|
||||
// Once a container executes a new network, the nic will be created. We should clean those up
|
||||
// best we can
|
||||
defer removeNetworkDevice(bridgePlugin.BrName)
|
||||
|
||||
try := podmanTest.Podman([]string{"run", "-it", "--rm", "--network", "newDualStacknetwork", ALPINE, "sh", "-c", "ip addr show eth0 | grep global | awk ' /inet6 / {print $2}'"})
|
||||
try.WaitWithDefaultTimeout()
|
||||
|
||||
_, subnet, err := net.ParseCIDR("fd00:4:3:2:1::/64")
|
||||
Expect(err).To(BeNil())
|
||||
containerIP, _, err := net.ParseCIDR(try.OutputToString())
|
||||
Expect(err).To(BeNil())
|
||||
// Ensure that the IP the container got is within the subnet the user asked for
|
||||
Expect(subnet.Contains(containerIP)).To(BeTrue())
|
||||
// verify the container has an IPv4 address too (the IPv4 subnet is autogenerated)
|
||||
try = podmanTest.Podman([]string{"run", "-it", "--rm", "--network", "newDualStacknetwork", ALPINE, "sh", "-c", "ip addr show eth0 | awk ' /inet / {print $2}'"})
|
||||
try.WaitWithDefaultTimeout()
|
||||
containerIP, _, err = net.ParseCIDR(try.OutputToString())
|
||||
Expect(err).To(BeNil())
|
||||
Expect(containerIP.To4()).To(Not(BeNil()))
|
||||
})
|
||||
|
||||
It("podman network create with invalid subnet", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create with ipv4 subnet and ipv6 flag", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "--ipv6", "fail"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create with empty subnet and ipv6 flag", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--ipv6", "fail"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create with invalid IP", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.0/17000", "fail"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
@ -247,6 +306,29 @@ var _ = Describe("Podman network create", func() {
|
||||
Expect(ncFail).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create two networks with same subnet should fail", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.13.0/24", "subnet1"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc.ExitCode()).To(BeZero())
|
||||
defer podmanTest.removeCNINetwork("subnet1")
|
||||
|
||||
ncFail := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.13.0/24", "subnet2"})
|
||||
ncFail.WaitWithDefaultTimeout()
|
||||
Expect(ncFail).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create two IPv6 networks with same subnet should fail", func() {
|
||||
SkipIfRootless("FIXME It needs the ip6tables modules loaded")
|
||||
nc := podmanTest.Podman([]string{"network", "create", "--subnet", "fd00:4:4:4:4::/64", "--ipv6", "subnet1v6"})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
Expect(nc.ExitCode()).To(BeZero())
|
||||
defer podmanTest.removeCNINetwork("subnet1v6")
|
||||
|
||||
ncFail := podmanTest.Podman([]string{"network", "create", "--subnet", "fd00:4:4:4:4::/64", "--ipv6", "subnet2v6"})
|
||||
ncFail.WaitWithDefaultTimeout()
|
||||
Expect(ncFail).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman network create with invalid network name", func() {
|
||||
nc := podmanTest.Podman([]string{"network", "create", "foo "})
|
||||
nc.WaitWithDefaultTimeout()
|
||||
|
Reference in New Issue
Block a user