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 `