Skip to content

Commit 1acb0ed

Browse files
committed
Enable Linux bridge ProxyArpWifi
This change enables the Linux bridge's ProxyArpWiFi feature eliminating the need flood ARP packets. When an endpoint is created the bridge driver already has the data needed to complete arp and fdb table entries. Rather than let the kernel discover this information on its own we populate the arp and fdb tables when the endpoint is configured. All other broadcast traffic will pass normal allowing the administrator to manage it using ebtables. Linux bridge ProxyArpWifi is enabled with: --opt com.docker.network.bridge.proxyarp=1 Dependencies: linux kernel v4.1-rc1 or later(commit 842a9ae08a25671db3d4f689eed68b4d64be15) Updated based on review comments from aboch. Signed-off-by: David Wilder <wilder@us.ibm.com>
1 parent 5dc95a3 commit 1acb0ed

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed

drivers/bridge/bridge.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type networkConfiguration struct {
6060
EnableIPv6 bool
6161
EnableIPMasquerade bool
6262
EnableICC bool
63+
EnableProxyArp bool
6364
Mtu int
6465
DefaultBindingIP net.IP
6566
DefaultBridge bool
@@ -240,6 +241,10 @@ func (c *networkConfiguration) fromLabels(labels map[string]string) error {
240241
if c.DefaultBindingIP = net.ParseIP(value); c.DefaultBindingIP == nil {
241242
return parseErr(label, value, "nil ip")
242243
}
244+
case EnableProxyArp:
245+
if c.EnableProxyArp, err = strconv.ParseBool(value); err != nil {
246+
return parseErr(label, value, err.Error())
247+
}
243248
case netlabel.ContainerIfacePrefix:
244249
c.ContainerIfacePrefix = value
245250
}
@@ -449,6 +454,7 @@ func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error)
449454
config = &networkConfiguration{
450455
EnableICC: true,
451456
EnableIPMasquerade: true,
457+
EnableProxyArp: false,
452458
}
453459
err = config.fromLabels(opt)
454460
case options.Generic:
@@ -1043,6 +1049,49 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
10431049
}
10441050
}
10451051

1052+
// The bridge proxy arp feature requires three things to happen:
1053+
// 1) Set the proxyarpwifi flag on the container side bridge port to disable ARP packet flooding.
1054+
// 2) Generate an arp entry in the host's ARP table mapping the containers IP address to its MAC.
1055+
// 3) Generate a static FDB entry for the containers MAC address.
1056+
1057+
if config.EnableProxyArp {
1058+
1059+
BridgeIF, err := d.nlh.LinkByName(config.BridgeName)
1060+
if err != nil {
1061+
return fmt.Errorf("could not find interface with destination name %s: %v", config.BridgeName, err)
1062+
}
1063+
1064+
err = d.nlh.LinkSetBrProxyArpWiFi(host, true)
1065+
if err != nil {
1066+
return fmt.Errorf("unable to set BridgeProxyArp mode on %s: %v", hostIfName, err)
1067+
}
1068+
1069+
nlnh := &netlink.Neigh{
1070+
IP: endpoint.addr.IP,
1071+
HardwareAddr: endpoint.macAddress,
1072+
}
1073+
1074+
// Generate the permanent arp entry.
1075+
nlnh.State = netlink.NUD_PERMANENT
1076+
nlnh.LinkIndex = BridgeIF.Attrs().Index
1077+
1078+
if err := d.nlh.NeighSet(nlnh); err != nil {
1079+
return fmt.Errorf("Failed to add neighbor entry: %v", err)
1080+
}
1081+
logrus.Debugf("An arp entry has been created: Interface=%s Ip=%s MAC=%s", config.BridgeName, nlnh.IP, nlnh.HardwareAddr)
1082+
1083+
// Generate the static fdb entry.
1084+
nlnh.State = netlink.NUD_NOARP
1085+
nlnh.Flags = netlink.NTF_MASTER
1086+
nlnh.Family = syscall.AF_BRIDGE
1087+
nlnh.LinkIndex = host.Attrs().Index
1088+
1089+
if err := d.nlh.NeighSet(nlnh); err != nil {
1090+
return fmt.Errorf("Failed to add fdb entry: %v", err)
1091+
}
1092+
logrus.Debugf("An fdb entry has been created: Interface=%s MAC=%s", hostIfName, nlnh.HardwareAddr)
1093+
}
1094+
10461095
// Up the host interface after finishing all netlink configuration
10471096
if err = d.nlh.LinkSetUp(host); err != nil {
10481097
return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err)

drivers/bridge/bridge_store.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
137137
nMap["EnableIPv6"] = ncfg.EnableIPv6
138138
nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
139139
nMap["EnableICC"] = ncfg.EnableICC
140+
nMap["EnableProxyArp"] = ncfg.EnableProxyArp
140141
nMap["Mtu"] = ncfg.Mtu
141142
nMap["Internal"] = ncfg.Internal
142143
nMap["DefaultBridge"] = ncfg.DefaultBridge
@@ -201,6 +202,10 @@ func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
201202
ncfg.BridgeIfaceCreator = ifaceCreator(v.(float64))
202203
}
203204

205+
if v, ok := nMap["EnableProxyArp"]; ok {
206+
ncfg.EnableProxyArp = v.(bool)
207+
}
208+
204209
return nil
205210
}
206211

drivers/bridge/bridge_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ func TestCreateFullOptionsLabels(t *testing.T) {
276276
DefaultBridge: "true",
277277
EnableICC: "true",
278278
EnableIPMasquerade: "true",
279+
EnableProxyArp: "true",
279280
DefaultBindingIP: bndIPs,
280281
}
281282

@@ -319,6 +320,10 @@ func TestCreateFullOptionsLabels(t *testing.T) {
319320
t.Fatal("incongruent EnableIPMasquerade in bridge network")
320321
}
321322

323+
if !nw.config.EnableProxyArp {
324+
t.Fatal("incongruent EnableProxyArp in bridge network")
325+
}
326+
322327
bndIP := net.ParseIP(bndIPs)
323328
if !bndIP.Equal(nw.config.DefaultBindingIP) {
324329
t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
@@ -1071,3 +1076,54 @@ func TestCreateWithExistingBridge(t *testing.T) {
10711076
t.Fatal("Deleting bridge network that using existing bridge interface unexpectedly deleted the bridge interface")
10721077
}
10731078
}
1079+
1080+
func TestProxyArp(t *testing.T) {
1081+
if !testutils.IsRunningInContainer() {
1082+
defer testutils.SetupTestOSContext(t)()
1083+
}
1084+
d := newDriver()
1085+
1086+
err := d.configure(nil)
1087+
if err != nil {
1088+
t.Fatalf("Failed to setup driver config: %v", err)
1089+
}
1090+
1091+
netconfig := &networkConfiguration{BridgeName: DefaultBridgeName, EnableProxyArp: true, DefaultBridge: true}
1092+
genericOption := make(map[string]interface{})
1093+
genericOption[netlabel.GenericData] = netconfig
1094+
1095+
ipdList := getIPv4Data(t, "")
1096+
1097+
err = d.CreateNetwork("ProxyArpTest", genericOption, nil, ipdList, nil)
1098+
if err != nil {
1099+
t.Fatalf("Bridge creation failed: %v", err)
1100+
}
1101+
1102+
te := newTestEndpoint(ipdList[0].Pool, 10)
1103+
err = d.CreateEndpoint("ProxyArpTest", "ep", te.Interface(), nil)
1104+
if err != nil {
1105+
t.Fatalf("Failed to create endpoint: %v", err)
1106+
}
1107+
1108+
BridgeIF, err := netlink.LinkByName(DefaultBridgeName)
1109+
if err != nil {
1110+
t.Fatalf("Failed to lookup bridge interface: %v", err)
1111+
}
1112+
1113+
dump, err := netlink.NeighList(BridgeIF.Attrs().Index, 0)
1114+
if err != nil {
1115+
t.Errorf("Failed to NeighList: %v", err)
1116+
}
1117+
1118+
FoundNeigh := 0
1119+
for _, v := range dump {
1120+
if v.State&netlink.NUD_PERMANENT != 0 &&
1121+
bytes.Equal(te.iface.mac, v.HardwareAddr) &&
1122+
te.iface.addr.IP.Equal(v.IP) {
1123+
FoundNeigh++
1124+
}
1125+
}
1126+
if FoundNeigh != 1 {
1127+
t.Errorf("Expected a single match in the neighbor table got %d matches", FoundNeigh)
1128+
}
1129+
}

drivers/bridge/labels.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ const (
1515

1616
// DefaultBridge label
1717
DefaultBridge = "com.docker.network.bridge.default_bridge"
18+
19+
// EnableProxyArp label
20+
EnableProxyArp = "com.docker.network.bridge.proxyarp"
1821
)

0 commit comments

Comments
 (0)