github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/networking_bridge_linux.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/coreos/go-iptables/iptables"
     8  	hclog "github.com/hashicorp/go-hclog"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  	"github.com/hashicorp/nomad/plugins/drivers"
    11  )
    12  
    13  const (
    14  	// defaultNomadBridgeName is the name of the bridge to use when not set by
    15  	// the client
    16  	defaultNomadBridgeName = "nomad"
    17  
    18  	// bridgeNetworkAllocIfPrefix is the prefix that is used for the interface
    19  	// name created inside of the alloc network which is connected to the bridge
    20  	bridgeNetworkAllocIfPrefix = "eth"
    21  
    22  	// defaultNomadAllocSubnet is the subnet to use for host local ip address
    23  	// allocation when not specified by the client
    24  	defaultNomadAllocSubnet = "172.26.64.0/20" // end 172.26.79.255
    25  
    26  	// cniAdminChainName is the name of the admin iptables chain used to allow
    27  	// forwarding traffic to allocations
    28  	cniAdminChainName = "NOMAD-ADMIN"
    29  )
    30  
    31  // bridgeNetworkConfigurator is a NetworkConfigurator which adds the alloc to a
    32  // shared bridge, configures masquerading for egress traffic and port mapping
    33  // for ingress
    34  type bridgeNetworkConfigurator struct {
    35  	cni         *cniNetworkConfigurator
    36  	allocSubnet string
    37  	bridgeName  string
    38  
    39  	logger hclog.Logger
    40  }
    41  
    42  func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath string, ignorePortMappingHostIP bool) (*bridgeNetworkConfigurator, error) {
    43  	b := &bridgeNetworkConfigurator{
    44  		bridgeName:  bridgeName,
    45  		allocSubnet: ipRange,
    46  		logger:      log,
    47  	}
    48  
    49  	if b.bridgeName == "" {
    50  		b.bridgeName = defaultNomadBridgeName
    51  	}
    52  
    53  	if b.allocSubnet == "" {
    54  		b.allocSubnet = defaultNomadAllocSubnet
    55  	}
    56  
    57  	c, err := newCNINetworkConfiguratorWithConf(log, cniPath, bridgeNetworkAllocIfPrefix, ignorePortMappingHostIP, buildNomadBridgeNetConfig(b.bridgeName, b.allocSubnet))
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	b.cni = c
    62  
    63  	return b, nil
    64  }
    65  
    66  // ensureForwardingRules ensures that a forwarding rule is added to iptables
    67  // to allow traffic inbound to the bridge network
    68  func (b *bridgeNetworkConfigurator) ensureForwardingRules() error {
    69  	ipt, err := iptables.New()
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	if err = ensureChain(ipt, "filter", cniAdminChainName); err != nil {
    75  		return err
    76  	}
    77  
    78  	if err := appendChainRule(ipt, cniAdminChainName, b.generateAdminChainRule()); err != nil {
    79  		return err
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  // ensureChain ensures that the given chain exists, creating it if missing
    86  func ensureChain(ipt *iptables.IPTables, table, chain string) error {
    87  	chains, err := ipt.ListChains(table)
    88  	if err != nil {
    89  		return fmt.Errorf("failed to list iptables chains: %v", err)
    90  	}
    91  	for _, ch := range chains {
    92  		if ch == chain {
    93  			return nil
    94  		}
    95  	}
    96  
    97  	err = ipt.NewChain(table, chain)
    98  
    99  	// if err is for chain already existing return as it is possible another
   100  	// goroutine created it first
   101  	if e, ok := err.(*iptables.Error); ok && e.ExitStatus() == 1 {
   102  		return nil
   103  	}
   104  
   105  	return err
   106  }
   107  
   108  // appendChainRule adds the given rule to the chain
   109  func appendChainRule(ipt *iptables.IPTables, chain string, rule []string) error {
   110  	exists, err := ipt.Exists("filter", chain, rule...)
   111  	if !exists && err == nil {
   112  		err = ipt.Append("filter", chain, rule...)
   113  	}
   114  	return err
   115  }
   116  
   117  // generateAdminChainRule builds the iptables rule that is inserted into the
   118  // CNI admin chain to ensure traffic forwarding to the bridge network
   119  func (b *bridgeNetworkConfigurator) generateAdminChainRule() []string {
   120  	return []string{"-o", b.bridgeName, "-d", b.allocSubnet, "-j", "ACCEPT"}
   121  }
   122  
   123  // Setup calls the CNI plugins with the add action
   124  func (b *bridgeNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
   125  	if err := b.ensureForwardingRules(); err != nil {
   126  		return nil, fmt.Errorf("failed to initialize table forwarding rules: %v", err)
   127  	}
   128  
   129  	return b.cni.Setup(ctx, alloc, spec)
   130  }
   131  
   132  // Teardown calls the CNI plugins with the delete action
   133  func (b *bridgeNetworkConfigurator) Teardown(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
   134  	return b.cni.Teardown(ctx, alloc, spec)
   135  }
   136  
   137  func buildNomadBridgeNetConfig(bridgeName, subnet string) []byte {
   138  	return []byte(fmt.Sprintf(nomadCNIConfigTemplate, bridgeName, subnet, cniAdminChainName))
   139  }
   140  
   141  const nomadCNIConfigTemplate = `{
   142  	"cniVersion": "0.4.0",
   143  	"name": "nomad",
   144  	"plugins": [
   145  		{
   146  			"type": "loopback"
   147  		},
   148  		{
   149  			"type": "bridge",
   150  			"bridge": "%s",
   151  			"ipMasq": true,
   152  			"isGateway": true,
   153  			"forceAddress": true,
   154  			"ipam": {
   155  				"type": "host-local",
   156  				"ranges": [
   157  					[
   158  						{
   159  							"subnet": "%s"
   160  						}
   161  					]
   162  				],
   163  				"routes": [
   164  					{ "dst": "0.0.0.0/0" }
   165  				]
   166  			}
   167  		},
   168  		{
   169  			"type": "firewall",
   170  			"backend": "iptables",
   171  			"iptablesAdminChainName": "%s"
   172  		},
   173  		{
   174  			"type": "portmap",
   175  			"capabilities": {"portMappings": true},
   176  			"snat": true
   177  		}
   178  	]
   179  }
   180  `