diff --git a/libnetwork/bridge/bridge.go b/libnetwork/bridge/bridge.go index 16fbe9a4d0..f9b9ce9d72 100644 --- a/libnetwork/bridge/bridge.go +++ b/libnetwork/bridge/bridge.go @@ -6,28 +6,76 @@ import ( "github.com/docker/libnetwork" ) -const networkType = "bridgednetwork" +const ( + NetworkType = "simplebridge" + VethPrefix = "veth" +) -type bridgeConfiguration struct { - Subnet net.IPNet +type Configuration struct { + BridgeName string + AddressIPv4 *net.IPNet + FixedCIDR *net.IPNet + FixedCIDRv6 *net.IPNet + EnableIPv6 bool + EnableIPTables bool + EnableIPForwarding bool } func init() { - libnetwork.RegisterNetworkType(networkType, Create, &bridgeConfiguration{}) + libnetwork.RegisterNetworkType(NetworkType, Create, &Configuration{}) } -func Create(config *bridgeConfiguration) (libnetwork.Network, error) { - return &bridgeNetwork{Config: *config}, nil -} +func Create(config *Configuration) (libnetwork.Network, error) { + bridgeIntfc := NewInterface(config) + bridgeSetup := NewBridgeSetup(bridgeIntfc) -type bridgeNetwork struct { - Config bridgeConfiguration -} + // If the bridge interface doesn't exist, we need to start the setup steps + // by creating a new device and assigning it an IPv4 address. + bridgeAlreadyExists := bridgeIntfc.Exists() + if !bridgeAlreadyExists { + bridgeSetup.QueueStep(SetupDevice) + bridgeSetup.QueueStep(SetupBridgeIPv4) + } -func (b *bridgeNetwork) Type() string { - return networkType -} + // Conditionnally queue setup steps depending on configuration values. + for _, step := range []struct { + Condition bool + Fn SetupStep + }{ + // Enable IPv6 on the bridge if required. We do this even for a + // previously existing bridge, as it may be here from a previous + // installation where IPv6 wasn't supported yet and needs to be + // assigned an IPv6 link-local address. + {config.EnableIPv6, SetupBridgeIPv6}, -func (b *bridgeNetwork) Link(name string) ([]*libnetwork.Interface, error) { - return nil, nil + // We ensure that the bridge has the expectedIPv4 and IPv6 addresses in + // the case of a previously existing device. + {bridgeAlreadyExists, SetupVerifyConfiguredAddresses}, + + // Setup the bridge to allocate containers IPv4 addresses in the + // specified subnet. + {config.FixedCIDR != nil, SetupFixedCIDRv4}, + + // Setup the bridge to allocate containers global IPv6 addresses in the + // specified subnet. + {config.FixedCIDRv6 != nil, SetupFixedCIDRv6}, + + // Setup IPTables. + {config.EnableIPTables, SetupIPTables}, + + // Setup IP forwarding. + {config.EnableIPForwarding, SetupIPForwarding}, + } { + if step.Condition { + bridgeSetup.QueueStep(step.Fn) + } + } + + // Apply the prepared list of steps, and abort at the first error. + bridgeSetup.QueueStep(SetupDeviceUp) + if err := bridgeSetup.Apply(); err != nil { + return nil, err + } + + return &bridgeNetwork{*config}, nil } diff --git a/libnetwork/bridge/interface.go b/libnetwork/bridge/interface.go new file mode 100644 index 0000000000..414f42ea9f --- /dev/null +++ b/libnetwork/bridge/interface.go @@ -0,0 +1,48 @@ +package bridge + +import "github.com/vishvananda/netlink" + +const ( + DefaultBridgeName = "docker0" +) + +type Interface struct { + Config *Configuration + Link netlink.Link +} + +func NewInterface(config *Configuration) *Interface { + i := &Interface{ + Config: config, + } + + // Initialize the bridge name to the default if unspecified. + if i.Config.BridgeName == "" { + i.Config.BridgeName = DefaultBridgeName + } + + // Attempt to find an existing bridge named with the specified name. + i.Link, _ = netlink.LinkByName(i.Config.BridgeName) + return i +} + +// Exists indicates if the existing bridge interface exists on the system. +func (i *Interface) Exists() bool { + return i.Link != nil +} + +// Addresses returns a single IPv4 address and all IPv6 addresses for the +// bridge interface. +func (i *Interface) Addresses() (netlink.Addr, []netlink.Addr, error) { + v4addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V4) + if err != nil { + return netlink.Addr{}, nil, err + } + + v6addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V6) + if err != nil { + return netlink.Addr{}, nil, err + } + + return v4addr[0], v6addr, nil +} diff --git a/libnetwork/bridge/network.go b/libnetwork/bridge/network.go new file mode 100644 index 0000000000..4e73ed5270 --- /dev/null +++ b/libnetwork/bridge/network.go @@ -0,0 +1,15 @@ +package bridge + +import "github.com/docker/libnetwork" + +type bridgeNetwork struct { + Config Configuration +} + +func (b *bridgeNetwork) Type() string { + return NetworkType +} + +func (b *bridgeNetwork) Link(name string) ([]*libnetwork.Interface, error) { + return nil, nil +} diff --git a/libnetwork/bridge/setup.go b/libnetwork/bridge/setup.go new file mode 100644 index 0000000000..c0cbbe98ae --- /dev/null +++ b/libnetwork/bridge/setup.go @@ -0,0 +1,35 @@ +package bridge + +type SetupStep func(*Interface) error + +type BridgeSetup struct { + bridge *Interface + steps []SetupStep +} + +func NewBridgeSetup(i *Interface) *BridgeSetup { + return &BridgeSetup{bridge: i} +} + +func (b *BridgeSetup) Apply() error { + for _, fn := range b.steps { + if err := fn(b.bridge); err != nil { + return err + } + } + return nil +} + +func (b *BridgeSetup) QueueStep(step SetupStep) { + b.steps = append(b.steps, step) +} + +//---------------------------------------------------------------------------// + +func SetupIPTables(i *Interface) error { + return nil +} + +func SetupIPForwarding(i *Interface) error { + return nil +} diff --git a/libnetwork/bridge/setup_device.go b/libnetwork/bridge/setup_device.go new file mode 100644 index 0000000000..d2e285b77f --- /dev/null +++ b/libnetwork/bridge/setup_device.go @@ -0,0 +1,53 @@ +package bridge + +import ( + "fmt" + "math/rand" + "net" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/parsers/kernel" + "github.com/vishvananda/netlink" +) + +// SetupDevice create a new bridge interface/ +func SetupDevice(i *Interface) error { + // We only attempt to create the bridge when the requested device name is + // the default one. + if i.Config.BridgeName != DefaultBridgeName { + return fmt.Errorf("bridge device with non default name %q must be created manually", i.Config.BridgeName) + } + + // Set the Interface netlink.Bridge. + i.Link = &netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: i.Config.BridgeName, + }, + } + + // Only set the bridge's MAC address if the kernel version is > 3.3, as it + // was not supported before that. + kv, err := kernel.GetKernelVersion() + if err == nil && (kv.Kernel >= 3 && kv.Major >= 3) { + i.Link.Attrs().HardwareAddr = generateRandomMAC() + log.Debugf("Setting bridge mac address to %s", i.Link.Attrs().HardwareAddr) + } + + // Call out to netlink to create the device. + return netlink.LinkAdd(i.Link) +} + +// SetupDeviceUp ups the given bridge interface. +func SetupDeviceUp(i *Interface) error { + return netlink.LinkSetUp(i.Link) +} + +func generateRandomMAC() net.HardwareAddr { + hw := make(net.HardwareAddr, 6) + for i := 0; i < 6; i++ { + hw[i] = byte(rand.Intn(255)) + } + hw[0] &^= 0x1 // clear multicast bit + hw[0] |= 0x2 // set local assignment bit (IEEE802) + return hw +} diff --git a/libnetwork/bridge/setup_fixedcidrv4.go b/libnetwork/bridge/setup_fixedcidrv4.go new file mode 100644 index 0000000000..bf5dc2701a --- /dev/null +++ b/libnetwork/bridge/setup_fixedcidrv4.go @@ -0,0 +1,16 @@ +package bridge + +import ( + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/daemon/networkdriver/ipallocator" +) + +func SetupFixedCIDRv4(i *Interface) error { + addrv4, _, err := i.Addresses() + if err != nil { + return err + } + + log.Debugf("Using IPv4 subnet: %v", i.Config.FixedCIDR) + return ipallocator.RegisterSubnet(addrv4.IPNet, i.Config.FixedCIDR) +} diff --git a/libnetwork/bridge/setup_fixedcidrv6.go b/libnetwork/bridge/setup_fixedcidrv6.go new file mode 100644 index 0000000000..bb15cf5880 --- /dev/null +++ b/libnetwork/bridge/setup_fixedcidrv6.go @@ -0,0 +1,11 @@ +package bridge + +import ( + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/daemon/networkdriver/ipallocator" +) + +func SetupFixedCIDRv6(i *Interface) error { + log.Debugf("Using IPv6 subnet: %v", i.Config.FixedCIDRv6) + return ipallocator.RegisterSubnet(i.Config.FixedCIDRv6, i.Config.FixedCIDRv6) +} diff --git a/libnetwork/bridge/setup_ipv4.go b/libnetwork/bridge/setup_ipv4.go new file mode 100644 index 0000000000..8068eea748 --- /dev/null +++ b/libnetwork/bridge/setup_ipv4.go @@ -0,0 +1,135 @@ +package bridge + +import ( + "fmt" + "net" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/networkfs/resolvconf" + "github.com/vishvananda/netlink" +) + +var bridgeNetworks []*net.IPNet + +func init() { + // Here we don't follow the convention of using the 1st IP of the range for the gateway. + // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. + // In theory this shouldn't matter - in practice there's bound to be a few scripts relying + // on the internal addressing or other stupid things like that. + // They shouldn't, but hey, let's not break them unless we really have to. + for _, addr := range []string{ + "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 + "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive + "10.1.42.1/16", + "10.42.42.1/16", + "172.16.42.1/24", + "172.16.43.1/24", + "172.16.44.1/24", + "10.0.42.1/24", + "10.0.43.1/24", + "192.168.42.1/24", + "192.168.43.1/24", + "192.168.44.1/24", + } { + ip, net, err := net.ParseCIDR(addr) + if err != nil { + log.Errorf("Failed to parse address %s", addr) + continue + } + net.IP = ip + bridgeNetworks = append(bridgeNetworks, net) + } +} + +func SetupBridgeIPv4(i *Interface) error { + bridgeIPv4, err := electBridgeIPv4(i.Config) + if err != nil { + return err + } + + log.Debugf("Creating bridge interface %q with network %s", i.Config.BridgeName, bridgeIPv4) + return netlink.AddrAdd(i.Link, &netlink.Addr{bridgeIPv4, ""}) +} + +func electBridgeIPv4(config *Configuration) (*net.IPNet, error) { + // Use the requested IPv4 CIDR when available. + if config.AddressIPv4 != nil { + return config.AddressIPv4, nil + } + + // We don't check for an error here, because we don't really care if we + // can't read /etc/resolv.conf. So instead we skip the append if resolvConf + // is nil. It either doesn't exist, or we can't read it for some reason. + nameservers := []string{} + if resolvConf, _ := resolvconf.Get(); resolvConf != nil { + nameservers = append(nameservers, resolvconf.GetNameserversAsCIDR(resolvConf)...) + } + + // Try to automatically elect appropriate brige IPv4 settings. + for _, n := range bridgeNetworks { + if err := checkNameserverOverlaps(nameservers, n); err == nil { + if err := checkRouteOverlaps(n); err == nil { + return n, nil + } + } + } + + return nil, fmt.Errorf("Couldn't find an address range for interface %q", config.BridgeName) +} + +func checkNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { + for _, ns := range nameservers { + _, nsNetwork, err := net.ParseCIDR(ns) + if err != nil { + return err + } + if networkOverlaps(toCheck, nsNetwork) { + return fmt.Errorf("Requested network %s overlaps with name server") + } + } + return nil +} + +func checkRouteOverlaps(toCheck *net.IPNet) error { + networks, err := netlink.RouteList(nil, netlink.FAMILY_V4) + if err != nil { + return err + } + + for _, network := range networks { + // TODO Is that right? + if network.Dst != nil && networkOverlaps(toCheck, network.Dst) { + return fmt.Errorf("Requested network %s overlaps with an existing network") + } + } + return nil +} + +func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { + if len(netX.IP) == len(netY.IP) { + if firstIP, _ := networkRange(netX); netY.Contains(firstIP) { + return true + } + if firstIP, _ := networkRange(netY); netX.Contains(firstIP) { + return true + } + } + return false +} + +func networkRange(network *net.IPNet) (net.IP, net.IP) { + var netIP net.IP + if network.IP.To4() != nil { + netIP = network.IP.To4() + } else if network.IP.To16() != nil { + netIP = network.IP.To16() + } else { + return nil, nil + } + + lastIP := make([]byte, len(netIP), len(netIP)) + for i := 0; i < len(netIP); i++ { + lastIP[i] = netIP[i] | ^network.Mask[i] + } + return netIP.Mask(network.Mask), net.IP(lastIP) +} diff --git a/libnetwork/bridge/setup_ipv6.go b/libnetwork/bridge/setup_ipv6.go new file mode 100644 index 0000000000..ac8642004f --- /dev/null +++ b/libnetwork/bridge/setup_ipv6.go @@ -0,0 +1,32 @@ +package bridge + +import ( + "fmt" + "io/ioutil" + "net" + + "github.com/vishvananda/netlink" +) + +var BridgeIPv6 *net.IPNet + +const BridgeIPv6Str = "fe80::1/64" + +func init() { + // We allow ourselves to panic in this special case because we indicate a + // failure to parse a compile-time define constant. + if ip, netw, err := net.ParseCIDR(BridgeIPv6Str); err == nil { + BridgeIPv6 = &net.IPNet{IP: ip, Mask: netw.Mask} + } else { + panic(fmt.Sprintf("Cannot parse default bridge IPv6 address %q: %v", BridgeIPv6Str, err)) + } +} + +func SetupBridgeIPv6(i *Interface) error { + // Enable IPv6 on the bridge + procFile := "/proc/sys/net/ipv6/conf/" + i.Config.BridgeName + "/disable_ipv6" + if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, 0644); err != nil { + return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err) + } + return netlink.AddrAdd(i.Link, &netlink.Addr{BridgeIPv6, ""}) +} diff --git a/libnetwork/bridge/setup_verify.go b/libnetwork/bridge/setup_verify.go new file mode 100644 index 0000000000..7baf8cf3e2 --- /dev/null +++ b/libnetwork/bridge/setup_verify.go @@ -0,0 +1,26 @@ +package bridge + +import "fmt" + +func SetupVerifyConfiguredAddresses(i *Interface) error { + // Fetch a single IPv4 and a slice of IPv6 addresses from the bridge. + addrv4, addrsv6, err := i.Addresses() + if err != nil { + return err + } + + // Verify that the bridge IPv4 address matches the requested configuration. + if i.Config.AddressIPv4 != nil && !addrv4.IP.Equal(i.Config.AddressIPv4.IP) { + return fmt.Errorf("Bridge IPv4 (%s) does not match requested configuration %s", addrv4.IP, i.Config.AddressIPv4.IP) + } + + // Verify that one of the bridge IPv6 addresses matches the requested + // configuration. + for _, addrv6 := range addrsv6 { + if addrv6.String() == BridgeIPv6.String() { + return nil + } + } + + return fmt.Errorf("Bridge IPv6 addresses do not match the expected bridge configuration %s", BridgeIPv6) +} diff --git a/libnetwork/cmd/test/main.go b/libnetwork/cmd/test/main.go index 151c2e7191..bb2d041068 100644 --- a/libnetwork/cmd/test/main.go +++ b/libnetwork/cmd/test/main.go @@ -10,10 +10,11 @@ import ( ) func main() { - _, net, _ := net.ParseCIDR("192.168.100.1/24") + ip, net, _ := net.ParseCIDR("192.168.100.1/24") + net.IP = ip - options := libnetwork.DriverParams{"Subnet": *net} - netw, err := libnetwork.NewNetwork("bridgednetwork", options) + options := libnetwork.DriverParams{"AddressIPv4": net} + netw, err := libnetwork.NewNetwork("simplebridge", options) if err != nil { log.Fatal(err) } diff --git a/libnetwork/namespace.go b/libnetwork/namespace.go index 99607a090c..c09c3505e3 100644 --- a/libnetwork/namespace.go +++ b/libnetwork/namespace.go @@ -14,6 +14,7 @@ func NewNamespace(path string) (Namespace, error) { } func (n *networkNamespace) AddInterface(i *Interface) error { + // TODO Open pipe, pass fd to child and write serialized Interface on it. if err := reexec(reexecMoveInterface, i.SrcName, i.DstName); err != nil { return err } diff --git a/libnetwork/pkg/options/options.go b/libnetwork/pkg/options/options.go index d5bc063d0f..a128cde770 100644 --- a/libnetwork/pkg/options/options.go +++ b/libnetwork/pkg/options/options.go @@ -45,10 +45,10 @@ func GenerateFromModel(options Generic, model interface{}) (interface{}, error) for name, value := range options { field := res.Elem().FieldByName(name) if !field.IsValid() { - return nil, NoSuchFieldError{name, reflect.TypeOf(model).Name()} + return nil, NoSuchFieldError{name, resType.String()} } if !field.CanSet() { - return nil, CannotSetFieldError{name, reflect.TypeOf(model).Name()} + return nil, CannotSetFieldError{name, resType.String()} } field.Set(reflect.ValueOf(value)) }