Skip to content

Commit 9ed9b63

Browse files
author
eduards
committed
Arping capabilities for CNM
New config option will allow to configure libnetwork to automatically arping and advertise ARP bindings on container IFup. Signed-off-by: eduards <eduards@plumgrid.com>
1 parent e26439f commit 9ed9b63

3 files changed

Lines changed: 187 additions & 6 deletions

File tree

plugin/driver/config.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
package driver
1616

1717
import (
18-
"github.com/alyu/configparser"
1918
"log"
19+
20+
"github.com/alyu/configparser"
2021
)
2122

2223
var (
23-
vip string
24-
username string
25-
password string
26-
default_vd string
27-
scope string
24+
vip string
25+
username string
26+
password string
27+
default_vd string
28+
scope string
29+
auto_arp bool = false
2830
)
2931

3032
// Read configuration file
@@ -43,6 +45,7 @@ func ReadConfig() {
4345
username = pg_section.ValueOf("pg_username")
4446
password = pg_section.ValueOf("pg_password")
4547
default_vd = pg_section.ValueOf("default_domain")
48+
4649
}
4750

4851
// get Libnetwork config
@@ -51,5 +54,6 @@ func ReadConfig() {
5154
log.Fatal(lib_err)
5255
} else {
5356
scope = lib_section.ValueOf("scope")
57+
auto_arp = lib_section.Exists("auto_arp")
5458
}
5559
}

plugin/driver/driver.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"net"
2424
"net/http"
2525
"os/exec"
26+
"runtime"
2627

2728
Log "github.com/Sirupsen/logrus"
2829
"github.com/docker/libnetwork/drivers/remote/api"
@@ -331,6 +332,23 @@ func (driver *driver) joinEndpoint(w http.ResponseWriter, r *http.Request) {
331332

332333
objectResponse(w, res)
333334
Log.Infof("Join endpoint %s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey)
335+
336+
if auto_arp {
337+
go func(net_ns_path string, pg_ifc_prefix string) {
338+
339+
// Disallow this goroutine to work on any other thread than this one
340+
// since namespace ops (unshare, setns) are done for a single thread, we
341+
// must ensure that the goroutine does not jump from OS thread to thread
342+
runtime.LockOSThread()
343+
defer runtime.UnlockOSThread()
344+
345+
err := RunContainerArping(net_ns_path, pg_ifc_prefix)
346+
if err != nil {
347+
Log.Printf("Error while running arping : %v", err)
348+
}
349+
350+
}(j.SandboxKey, ifname.DstPrefix)
351+
}
334352
}
335353

336354
// leave call

plugin/driver/network_utils.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2016 PLUMgrid
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.package main
14+
15+
package driver
16+
17+
import (
18+
"fmt"
19+
"net"
20+
"strings"
21+
"time"
22+
23+
"github.com/google/gopacket"
24+
"github.com/google/gopacket/layers"
25+
"github.com/google/gopacket/pcap"
26+
27+
Log "github.com/Sirupsen/logrus"
28+
"github.com/eduser25/cni/pkg/ns"
29+
)
30+
31+
// Plumgrid just specifies the prefix, so we will arping on all ifcs
32+
// that match with the prefix given to libnetwork
33+
func RunContainerArping(net_ns_path string, pg_ifc_prefix string) error {
34+
35+
Log.Printf("Attempting to acces the container %s NetNS", net_ns_path)
36+
var netns ns.NetNS
37+
var err error
38+
success := false
39+
40+
for attempts := 0; attempts <= 5; attempts++ {
41+
42+
netns, err = ns.GetNS(net_ns_path)
43+
44+
if err != nil {
45+
Log.Printf("Attempt %d: failed to open netns %s: %v", attempts, net_ns_path, err)
46+
time.Sleep(1 * time.Second)
47+
} else {
48+
success = true
49+
break
50+
}
51+
attempts += 1
52+
}
53+
54+
if !success {
55+
return fmt.Errorf("Attempts exhaused, could not access %s, exiting", net_ns_path)
56+
}
57+
defer netns.Close()
58+
59+
// Let's go in
60+
err = netns.Do(func(_ ns.NetNS) error {
61+
62+
// Get Ifcs on container
63+
ifc_list, err := net.Interfaces()
64+
if err != nil {
65+
return fmt.Errorf("Failed to get interfaces in Netns: %v", err)
66+
}
67+
68+
// Iterate Ifcs
69+
for _, ifc := range ifc_list {
70+
if strings.HasPrefix(ifc.Name, pg_ifc_prefix) {
71+
// Valid PG-created ifc
72+
73+
// TODO we might want to add a retry here to make sure,
74+
// in case the IFC is up but still has no IP, we retry
75+
address_slice, err := ifc.Addrs()
76+
if err != nil {
77+
Log.Errorf("Failed to get Address slice for ifc %s: %v", ifc.Name, err)
78+
continue
79+
}
80+
81+
if len(address_slice) == 0 {
82+
Log.Errorf("Interface %s had no IP address at the moment we checked", ifc.Name)
83+
continue
84+
}
85+
86+
Log.Printf("Attempting raw socket open on %s", ifc.Name)
87+
handle, err := pcap.OpenLive(ifc.Name, 65536, true, pcap.BlockForever)
88+
if err != nil {
89+
return fmt.Errorf("Failed to open raw socket %s : %v", ifc.Name, err)
90+
}
91+
92+
// We will just arping for all IPv4 addresses on this ifc, just in case
93+
for _, ifc_addr := range address_slice {
94+
ip, _, err := net.ParseCIDR(ifc_addr.String())
95+
if err != nil {
96+
Log.Printf("Failed to parse network %s : %v", ifc_addr.String(), err)
97+
continue
98+
}
99+
100+
if ip.To4() == nil {
101+
// ignoring IPV6 addr
102+
continue
103+
}
104+
105+
// Get ARP packet
106+
arp_pkt := getGratuitousArp(ifc.HardwareAddr, ip)
107+
108+
for i := 0; i <= 3; i++ {
109+
Log.Printf("Sending %d GARP on %s : %s - %s",
110+
i, ifc.Name, ifc.HardwareAddr.String(), ip.String())
111+
112+
if err := handle.WritePacketData(arp_pkt); err != nil {
113+
handle.Close()
114+
return fmt.Errorf("Failed to write ARP packet data on %s : %v", ifc.Name, err)
115+
}
116+
time.Sleep(750 * time.Millisecond)
117+
}
118+
}
119+
120+
// Close the socket
121+
handle.Close()
122+
}
123+
}
124+
return nil
125+
})
126+
127+
return err
128+
}
129+
130+
func getGratuitousArp(mac net.HardwareAddr, ip net.IP) []byte {
131+
// Set up all the layers' fields we can.
132+
eth := layers.Ethernet{
133+
SrcMAC: mac,
134+
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
135+
EthernetType: layers.EthernetTypeARP,
136+
}
137+
arp := layers.ARP{
138+
AddrType: layers.LinkTypeEthernet,
139+
Protocol: layers.EthernetTypeIPv4,
140+
HwAddressSize: 6,
141+
ProtAddressSize: 4,
142+
Operation: layers.ARPReply,
143+
SourceHwAddress: mac,
144+
SourceProtAddress: ip.To4(),
145+
DstHwAddress: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
146+
DstProtAddress: ip.To4(),
147+
}
148+
// Set up buffer and options for serialization.
149+
buf := gopacket.NewSerializeBuffer()
150+
151+
opts := gopacket.SerializeOptions{
152+
FixLengths: true,
153+
ComputeChecksums: true,
154+
}
155+
// Send one packet for every address.
156+
gopacket.SerializeLayers(buf, opts, &eth, &arp)
157+
158+
return buf.Bytes()
159+
}

0 commit comments

Comments
 (0)