184 lines
5.3 KiB
Go
184 lines
5.3 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/containers/common/libnetwork/types"
|
|
"github.com/containers/common/libnetwork/util"
|
|
)
|
|
|
|
// ValidateSubnet will validate a given Subnet. It checks if the
|
|
// given gateway and lease range are part of this subnet. If the
|
|
// gateway is empty and addGateway is true it will get the first
|
|
// available ip in the subnet assigned.
|
|
func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) error {
|
|
if s == nil {
|
|
return errors.New("subnet is nil")
|
|
}
|
|
if s.Subnet.IP == nil {
|
|
return errors.New("subnet ip is nil")
|
|
}
|
|
|
|
// Reparse to ensure subnet is valid.
|
|
// Do not use types.ParseCIDR() because we want the ip to be
|
|
// the network address and not a random ip in the subnet.
|
|
_, n, err := net.ParseCIDR(s.Subnet.String())
|
|
if err != nil {
|
|
return fmt.Errorf("subnet invalid: %w", err)
|
|
}
|
|
|
|
// check that the new subnet does not conflict with existing ones
|
|
if NetworkIntersectsWithNetworks(n, usedNetworks) {
|
|
return fmt.Errorf("subnet %s is already used on the host or by another config", n.String())
|
|
}
|
|
|
|
s.Subnet = types.IPNet{IPNet: *n}
|
|
if s.Gateway != nil {
|
|
if !s.Subnet.Contains(s.Gateway) {
|
|
return fmt.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
|
|
}
|
|
util.NormalizeIP(&s.Gateway)
|
|
} else if addGateway {
|
|
ip, err := util.FirstIPInSubnet(n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.Gateway = ip
|
|
}
|
|
|
|
if s.LeaseRange != nil {
|
|
if s.LeaseRange.StartIP != nil {
|
|
if !s.Subnet.Contains(s.LeaseRange.StartIP) {
|
|
return fmt.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
|
|
}
|
|
util.NormalizeIP(&s.LeaseRange.StartIP)
|
|
}
|
|
if s.LeaseRange.EndIP != nil {
|
|
if !s.Subnet.Contains(s.LeaseRange.EndIP) {
|
|
return fmt.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
|
|
}
|
|
util.NormalizeIP(&s.LeaseRange.EndIP)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateSubnets will validate the subnets for this network.
|
|
// It also sets the gateway if the gateway is empty and addGateway is set to true
|
|
// IPv6Enabled to true if at least one subnet is ipv6.
|
|
func ValidateSubnets(network *types.Network, addGateway bool, usedNetworks []*net.IPNet) error {
|
|
for i := range network.Subnets {
|
|
err := ValidateSubnet(&network.Subnets[i], addGateway, usedNetworks)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if util.IsIPv6(network.Subnets[i].Subnet.IP) {
|
|
network.IPv6Enabled = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateRoutes(routes []types.Route) error {
|
|
for _, route := range routes {
|
|
err := ValidateRoute(route)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateRoute(route types.Route) error {
|
|
if route.Destination.IP == nil {
|
|
return errors.New("route destination ip nil")
|
|
}
|
|
|
|
if route.Destination.Mask == nil {
|
|
return errors.New("route destination mask nil")
|
|
}
|
|
|
|
if route.Gateway == nil {
|
|
return errors.New("route gateway nil")
|
|
}
|
|
|
|
// Reparse to ensure destination is valid.
|
|
ip, ipNet, err := net.ParseCIDR(route.Destination.String())
|
|
if err != nil {
|
|
return fmt.Errorf("route destination invalid: %w", err)
|
|
}
|
|
|
|
// check that destination is a network and not an address
|
|
if !ip.Equal(ipNet.IP) {
|
|
return errors.New("route destination invalid")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ValidateSetupOptions(n NetUtil, namespacePath string, options types.SetupOptions) error {
|
|
if namespacePath == "" {
|
|
return errors.New("namespacePath is empty")
|
|
}
|
|
if options.ContainerID == "" {
|
|
return errors.New("ContainerID is empty")
|
|
}
|
|
if len(options.Networks) == 0 {
|
|
return errors.New("must specify at least one network")
|
|
}
|
|
for name, netOpts := range options.Networks {
|
|
network, err := n.Network(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = validatePerNetworkOpts(network, &netOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validatePerNetworkOpts checks that all given static ips are in a subnet on this network
|
|
func validatePerNetworkOpts(network *types.Network, netOpts *types.PerNetworkOptions) error {
|
|
if netOpts.InterfaceName == "" {
|
|
return fmt.Errorf("interface name on network %s is empty", network.Name)
|
|
}
|
|
if network.IPAMOptions[types.Driver] == types.HostLocalIPAMDriver {
|
|
outer:
|
|
for _, ip := range netOpts.StaticIPs {
|
|
for _, s := range network.Subnets {
|
|
if s.Subnet.Contains(ip) {
|
|
continue outer
|
|
}
|
|
}
|
|
return fmt.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.Name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateInterfaceName validates the interface name based on the following rules:
|
|
// 1. The name must be less than MaxInterfaceNameLength characters
|
|
// 2. The name must not be "." or ".."
|
|
// 3. The name must not contain / or : or any whitespace characters
|
|
// ref to https://github.com/torvalds/linux/blob/81e4f8d68c66da301bb881862735bd74c6241a19/include/uapi/linux/if.h#L33C18-L33C20
|
|
func ValidateInterfaceName(ifName string) error {
|
|
if len(ifName) > types.MaxInterfaceNameLength {
|
|
return fmt.Errorf("interface name is too long: interface names must be %d characters or less: %w", types.MaxInterfaceNameLength, types.ErrInvalidArg)
|
|
}
|
|
if ifName == "." || ifName == ".." {
|
|
return fmt.Errorf("interface name is . or ..: %w", types.ErrInvalidArg)
|
|
}
|
|
if strings.ContainsFunc(ifName, func(r rune) bool {
|
|
return r == '/' || r == ':' || unicode.IsSpace(r)
|
|
}) {
|
|
return fmt.Errorf("interface name contains / or : or whitespace characters: %w", types.ErrInvalidArg)
|
|
}
|
|
return nil
|
|
}
|