Signed-off-by: Alessandro Boch <aboch@docker.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,106 @@ |
| 0 |
+// Package ipamutils provides utililty functions for ipam management |
|
| 1 |
+package ipamutils |
|
| 2 |
+ |
|
| 3 |
+import ( |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "net" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/libnetwork/netutils" |
|
| 8 |
+ "github.com/docker/libnetwork/resolvconf" |
|
| 9 |
+ "github.com/vishvananda/netlink" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+var ( |
|
| 13 |
+ // PredefinedBroadNetworks contains a list of 31 IPv4 private networks with host size 16 and 12 |
|
| 14 |
+ // (172.17-31.x.x/16, 192.168.x.x/20) which do not overlap with the networks in `PredefinedGranularNetworks` |
|
| 15 |
+ PredefinedBroadNetworks []*net.IPNet |
|
| 16 |
+ // PredefinedGranularNetworks contains a list of 64K IPv4 private networks with host size 8 |
|
| 17 |
+ // (10.x.x.x/24) which do not overlap with the networks in `PredefinedBroadNetworks` |
|
| 18 |
+ PredefinedGranularNetworks []*net.IPNet |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+func init() {
|
|
| 22 |
+ PredefinedBroadNetworks = initBroadPredefinedNetworks() |
|
| 23 |
+ PredefinedGranularNetworks = initGranularPredefinedNetworks() |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// ElectInterfaceAddresses looks for an interface on the OS with the specified name |
|
| 27 |
+// and returns its IPv4 and IPv6 addresses in CIDR form. If the interface does not exist, |
|
| 28 |
+// it chooses from a predifined list the first IPv4 address which does not conflict |
|
| 29 |
+// with other interfaces on the system. |
|
| 30 |
+func ElectInterfaceAddresses(name string) ([]*net.IPNet, []*net.IPNet, error) {
|
|
| 31 |
+ var v4Nets, v6Nets []*net.IPNet |
|
| 32 |
+ |
|
| 33 |
+ link, _ := netlink.LinkByName(name) |
|
| 34 |
+ if link != nil {
|
|
| 35 |
+ v4addr, err := netlink.AddrList(link, netlink.FAMILY_V4) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return nil, nil, err |
|
| 38 |
+ } |
|
| 39 |
+ v6addr, err := netlink.AddrList(link, netlink.FAMILY_V6) |
|
| 40 |
+ if err != nil {
|
|
| 41 |
+ return nil, nil, err |
|
| 42 |
+ } |
|
| 43 |
+ for _, nlAddr := range v4addr {
|
|
| 44 |
+ v4Nets = append(v4Nets, nlAddr.IPNet) |
|
| 45 |
+ } |
|
| 46 |
+ for _, nlAddr := range v6addr {
|
|
| 47 |
+ v6Nets = append(v6Nets, nlAddr.IPNet) |
|
| 48 |
+ } |
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if link == nil || len(v4Nets) == 0 {
|
|
| 52 |
+ // Choose from predifined broad networks |
|
| 53 |
+ v4Net, err := FindAvailableNetwork(PredefinedBroadNetworks) |
|
| 54 |
+ if err != nil {
|
|
| 55 |
+ return nil, nil, err |
|
| 56 |
+ } |
|
| 57 |
+ v4Nets = append(v4Nets, v4Net) |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ return v4Nets, v6Nets, nil |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// FindAvailableNetwork returns a network from the passed list which does not |
|
| 64 |
+// overlap with existing interfaces in the system |
|
| 65 |
+func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) {
|
|
| 66 |
+ // We don't check for an error here, because we don't really care if we |
|
| 67 |
+ // can't read /etc/resolv.conf. So instead we skip the append if resolvConf |
|
| 68 |
+ // is nil. It either doesn't exist, or we can't read it for some reason. |
|
| 69 |
+ var nameservers []string |
|
| 70 |
+ if rc, err := resolvconf.Get(); err == nil {
|
|
| 71 |
+ nameservers = resolvconf.GetNameserversAsCIDR(rc.Content) |
|
| 72 |
+ } |
|
| 73 |
+ for _, nw := range list {
|
|
| 74 |
+ if err := netutils.CheckNameserverOverlaps(nameservers, nw); err == nil {
|
|
| 75 |
+ if err := netutils.CheckRouteOverlaps(nw); err == nil {
|
|
| 76 |
+ return nw, nil |
|
| 77 |
+ } |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ return nil, fmt.Errorf("no available network")
|
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func initBroadPredefinedNetworks() []*net.IPNet {
|
|
| 84 |
+ pl := make([]*net.IPNet, 0, 31) |
|
| 85 |
+ mask := []byte{255, 255, 0, 0}
|
|
| 86 |
+ for i := 17; i < 32; i++ {
|
|
| 87 |
+ pl = append(pl, &net.IPNet{IP: []byte{172, byte(i), 0, 0}, Mask: mask})
|
|
| 88 |
+ } |
|
| 89 |
+ mask20 := []byte{255, 255, 240, 0}
|
|
| 90 |
+ for i := 0; i < 16; i++ {
|
|
| 91 |
+ pl = append(pl, &net.IPNet{IP: []byte{192, 168, byte(i << 4), 0}, Mask: mask20})
|
|
| 92 |
+ } |
|
| 93 |
+ return pl |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+func initGranularPredefinedNetworks() []*net.IPNet {
|
|
| 97 |
+ pl := make([]*net.IPNet, 0, 256*256) |
|
| 98 |
+ mask := []byte{255, 255, 255, 0}
|
|
| 99 |
+ for i := 0; i < 256; i++ {
|
|
| 100 |
+ for j := 0; j < 256; j++ {
|
|
| 101 |
+ pl = append(pl, &net.IPNet{IP: []byte{10, byte(i), byte(j), 0}, Mask: mask})
|
|
| 102 |
+ } |
|
| 103 |
+ } |
|
| 104 |
+ return pl |
|
| 105 |
+} |
| 0 | 106 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,113 @@ |
| 0 |
+package ipamutils |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/libnetwork/testutils" |
|
| 7 |
+ "github.com/docker/libnetwork/types" |
|
| 8 |
+ "github.com/vishvananda/netlink" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func TestGranularPredefined(t *testing.T) {
|
|
| 12 |
+ for _, nw := range PredefinedGranularNetworks {
|
|
| 13 |
+ if ones, bits := nw.Mask.Size(); bits != 32 || ones != 24 {
|
|
| 14 |
+ t.Fatalf("Unexpected size for network in granular list: %v", nw)
|
|
| 15 |
+ } |
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ for _, nw := range PredefinedBroadNetworks {
|
|
| 19 |
+ if ones, bits := nw.Mask.Size(); bits != 32 || (ones != 20 && ones != 16) {
|
|
| 20 |
+ t.Fatalf("Unexpected size for network in broad list: %v", nw)
|
|
| 21 |
+ } |
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func TestNetworkRequest(t *testing.T) {
|
|
| 27 |
+ defer testutils.SetupTestOSContext(t)() |
|
| 28 |
+ _, exp, err := net.ParseCIDR("172.17.0.0/16")
|
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ t.Fatal(err) |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ nw, err := FindAvailableNetwork(PredefinedBroadNetworks) |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ t.Fatal(err) |
|
| 36 |
+ } |
|
| 37 |
+ if !types.CompareIPNet(exp, nw) {
|
|
| 38 |
+ t.Fatalf("exected %s. got %s", exp, nw)
|
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ _, exp, err = net.ParseCIDR("10.0.0.0/24")
|
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ t.Fatal(err) |
|
| 44 |
+ } |
|
| 45 |
+ nw, err = FindAvailableNetwork(PredefinedGranularNetworks) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ t.Fatal(err) |
|
| 48 |
+ } |
|
| 49 |
+ if !types.CompareIPNet(exp, nw) {
|
|
| 50 |
+ t.Fatalf("exected %s. got %s", exp, nw)
|
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ // Add iface and ssert returned address on request |
|
| 54 |
+ createInterface(t, "test", "172.17.42.1/16") |
|
| 55 |
+ |
|
| 56 |
+ _, exp, err = net.ParseCIDR("172.18.0.0/16")
|
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ t.Fatal(err) |
|
| 59 |
+ } |
|
| 60 |
+ nw, err = FindAvailableNetwork(PredefinedBroadNetworks) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ t.Fatal(err) |
|
| 63 |
+ } |
|
| 64 |
+ if !types.CompareIPNet(exp, nw) {
|
|
| 65 |
+ t.Fatalf("exected %s. got %s", exp, nw)
|
|
| 66 |
+ } |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func TestElectInterfaceAddress(t *testing.T) {
|
|
| 70 |
+ defer testutils.SetupTestOSContext(t)() |
|
| 71 |
+ nws := "172.101.202.254/16" |
|
| 72 |
+ createInterface(t, "test", nws) |
|
| 73 |
+ |
|
| 74 |
+ ipv4Nw, ipv6Nw, err := ElectInterfaceAddresses("test")
|
|
| 75 |
+ if err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ if len(ipv4Nw) == 0 {
|
|
| 80 |
+ t.Fatalf("unexpected empty ipv4 network addresses")
|
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ if len(ipv6Nw) == 0 {
|
|
| 84 |
+ t.Fatalf("unexpected empty ipv4 network addresses")
|
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ if nws != ipv4Nw[0].String() {
|
|
| 88 |
+ t.Fatalf("expected %s. got %s", nws, ipv4Nw[0])
|
|
| 89 |
+ } |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func createInterface(t *testing.T, name, nw string) {
|
|
| 93 |
+ // Add interface |
|
| 94 |
+ link := &netlink.Bridge{
|
|
| 95 |
+ LinkAttrs: netlink.LinkAttrs{
|
|
| 96 |
+ Name: "test", |
|
| 97 |
+ }, |
|
| 98 |
+ } |
|
| 99 |
+ bip, err := types.ParseCIDR(nw) |
|
| 100 |
+ if err != nil {
|
|
| 101 |
+ t.Fatal(err) |
|
| 102 |
+ } |
|
| 103 |
+ if err = netlink.LinkAdd(link); err != nil {
|
|
| 104 |
+ t.Fatalf("Failed to create interface via netlink: %v", err)
|
|
| 105 |
+ } |
|
| 106 |
+ if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: bip}); err != nil {
|
|
| 107 |
+ t.Fatal(err) |
|
| 108 |
+ } |
|
| 109 |
+ if err = netlink.LinkSetUp(link); err != nil {
|
|
| 110 |
+ t.Fatal(err) |
|
| 111 |
+ } |
|
| 112 |
+} |