azure-container-networking/network/bridge_endpointclient_linux.go

317 строки
10 KiB
Go

package network
import (
"fmt"
"net"
"github.com/Azure/azure-container-networking/ebtables"
"github.com/Azure/azure-container-networking/netio"
"github.com/Azure/azure-container-networking/netlink"
"github.com/Azure/azure-container-networking/network/networkutils"
"github.com/Azure/azure-container-networking/platform"
"go.uber.org/zap"
)
const (
defaultV6VnetCidr = "2001:1234:5678:9abc::/64"
defaultV6HostGw = "fe80::1234:5678:9abc"
defaultHostGwMac = "12:34:56:78:9a:bc"
)
type LinuxBridgeEndpointClient struct {
bridgeName string
hostPrimaryIfName string
hostVethName string
containerVethName string
hostPrimaryMac net.HardwareAddr
containerMac net.HardwareAddr
hostIPAddresses []*net.IPNet
mode string
netlink netlink.NetlinkInterface
plClient platform.ExecClient
netioshim netio.NetIOInterface
nuc networkutils.NetworkUtils
}
func NewLinuxBridgeEndpointClient(
extIf *externalInterface,
hostVethName string,
containerVethName string,
mode string,
nl netlink.NetlinkInterface,
plc platform.ExecClient,
) *LinuxBridgeEndpointClient {
client := &LinuxBridgeEndpointClient{
bridgeName: extIf.BridgeName,
hostPrimaryIfName: extIf.Name,
hostVethName: hostVethName,
containerVethName: containerVethName,
hostPrimaryMac: extIf.MacAddress,
hostIPAddresses: []*net.IPNet{},
mode: mode,
netlink: nl,
plClient: plc,
netioshim: &netio.NetIO{},
}
client.hostIPAddresses = append(client.hostIPAddresses, extIf.IPAddresses...)
client.nuc = networkutils.NewNetworkUtils(nl, plc)
return client
}
func (client *LinuxBridgeEndpointClient) AddEndpoints(epInfo *EndpointInfo) error {
if err := client.nuc.CreateEndpoint(client.hostVethName, client.containerVethName, nil); err != nil {
return err
}
containerIf, err := net.InterfaceByName(client.containerVethName)
if err != nil {
return err
}
client.containerMac = containerIf.HardwareAddr
return nil
}
func (client *LinuxBridgeEndpointClient) AddEndpointRules(epInfo *EndpointInfo) error {
var err error
logger.Info("Setting link master", zap.String("hostVethName", client.hostVethName), zap.String("bridgeName", client.bridgeName))
if err := client.netlink.SetLinkMaster(client.hostVethName, client.bridgeName); err != nil {
return err
}
for _, ipAddr := range epInfo.IPAddresses {
if ipAddr.IP.To4() != nil {
// Add ARP reply rule.
logger.Info("Adding ARP reply rule for IP address", zap.String("address", ipAddr.String()))
if err = ebtables.SetArpReply(ipAddr.IP, client.getArpReplyAddress(client.containerMac), ebtables.Append); err != nil {
return err
}
}
// Add MAC address translation rule.
logger.Info("Adding MAC DNAT rule for IP address", zap.String("address", ipAddr.String()))
if err := ebtables.SetDnatForIPAddress(client.hostPrimaryIfName, ipAddr.IP, client.containerMac, ebtables.Append); err != nil {
return err
}
if client.mode != opModeTunnel && ipAddr.IP.To4() != nil {
logger.Info("Adding static arp for IP address and MAC in VM", zap.String("address", ipAddr.String()), zap.String("MAC", client.containerMac.String()))
linkInfo := netlink.LinkInfo{
Name: client.bridgeName,
IPAddr: ipAddr.IP,
MacAddress: client.containerMac,
}
if err := client.netlink.SetOrRemoveLinkAddress(linkInfo, netlink.ADD, netlink.NUD_PERMANENT); err != nil {
logger.Info("Failed setting arp in vm with", zap.Error(err))
}
}
}
addRuleToRouteViaHost(epInfo)
logger.Info("Setting hairpin for ", zap.String("hostveth", client.hostVethName))
if err := client.netlink.SetLinkHairpin(client.hostVethName, true); err != nil {
logger.Info("Setting up hairpin failed for interface error", zap.String("interfaceName", client.hostVethName), zap.Error(err))
return err
}
return nil
}
func (client *LinuxBridgeEndpointClient) DeleteEndpointRules(ep *endpoint) {
// Delete rules for IP addresses on the container interface.
for _, ipAddr := range ep.IPAddresses {
if ipAddr.IP.To4() != nil {
// Delete ARP reply rule.
logger.Info("Deleting ARP reply rule for IP address on", zap.String("address", ipAddr.String()), zap.String("id", ep.Id))
err := ebtables.SetArpReply(ipAddr.IP, client.getArpReplyAddress(ep.MacAddress), ebtables.Delete)
if err != nil {
logger.Error("Failed to delete ARP reply rule for IP address", zap.String("address", ipAddr.String()), zap.Error(err))
}
}
// Delete MAC address translation rule.
logger.Info("Deleting MAC DNAT rule for IP address on", zap.String("address", ipAddr.String()), zap.String("id", ep.Id))
err := ebtables.SetDnatForIPAddress(client.hostPrimaryIfName, ipAddr.IP, ep.MacAddress, ebtables.Delete)
if err != nil {
logger.Error("Failed to delete MAC DNAT rule for IP address", zap.String("address", ipAddr.String()), zap.Error(err))
}
if client.mode != opModeTunnel && ipAddr.IP.To4() != nil {
logger.Info("Removing static arp for IP address and MAC from VM", zap.String("address", ipAddr.String()), zap.String("MAC", ep.MacAddress.String()))
linkInfo := netlink.LinkInfo{
Name: client.bridgeName,
IPAddr: ipAddr.IP,
MacAddress: ep.MacAddress,
}
err := client.netlink.SetOrRemoveLinkAddress(linkInfo, netlink.REMOVE, netlink.NUD_INCOMPLETE)
if err != nil {
logger.Error("Failed removing arp from vm with", zap.Error(err))
}
}
}
}
// getArpReplyAddress returns the MAC address to use in ARP replies.
func (client *LinuxBridgeEndpointClient) getArpReplyAddress(epMacAddress net.HardwareAddr) net.HardwareAddr {
var macAddress net.HardwareAddr
if client.mode == opModeTunnel {
// In tunnel mode, resolve all IP addresses to the virtual MAC address for hairpinning.
macAddress, _ = net.ParseMAC(virtualMacAddress)
} else {
// Otherwise, resolve to actual MAC address.
macAddress = epMacAddress
}
return macAddress
}
func (client *LinuxBridgeEndpointClient) MoveEndpointsToContainerNS(epInfo *EndpointInfo, nsID uintptr) error {
// Move the container interface to container's network namespace.
logger.Info("Setting link netns", zap.String("containerVethName", client.containerVethName), zap.String("NetNsPath", epInfo.NetNsPath))
if err := client.netlink.SetLinkNetNs(client.containerVethName, nsID); err != nil {
return newErrorLinuxBridgeClient(err.Error())
}
return nil
}
func (client *LinuxBridgeEndpointClient) SetupContainerInterfaces(epInfo *EndpointInfo) error {
if err := client.nuc.SetupContainerInterface(client.containerVethName, epInfo.IfName); err != nil {
return err
}
client.containerVethName = epInfo.IfName
return nil
}
func (client *LinuxBridgeEndpointClient) ConfigureContainerInterfacesAndRoutes(epInfo *EndpointInfo) error {
if err := client.nuc.AssignIPToInterface(client.containerVethName, epInfo.IPAddresses); err != nil {
return err
}
if err := addRoutes(client.netlink, client.netioshim, client.containerVethName, epInfo.Routes); err != nil {
return err
}
if err := client.setupIPV6Routes(epInfo); err != nil {
return err
}
if err := client.setIPV6NeighEntry(epInfo); err != nil {
return err
}
return nil
}
func (client *LinuxBridgeEndpointClient) DeleteEndpoints(ep *endpoint) error {
logger.Info("Deleting veth pair", zap.String("hostIfName", ep.HostIfName), zap.String("interfaceName", ep.IfName))
err := client.netlink.DeleteLink(ep.HostIfName)
if err != nil {
logger.Error("Failed to delete veth pair", zap.String("hostIfName", ep.HostIfName), zap.Error(err))
return err
}
return nil
}
func addRuleToRouteViaHost(epInfo *EndpointInfo) error {
for _, ipAddr := range epInfo.IPsToRouteViaHost {
tableName := "broute"
chainName := "BROUTING"
rule := fmt.Sprintf("-p IPv4 --ip-dst %s -j redirect", ipAddr)
// Check if EB rule exists
logger.Info("Checking if EB rule already exists in table chain", zap.String("rule", rule), zap.String("tableName", tableName), zap.String("chainName", chainName))
exists, err := ebtables.EbTableRuleExists(tableName, chainName, rule)
if err != nil {
logger.Error("Failed to check if EB table rule exists", zap.Error(err))
return err
}
if exists {
// EB rule already exists.
logger.Info("EB rule already exists in table chain", zap.String("rule", rule), zap.String("tableName", tableName), zap.String("chainName", chainName))
} else {
// Add EB rule to route via host.
logger.Info("Adding EB rule to route via host for IP", zap.Any("address", ipAddr))
if err := ebtables.SetBrouteAccept(ipAddr, ebtables.Append); err != nil {
logger.Error("Failed to add EB rule to route via host with", zap.Error(err))
return err
}
}
}
return nil
}
func (client *LinuxBridgeEndpointClient) setupIPV6Routes(epInfo *EndpointInfo) error {
if epInfo.IPV6Mode != "" {
if epInfo.VnetCidrs == "" {
epInfo.VnetCidrs = defaultV6VnetCidr
}
routes := []RouteInfo{}
_, v6IpNet, _ := net.ParseCIDR(epInfo.VnetCidrs)
v6Gw := net.ParseIP(defaultV6HostGw)
vnetRoute := RouteInfo{
Dst: *v6IpNet,
Gw: v6Gw,
Priority: 101,
}
var vmV6Route RouteInfo
for _, ipAddr := range client.hostIPAddresses {
if ipAddr.IP.To4() == nil {
vmV6Route = RouteInfo{
Dst: *ipAddr,
Priority: 100,
}
}
}
_, defIPNet, _ := net.ParseCIDR("::/0")
defaultV6Route := RouteInfo{
Dst: *defIPNet,
Gw: v6Gw,
}
routes = append(routes, vnetRoute)
routes = append(routes, vmV6Route)
routes = append(routes, defaultV6Route)
logger.Info("Adding ipv6 routes in", zap.Any("container", routes))
if err := addRoutes(client.netlink, client.netioshim, client.containerVethName, routes); err != nil {
return nil
}
}
return nil
}
func (client *LinuxBridgeEndpointClient) setIPV6NeighEntry(epInfo *EndpointInfo) error {
if epInfo.IPV6Mode != "" {
logger.Info("Add neigh entry for host gw ip")
hardwareAddr, _ := net.ParseMAC(defaultHostGwMac)
hostGwIp := net.ParseIP(defaultV6HostGw)
linkInfo := netlink.LinkInfo{
Name: client.containerVethName,
IPAddr: hostGwIp,
MacAddress: hardwareAddr,
}
if err := client.netlink.SetOrRemoveLinkAddress(linkInfo, netlink.ADD, netlink.NUD_PERMANENT); err != nil {
logger.Error("Failed setting neigh entry in", zap.Any("container", err.Error()))
return err
}
}
return nil
}