agent/utils/net/netutils_linux.go (105 lines of code) (raw):
//go:build linux
// +build linux
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package net
import (
"bytes"
"errors"
"fmt"
"net"
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
"github.com/aws/amazon-ecs-agent/agent/utils/netlinkwrapper"
"github.com/vishvananda/netlink"
)
// Finds a link by provided mac address.
func FindLinkByMac(netlinkClient netlinkwrapper.NetLink, mac string) (netlink.Link, error) {
parsedMac, err := net.ParseMAC(mac)
if err != nil {
return nil, fmt.Errorf("failed to parse mac '%s': %w", mac, err)
}
links, err := netlinkClient.LinkList()
if err != nil {
return nil, fmt.Errorf("failed to list all links: %w", err)
}
for _, link := range links {
attrs := link.Attrs()
if attrs != nil && bytes.Equal(parsedMac, attrs.HardwareAddr) {
return link, nil
}
}
return nil, errors.New("link was not found")
}
// Checks if the given link has a default route of the given IP family.
//
// Link provided can be `nil`. In that case this function will look at routes for
// all links on the instance.
func HasDefaultRoute(
netlinkClient netlinkwrapper.NetLink, link netlink.Link, ipFamily int,
) (bool, error) {
routes, err := netlinkClient.RouteList(link, ipFamily)
if err != nil {
return false, fmt.Errorf("failed to list routes: %w", err)
}
for _, route := range routes {
if isDefaultRoute(route, ipFamily) {
return true, nil
}
}
return false, nil
}
// Checks if the provided route is a default route for the specified IP Family.
//
// A default route is defined here as a route with a Gateway and all IP addresses covered
// in its destination (0.0.0.0/0 or ::/0).
func isDefaultRoute(route netlink.Route, ipFamily int) bool {
// Must have a gateway
if route.Gw == nil {
return false
}
// nil destination covers all destinations
if route.Dst == nil {
return true
}
switch ipFamily {
case netlink.FAMILY_V4:
return isFullRangeIPv4(route.Dst)
case netlink.FAMILY_V6:
return isFullRangeIPv6(route.Dst)
default:
return isFullRangeIPv4(route.Dst) || isFullRangeIPv6(route.Dst)
}
}
// Checks if the provided IPNet is 0.0.0.0/0
func isFullRangeIPv4(ipnet *net.IPNet) bool {
mask := ipnet.Mask
ipv4 := ipnet.IP.To4()
if ipv4 != nil {
// IP and mask both should be zero to cover all destinations
return ipv4.Equal(net.IPv4zero) && len(mask) == net.IPv4len && allZeros(mask)
}
return false
}
// Checks if the provided IPNet is ::/0
func isFullRangeIPv6(ipnet *net.IPNet) bool {
mask := ipnet.Mask
ipv6 := ipnet.IP.To16()
if ipv6 != nil {
// IP and mask both should be zero to cover all destinations
return ipv6.Equal(net.IPv6zero) && len(mask) == net.IPv6len && allZeros(mask)
}
return false
}
// allZeros checks if all bytes in the given net.IPMask are zero.
func allZeros(mask net.IPMask) bool {
for _, b := range mask {
if b != 0 {
return false
}
}
return true
}
// This function determines IPv4 and IPv6 compatibility by checking if default routes
// exist for each. Mac address of a network interface can be provided optionally to restrict
// compatibility checks to that particular network interface. If no mac address is provided
// then compatibility checks are performed for all network interfaces on the instance.
func DetermineIPCompatibility(
nlWrapper netlinkwrapper.NetLink, mac string,
) (ipcompatibility.IPCompatibility, error) {
// Find link for the mac if provided
var link netlink.Link
if mac != "" {
var err error
link, err = FindLinkByMac(nlWrapper, mac)
if err != nil {
return ipcompatibility.NewIPCompatibility(false, false),
fmt.Errorf("failed to find link for mac '%s': %w", mac, err)
}
}
// Determine IPv4 compatibility
ipv4Compatible, err := HasDefaultRoute(nlWrapper, link, netlink.FAMILY_V4)
if err != nil {
return ipcompatibility.NewIPCompatibility(false, false),
fmt.Errorf("failed to determine IPv4 compatibility: %w", err)
}
// Determine IPv6 compatibility
ipv6Compatible, err := HasDefaultRoute(nlWrapper, link, netlink.FAMILY_V6)
if err != nil {
return ipcompatibility.NewIPCompatibility(false, false),
fmt.Errorf("failed to determine IPv6 compatibility: %w", err)
}
return ipcompatibility.NewIPCompatibility(ipv4Compatible, ipv6Compatible), nil
}