package network import ( "net" "os" "path/filepath" "strings" "syscall" "time" "github.com/containernetworking/cni/libcni" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/pkg/network" "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" ) const ( defaultIPv4Route = "0.0.0.0/0" defaultIPv6Route = "::/0" ) // NcList describes a generic map type NcList map[string]interface{} // NcArgs describes the cni args field type NcArgs map[string]NcLabels // NcLabels describes the label map type NcLabels map[string]string // PodmanLabelKey key used to store the podman network label in a cni config const PodmanLabelKey = "podman_labels" // NewNcList creates a generic map of values with string // keys and adds in version and network name func NewNcList(name, version string, labels NcLabels) NcList { n := NcList{} n["cniVersion"] = version n["name"] = name if len(labels) > 0 { n["args"] = NcArgs{PodmanLabelKey: labels} } return n } // NewHostLocalBridge creates a new LocalBridge for host-local func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, mtu int, vlan int, ipamConf IPAMHostLocalConf) *HostLocalBridge { hostLocalBridge := HostLocalBridge{ PluginType: "bridge", BrName: name, IPMasq: ipMasq, MTU: mtu, HairpinMode: true, Vlan: vlan, IPAM: ipamConf, } if isGateWay { hostLocalBridge.IsGW = true } if isDefaultGW { hostLocalBridge.IsDefaultGW = true } return &hostLocalBridge } // NewIPAMHostLocalConf creates a new IPAMHostLocal configuration func NewIPAMHostLocalConf(routes []IPAMRoute, ipamRanges [][]IPAMLocalHostRangeConf) (IPAMHostLocalConf, error) { ipamConf := IPAMHostLocalConf{ PluginType: "host-local", Routes: routes, // Possible future support ? Leaving for clues //ResolveConf: "", //DataDir: "" } ipamConf.Ranges = ipamRanges return ipamConf, nil } // 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 != nil && ipRange.IP != nil { first, err := FirstIPInSubnet(ipRange) if err != nil { return nil, err } last, err := LastIPInSubnet(ipRange) if err != nil { return nil, err } hostRange.RangeStart = first.String() hostRange.RangeEnd = last.String() } if gw != nil { hostRange.Gateway = gw.String() } else { // Add first ip in subnet as gateway. It is not required // by cni but should be included because of network inspect. hostRange.Gateway = CalcGatewayIP(subnet).String() } ranges = append(ranges, hostRange) return ranges, nil } // NewIPAMRoute creates a new IPAM route configuration func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer return IPAMRoute{Dest: r.String()} } // NewIPAMDefaultRoute creates a new IPAMDefault route of // 0.0.0.0/0 for IPv4 or ::/0 for IPv6 func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) { route := defaultIPv4Route if isIPv6 { route = defaultIPv6Route } _, n, err := net.ParseCIDR(route) if err != nil { return IPAMRoute{}, err } return NewIPAMRoute(n), nil } // NewPortMapPlugin creates a predefined, default portmapping // configuration func NewPortMapPlugin() PortMapConfig { caps := make(map[string]bool) caps["portMappings"] = true p := PortMapConfig{ PluginType: "portmap", Capabilities: caps, } return p } // NewFirewallPlugin creates a generic firewall plugin func NewFirewallPlugin() FirewallConfig { return FirewallConfig{ PluginType: "firewall", } } // NewTuningPlugin creates a generic tuning section func NewTuningPlugin() TuningConfig { return TuningConfig{ PluginType: "tuning", } } // NewDNSNamePlugin creates the dnsname config with a given // domainname func NewDNSNamePlugin(domainName string) DNSNameConfig { caps := make(map[string]bool, 1) caps["aliases"] = true return DNSNameConfig{ PluginType: "dnsname", DomainName: domainName, Capabilities: caps, } } // HasDNSNamePlugin looks to see if the dnsname cni plugin is present func HasDNSNamePlugin(paths []string) bool { for _, p := range paths { if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil { return true } } return false } // NewMacVLANPlugin creates a macvlanconfig with a given device name func NewMacVLANPlugin(device string, gateway net.IP, ipRange *net.IPNet, subnet *net.IPNet, mtu int) (MacVLANConfig, error) { i := IPAMDHCP{DHCP: "dhcp"} if gateway != nil || ipRange != nil || subnet != nil { ipam, err := NewIPAMLocalHostRange(subnet, ipRange, gateway) if err != nil { return MacVLANConfig{}, err } ranges := make([][]IPAMLocalHostRangeConf, 0) ranges = append(ranges, ipam) i.Ranges = ranges } m := MacVLANConfig{ PluginType: "macvlan", IPAM: i, } if mtu > 0 { m.MTU = mtu } // CNI is supposed to use the default route if a // parent device is not provided if len(device) > 0 { m.Master = device } return m, nil } // IfPassesFilter filters NetworkListReport and returns true if the filter match the given config func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) { result := true for key, filterValues := range filters { result = false switch strings.ToLower(key) { case "name": // matches one name, regex allowed result = util.StringMatchRegexSlice(netconf.Name, filterValues) case "plugin": // match one plugin plugins := network.GetCNIPlugins(netconf) for _, val := range filterValues { if strings.Contains(plugins, val) { result = true break } } case "label": // matches all labels result = matchPruneLabelFilters(netconf, filterValues) case "driver": // matches only for the DefaultNetworkDriver for _, filterValue := range filterValues { plugins := network.GetCNIPlugins(netconf) if filterValue == DefaultNetworkDriver && strings.Contains(plugins, DefaultNetworkDriver) { result = true } } case "id": // matches part of one id for _, filterValue := range filterValues { if strings.Contains(network.GetNetworkID(netconf.Name), filterValue) { result = true break } } // TODO: add dangling filter default: return false, errors.Errorf("invalid filter %q", key) } } return result, nil } // IfPassesPruneFilter filters NetworkListReport and returns true if the prune filter match the given config func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigList, f map[string][]string) (bool, error) { for key, filterValues := range f { switch strings.ToLower(key) { case "label": return matchPruneLabelFilters(netconf, filterValues), nil case "until": until, err := util.ComputeUntilTimestamp(key, filterValues) if err != nil { return false, err } created, err := getCreatedTimestamp(config, netconf) if err != nil { return false, err } if created.Before(until) { return true, nil } default: return false, errors.Errorf("invalid filter %q", key) } } return false, nil } func matchPruneLabelFilters(netconf *libcni.NetworkConfigList, filterValues []string) bool { labels := GetNetworkLabels(netconf) result := true outer: for _, filterValue := range filterValues { filterArray := strings.SplitN(filterValue, "=", 2) filterKey := filterArray[0] if len(filterArray) > 1 { filterValue = filterArray[1] } else { filterValue = "" } for labelKey, labelValue := range labels { if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { result = true continue outer } } result = false } return result } func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) { networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name) if err != nil { return nil, err } f, err := os.Stat(networkConfigPath) if err != nil { return nil, err } stat := f.Sys().(*syscall.Stat_t) created := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) // nolint: unconvert return &created, nil }