network/networkutils/networkutils_linux.go (210 lines of code) (raw):
//go:build linux
// +build linux
package networkutils
import (
"fmt"
"net"
"github.com/Azure/azure-container-networking/cni/log"
"github.com/Azure/azure-container-networking/iptables"
"github.com/Azure/azure-container-networking/netlink"
"github.com/Azure/azure-container-networking/platform"
"github.com/pkg/errors"
"go.uber.org/zap"
)
/*RFC For Private Address Space: https://tools.ietf.org/html/rfc1918
The Internet Assigned Numbers Authority (IANA) has reserved the
following three blocks of the IP address space for private internets:
10.0.0.0 - 10.255.255.255 (10/8 prefix)
172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
RFC for Link Local Addresses: https://tools.ietf.org/html/rfc3927
This document describes how a host may
automatically configure an interface with an IPv4 address within the
169.254/16 prefix that is valid for communication with other devices
connected to the same physical (or logical) link.
*/
const (
toggleIPV6Cmd = "sysctl -w net.ipv6.conf.all.disable_ipv6=%d"
enableIPV6ForwardCmd = "sysctl -w net.ipv6.conf.all.forwarding=1"
enableIPV4ForwardCmd = "sysctl -w net.ipv4.conf.all.forwarding=1"
disableRACmd = "sysctl -w net.ipv6.conf.%s.accept_ra=0"
acceptRAV6File = "/proc/sys/net/ipv6/conf/%s/accept_ra"
)
var logger = log.CNILogger.With(zap.String("component", "net-utils"))
type ipTablesClient interface {
InsertIptableRule(version, tableName, chainName, match, target string) error
AppendIptableRule(version, tableName, chainName, match, target string) error
DeleteIptableRule(version, tableName, chainName, match, target string) error
}
var errorNetworkUtils = errors.New("NetworkUtils Error")
func newErrorNetworkUtils(errStr string) error {
return fmt.Errorf("%w : %s", errorNetworkUtils, errStr)
}
type NetworkUtils struct {
netlink netlink.NetlinkInterface
plClient platform.ExecClient
}
func NewNetworkUtils(nl netlink.NetlinkInterface, plClient platform.ExecClient) NetworkUtils {
return NetworkUtils{
netlink: nl,
plClient: plClient,
}
}
func (nu NetworkUtils) CreateEndpoint(hostVethName, containerVethName string, macAddress net.HardwareAddr) error {
logger.Info("Creating veth pair", zap.String("hostVethName", hostVethName), zap.String("containerVethName", containerVethName))
link := netlink.VEthLink{
LinkInfo: netlink.LinkInfo{
Type: netlink.LINK_TYPE_VETH,
Name: hostVethName,
MacAddress: macAddress,
},
PeerName: containerVethName,
}
err := nu.netlink.AddLink(&link)
if err != nil {
logger.Error("Failed to create veth pair with", zap.Error(err))
return newErrorNetworkUtils(err.Error())
}
err = nu.netlink.SetLinkState(hostVethName, true)
if err != nil {
return newErrorNetworkUtils(err.Error())
}
if err := nu.DisableRAForInterface(hostVethName); err != nil {
return newErrorNetworkUtils(err.Error())
}
return nil
}
func (nu NetworkUtils) SetupContainerInterface(containerVethName, targetIfName string) error {
// Interface needs to be down before renaming.
if err := nu.netlink.SetLinkState(containerVethName, false); err != nil {
return newErrorNetworkUtils(err.Error())
}
// Rename the container interface.
logger.Info("Setting link", zap.String("containerVethName", containerVethName), zap.String("targetIfName", targetIfName))
if err := nu.netlink.SetLinkName(containerVethName, targetIfName); err != nil {
return newErrorNetworkUtils(err.Error())
}
if err := nu.DisableRAForInterface(targetIfName); err != nil {
return newErrorNetworkUtils(err.Error())
}
// Bring the interface back up.
err := nu.netlink.SetLinkState(targetIfName, true)
if err != nil {
return newErrorNetworkUtils(err.Error())
}
return nil
}
func (nu NetworkUtils) AssignIPToInterface(interfaceName string, ipAddresses []net.IPNet) error {
var err error
// Assign IP address to container network interface.
for i, ipAddr := range ipAddresses {
logger.Info("Adding IP", zap.String("address", ipAddr.String()), zap.String("interfaceName", interfaceName))
err = nu.netlink.AddIPAddress(interfaceName, ipAddr.IP, &ipAddresses[i])
if err != nil {
return newErrorNetworkUtils(err.Error())
}
}
return nil
}
func (nu NetworkUtils) addOrDeleteFilterRule(iptablesClient ipTablesClient, bridgeName, action, ipAddress, chainName, target string) error {
var err error
option := "i"
if chainName == iptables.Output {
option = "o"
}
matchCondition := fmt.Sprintf("-%s %s -d %s", option, bridgeName, ipAddress)
switch action {
case iptables.Insert:
err = iptablesClient.InsertIptableRule(iptables.V4, iptables.Filter, chainName, matchCondition, target)
case iptables.Append:
err = iptablesClient.AppendIptableRule(iptables.V4, iptables.Filter, chainName, matchCondition, target)
case iptables.Delete:
err = iptablesClient.DeleteIptableRule(iptables.V4, iptables.Filter, chainName, matchCondition, target)
}
return err
}
func (nu NetworkUtils) AllowIPAddresses(iptablesClient ipTablesClient, bridgeName string, skipAddresses []string, action string) error {
chains := getFilterChains()
target := getFilterchainTarget()
logger.Info("Addresses to allow", zap.Any("skipAddresses", skipAddresses))
for _, address := range skipAddresses {
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, address, chains[0], target[0]); err != nil {
return err
}
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, address, chains[1], target[0]); err != nil {
return err
}
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, address, chains[2], target[0]); err != nil {
return err
}
}
return nil
}
func (nu NetworkUtils) BlockEgressTrafficFromContainer(iptablesClient ipTablesClient, version, ipAddress, protocol string, port int) error {
// iptables -t filter -I FORWARD -j DROP -d <ip> -p <protocol> -m <protocol> --dport <port>
dropTraffic := fmt.Sprintf("-d %s -p %s -m %s --dport %d", ipAddress, protocol, protocol, port)
return errors.Wrap(iptablesClient.InsertIptableRule(version, iptables.Filter, iptables.Forward, dropTraffic, iptables.Drop), "iptables block traffic failed")
}
func (nu NetworkUtils) BlockIPAddresses(iptablesClient ipTablesClient, bridgeName, action string) error {
privateIPAddresses := getPrivateIPSpace()
chains := getFilterChains()
target := getFilterchainTarget()
logger.Info("Addresses to block", zap.Any("privateIPAddresses", privateIPAddresses))
for _, ipAddress := range privateIPAddresses {
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, ipAddress, chains[0], target[1]); err != nil {
return err
}
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, ipAddress, chains[1], target[1]); err != nil {
return err
}
if err := nu.addOrDeleteFilterRule(iptablesClient, bridgeName, action, ipAddress, chains[2], target[1]); err != nil {
return err
}
}
return nil
}
func (nu NetworkUtils) EnableIPV4Forwarding() error {
_, err := nu.plClient.ExecuteRawCommand(enableIPV4ForwardCmd)
if err != nil {
logger.Error("Enable ipv4 forwarding failed with", zap.Error(err))
return errors.Wrap(err, "enable ipv4 forwarding failed")
}
return nil
}
func (nu NetworkUtils) EnableIPV6Forwarding() error {
cmd := fmt.Sprint(enableIPV6ForwardCmd)
_, err := nu.plClient.ExecuteRawCommand(cmd)
if err != nil {
logger.Error("Enable ipv6 forwarding failed with", zap.Error(err))
return err
}
return nil
}
// This functions enables/disables ipv6 setting based on enable parameter passed.
func (nu NetworkUtils) UpdateIPV6Setting(disable int) error {
// sysctl -w net.ipv6.conf.all.disable_ipv6=0/1
cmd := fmt.Sprintf(toggleIPV6Cmd, disable)
_, err := nu.plClient.ExecuteRawCommand(cmd)
if err != nil {
logger.Error("Update IPV6 Setting failed with", zap.Error(err))
}
return err
}
// This function adds rule which snat to ip passed filtered by match string.
func (nu NetworkUtils) AddSnatRule(iptablesClient ipTablesClient, match string, ip net.IP) error {
version := iptables.V4
if ip.To4() == nil {
version = iptables.V6
}
target := fmt.Sprintf("SNAT --to %s", ip.String())
return errors.Wrap(iptablesClient.InsertIptableRule(version, iptables.Nat, iptables.Postrouting, match, target), "failed to add snat rule")
}
func (nu NetworkUtils) DisableRAForInterface(ifName string) error {
raFilePath := fmt.Sprintf(acceptRAV6File, ifName)
exist, err := platform.CheckIfFileExists(raFilePath)
if !exist {
logger.Error("accept_ra file doesn't exist with", zap.Error(err))
return nil
}
cmd := fmt.Sprintf(disableRACmd, ifName)
out, err := nu.plClient.ExecuteRawCommand(cmd)
if err != nil {
logger.Error("Diabling ra failed with", zap.Error(err), zap.Any("out", out))
}
return err
}
func (nu NetworkUtils) SetProxyArp(ifName string) error {
cmd := fmt.Sprintf("echo 1 > /proc/sys/net/ipv4/conf/%v/proxy_arp", ifName)
_, err := nu.plClient.ExecuteRawCommand(cmd)
return errors.Wrapf(err, "failed to set proxy arp for interface %v", ifName)
}
func getPrivateIPSpace() []string {
privateIPAddresses := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16"}
return privateIPAddresses
}
func getFilterChains() []string {
chains := []string{"FORWARD", "INPUT", "OUTPUT"}
return chains
}
func getFilterchainTarget() []string {
actions := []string{"ACCEPT", "DROP"}
return actions
}