Docker-DCO-1.1-Signed-off-by: William Thurston <me@williamthurston.com> (github: jhspaybar)
| ... | ... |
@@ -88,7 +88,7 @@ func setupNetworking(args *execdriver.InitArgs) error {
|
| 88 | 88 |
return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway)
|
| 89 | 89 |
} |
| 90 | 90 |
|
| 91 |
- if err := netlink.AddDefaultGw(gw); err != nil {
|
|
| 91 |
+ if err := netlink.AddDefaultGw(gw.String(), "eth0"); err != nil {
|
|
| 92 | 92 |
return fmt.Errorf("Unable to set up networking: %v", err)
|
| 93 | 93 |
} |
| 94 | 94 |
} |
| ... | ... |
@@ -46,6 +46,9 @@ type Container struct {
|
| 46 | 46 |
// Networks specifies the container's network setup to be created |
| 47 | 47 |
Networks []*Network `json:"networks,omitempty"` |
| 48 | 48 |
|
| 49 |
+ // Routes can be specified to create entries in the route table as the container is started |
|
| 50 |
+ Routes []*Route `json:"routes,omitempty"` |
|
| 51 |
+ |
|
| 49 | 52 |
// Cgroups specifies specific cgroup settings for the various subsystems that the container is |
| 50 | 53 |
// placed into to limit the resources the container has available |
| 51 | 54 |
Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` |
| ... | ... |
@@ -91,3 +94,24 @@ type Network struct {
|
| 91 | 91 |
// container's interfaces if a pair is created, specifically in the case of type veth |
| 92 | 92 |
Mtu int `json:"mtu,omitempty"` |
| 93 | 93 |
} |
| 94 |
+ |
|
| 95 |
+// Routes can be specified to create entries in the route table as the container is started |
|
| 96 |
+// |
|
| 97 |
+// All of destination, source, and gateway should be either IPv4 or IPv6. |
|
| 98 |
+// One of the three options must be present, and ommitted entries will use their |
|
| 99 |
+// IP family default for the route table. For IPv4 for example, setting the |
|
| 100 |
+// gateway to 1.2.3.4 and the interface to eth0 will set up a standard |
|
| 101 |
+// destination of 0.0.0.0(or *) when viewed in the route table. |
|
| 102 |
+type Route struct {
|
|
| 103 |
+ // Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 |
|
| 104 |
+ Destination string `json:"destination,omitempty"` |
|
| 105 |
+ |
|
| 106 |
+ // Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6 |
|
| 107 |
+ Source string `json:"source,omitempty"` |
|
| 108 |
+ |
|
| 109 |
+ // Sets the gateway. Accepts IPv4 and IPv6 |
|
| 110 |
+ Gateway string `json:"gateway,omitempty"` |
|
| 111 |
+ |
|
| 112 |
+ // The device to set this route up for, for example: eth0 |
|
| 113 |
+ InterfaceName string `json:"interface_name,omitempty"` |
|
| 114 |
+} |
| ... | ... |
@@ -39,6 +39,11 @@ func TestContainerJsonFormat(t *testing.T) {
|
| 39 | 39 |
t.Fail() |
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
+ if len(container.Routes) != 2 {
|
|
| 43 |
+ t.Log("should have found 2 routes")
|
|
| 44 |
+ t.Fail() |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 42 | 47 |
if !container.Namespaces["NEWNET"] {
|
| 43 | 48 |
t.Log("namespaces should contain NEWNET")
|
| 44 | 49 |
t.Fail() |
| ... | ... |
@@ -53,8 +53,8 @@ func SetInterfaceMaster(name, master string) error {
|
| 53 | 53 |
return netlink.AddToBridge(iface, masterIface) |
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 |
-func SetDefaultGateway(ip string) error {
|
|
| 57 |
- return netlink.AddDefaultGw(net.ParseIP(ip)) |
|
| 56 |
+func SetDefaultGateway(ip, ifaceName string) error {
|
|
| 57 |
+ return netlink.AddDefaultGw(ip, ifaceName) |
|
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 | 60 |
func SetInterfaceIp(name string, rawIp string) error {
|
| ... | ... |
@@ -12,6 +12,8 @@ import ( |
| 12 | 12 |
type Veth struct {
|
| 13 | 13 |
} |
| 14 | 14 |
|
| 15 |
+const defaultDevice = "eth0" |
|
| 16 |
+ |
|
| 15 | 17 |
func (v *Veth) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error {
|
| 16 | 18 |
var ( |
| 17 | 19 |
bridge string |
| ... | ... |
@@ -56,21 +58,21 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con |
| 56 | 56 |
if err := InterfaceDown(vethChild); err != nil {
|
| 57 | 57 |
return fmt.Errorf("interface down %s %s", vethChild, err)
|
| 58 | 58 |
} |
| 59 |
- if err := ChangeInterfaceName(vethChild, "eth0"); err != nil {
|
|
| 60 |
- return fmt.Errorf("change %s to eth0 %s", vethChild, err)
|
|
| 59 |
+ if err := ChangeInterfaceName(vethChild, defaultDevice); err != nil {
|
|
| 60 |
+ return fmt.Errorf("change %s to %s %s", vethChild, defaultDevice, err)
|
|
| 61 | 61 |
} |
| 62 |
- if err := SetInterfaceIp("eth0", config.Address); err != nil {
|
|
| 63 |
- return fmt.Errorf("set eth0 ip %s", err)
|
|
| 62 |
+ if err := SetInterfaceIp(defaultDevice, config.Address); err != nil {
|
|
| 63 |
+ return fmt.Errorf("set %s ip %s", defaultDevice, err)
|
|
| 64 | 64 |
} |
| 65 |
- if err := SetMtu("eth0", config.Mtu); err != nil {
|
|
| 66 |
- return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err)
|
|
| 65 |
+ if err := SetMtu(defaultDevice, config.Mtu); err != nil {
|
|
| 66 |
+ return fmt.Errorf("set %s mtu to %d %s", defaultDevice, config.Mtu, err)
|
|
| 67 | 67 |
} |
| 68 |
- if err := InterfaceUp("eth0"); err != nil {
|
|
| 69 |
- return fmt.Errorf("eth0 up %s", err)
|
|
| 68 |
+ if err := InterfaceUp(defaultDevice); err != nil {
|
|
| 69 |
+ return fmt.Errorf("%s up %s", defaultDevice, err)
|
|
| 70 | 70 |
} |
| 71 | 71 |
if config.Gateway != "" {
|
| 72 |
- if err := SetDefaultGateway(config.Gateway); err != nil {
|
|
| 73 |
- return fmt.Errorf("set gateway to %s %s", config.Gateway, err)
|
|
| 72 |
+ if err := SetDefaultGateway(config.Gateway, defaultDevice); err != nil {
|
|
| 73 |
+ return fmt.Errorf("set gateway to %s on device %s failed with %s", config.Gateway, defaultDevice, err)
|
|
| 74 | 74 |
} |
| 75 | 75 |
} |
| 76 | 76 |
return nil |
| ... | ... |
@@ -18,6 +18,7 @@ import ( |
| 18 | 18 |
"github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" |
| 19 | 19 |
"github.com/dotcloud/docker/pkg/libcontainer/security/restrict" |
| 20 | 20 |
"github.com/dotcloud/docker/pkg/libcontainer/utils" |
| 21 |
+ "github.com/dotcloud/docker/pkg/netlink" |
|
| 21 | 22 |
"github.com/dotcloud/docker/pkg/system" |
| 22 | 23 |
"github.com/dotcloud/docker/pkg/user" |
| 23 | 24 |
) |
| ... | ... |
@@ -60,6 +61,9 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string, |
| 60 | 60 |
if err := setupNetwork(container, context); err != nil {
|
| 61 | 61 |
return fmt.Errorf("setup networking %s", err)
|
| 62 | 62 |
} |
| 63 |
+ if err := setupRoute(container); err != nil {
|
|
| 64 |
+ return fmt.Errorf("setup route %s", err)
|
|
| 65 |
+ } |
|
| 63 | 66 |
|
| 64 | 67 |
label.Init() |
| 65 | 68 |
|
| ... | ... |
@@ -168,6 +172,15 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex |
| 168 | 168 |
return nil |
| 169 | 169 |
} |
| 170 | 170 |
|
| 171 |
+func setupRoute(container *libcontainer.Container) error {
|
|
| 172 |
+ for _, config := range container.Routes {
|
|
| 173 |
+ if err := netlink.AddRoute(config.Destination, config.Source, config.Gateway, config.InterfaceName); err != nil {
|
|
| 174 |
+ return err |
|
| 175 |
+ } |
|
| 176 |
+ } |
|
| 177 |
+ return nil |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 171 | 180 |
// FinalizeNamespace drops the caps, sets the correct user |
| 172 | 181 |
// and working dir, and closes any leaky file descriptors |
| 173 | 182 |
// before execing the command inside the namespace |
| ... | ... |
@@ -131,10 +131,9 @@ type RtMsg struct {
|
| 131 | 131 |
syscall.RtMsg |
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
-func newRtMsg(family int) *RtMsg {
|
|
| 134 |
+func newRtMsg() *RtMsg {
|
|
| 135 | 135 |
return &RtMsg{
|
| 136 | 136 |
RtMsg: syscall.RtMsg{
|
| 137 |
- Family: uint8(family), |
|
| 138 | 137 |
Table: syscall.RT_TABLE_MAIN, |
| 139 | 138 |
Scope: syscall.RT_SCOPE_UNIVERSE, |
| 140 | 139 |
Protocol: syscall.RTPROT_BOOT, |
| ... | ... |
@@ -367,40 +366,118 @@ done: |
| 367 | 367 |
return nil |
| 368 | 368 |
} |
| 369 | 369 |
|
| 370 |
-// Add a new default gateway. Identical to: |
|
| 371 |
-// ip route add default via $ip |
|
| 372 |
-func AddDefaultGw(ip net.IP) error {
|
|
| 370 |
+// Add a new route table entry. |
|
| 371 |
+func AddRoute(destination, source, gateway, device string) error {
|
|
| 372 |
+ if destination == "" && source == "" && gateway == "" {
|
|
| 373 |
+ return fmt.Errorf("one of destination, source or gateway must not be blank")
|
|
| 374 |
+ } |
|
| 375 |
+ |
|
| 373 | 376 |
s, err := getNetlinkSocket() |
| 374 | 377 |
if err != nil {
|
| 375 | 378 |
return err |
| 376 | 379 |
} |
| 377 | 380 |
defer s.Close() |
| 378 | 381 |
|
| 379 |
- family := getIpFamily(ip) |
|
| 380 |
- |
|
| 381 | 382 |
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) |
| 383 |
+ msg := newRtMsg() |
|
| 384 |
+ currentFamily := -1 |
|
| 385 |
+ var rtAttrs []*RtAttr |
|
| 382 | 386 |
|
| 383 |
- msg := newRtMsg(family) |
|
| 384 |
- wb.AddData(msg) |
|
| 387 |
+ if destination != "" {
|
|
| 388 |
+ destIP, destNet, err := net.ParseCIDR(destination) |
|
| 389 |
+ if err != nil {
|
|
| 390 |
+ return fmt.Errorf("destination CIDR %s couldn't be parsed", destination)
|
|
| 391 |
+ } |
|
| 392 |
+ destFamily := getIpFamily(destIP) |
|
| 393 |
+ currentFamily = destFamily |
|
| 394 |
+ destLen, bits := destNet.Mask.Size() |
|
| 395 |
+ if destLen == 0 && bits == 0 {
|
|
| 396 |
+ return fmt.Errorf("destination CIDR %s generated a non-canonical Mask", destination)
|
|
| 397 |
+ } |
|
| 398 |
+ msg.Family = uint8(destFamily) |
|
| 399 |
+ msg.Dst_len = uint8(destLen) |
|
| 400 |
+ var destData []byte |
|
| 401 |
+ if destFamily == syscall.AF_INET {
|
|
| 402 |
+ destData = destIP.To4() |
|
| 403 |
+ } else {
|
|
| 404 |
+ destData = destIP.To16() |
|
| 405 |
+ } |
|
| 406 |
+ rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_DST, destData)) |
|
| 407 |
+ } |
|
| 385 | 408 |
|
| 386 |
- var ipData []byte |
|
| 387 |
- if family == syscall.AF_INET {
|
|
| 388 |
- ipData = ip.To4() |
|
| 389 |
- } else {
|
|
| 390 |
- ipData = ip.To16() |
|
| 409 |
+ if source != "" {
|
|
| 410 |
+ srcIP, srcNet, err := net.ParseCIDR(source) |
|
| 411 |
+ if err != nil {
|
|
| 412 |
+ return fmt.Errorf("source CIDR %s couldn't be parsed", source)
|
|
| 413 |
+ } |
|
| 414 |
+ srcFamily := getIpFamily(srcIP) |
|
| 415 |
+ if currentFamily != -1 && currentFamily != srcFamily {
|
|
| 416 |
+ return fmt.Errorf("source and destination ip were not the same IP family")
|
|
| 417 |
+ } |
|
| 418 |
+ currentFamily = srcFamily |
|
| 419 |
+ srcLen, bits := srcNet.Mask.Size() |
|
| 420 |
+ if srcLen == 0 && bits == 0 {
|
|
| 421 |
+ return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source)
|
|
| 422 |
+ } |
|
| 423 |
+ msg.Family = uint8(srcFamily) |
|
| 424 |
+ msg.Src_len = uint8(srcLen) |
|
| 425 |
+ var srcData []byte |
|
| 426 |
+ if srcFamily == syscall.AF_INET {
|
|
| 427 |
+ srcData = srcIP.To4() |
|
| 428 |
+ } else {
|
|
| 429 |
+ srcData = srcIP.To16() |
|
| 430 |
+ } |
|
| 431 |
+ rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData)) |
|
| 391 | 432 |
} |
| 392 | 433 |
|
| 393 |
- gateway := newRtAttr(syscall.RTA_GATEWAY, ipData) |
|
| 434 |
+ if gateway != "" {
|
|
| 435 |
+ gwIP := net.ParseIP(gateway) |
|
| 436 |
+ if gwIP == nil {
|
|
| 437 |
+ return fmt.Errorf("gateway IP %s couldn't be parsed", gateway)
|
|
| 438 |
+ } |
|
| 439 |
+ gwFamily := getIpFamily(gwIP) |
|
| 440 |
+ if currentFamily != -1 && currentFamily != gwFamily {
|
|
| 441 |
+ return fmt.Errorf("gateway, source, and destination ip were not the same IP family")
|
|
| 442 |
+ } |
|
| 443 |
+ msg.Family = uint8(gwFamily) |
|
| 444 |
+ var gwData []byte |
|
| 445 |
+ if gwFamily == syscall.AF_INET {
|
|
| 446 |
+ gwData = gwIP.To4() |
|
| 447 |
+ } else {
|
|
| 448 |
+ gwData = gwIP.To16() |
|
| 449 |
+ } |
|
| 450 |
+ rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_GATEWAY, gwData)) |
|
| 451 |
+ } |
|
| 394 | 452 |
|
| 395 |
- wb.AddData(gateway) |
|
| 453 |
+ wb.AddData(msg) |
|
| 454 |
+ for _, attr := range rtAttrs {
|
|
| 455 |
+ wb.AddData(attr) |
|
| 456 |
+ } |
|
| 396 | 457 |
|
| 397 |
- if err := s.Send(wb); err != nil {
|
|
| 458 |
+ var ( |
|
| 459 |
+ native = nativeEndian() |
|
| 460 |
+ b = make([]byte, 4) |
|
| 461 |
+ ) |
|
| 462 |
+ iface, err := net.InterfaceByName(device) |
|
| 463 |
+ if err != nil {
|
|
| 398 | 464 |
return err |
| 399 | 465 |
} |
| 466 |
+ native.PutUint32(b, uint32(iface.Index)) |
|
| 467 |
+ |
|
| 468 |
+ wb.AddData(newRtAttr(syscall.RTA_OIF, b)) |
|
| 400 | 469 |
|
| 470 |
+ if err := s.Send(wb); err != nil {
|
|
| 471 |
+ return err |
|
| 472 |
+ } |
|
| 401 | 473 |
return s.HandleAck(wb.Seq) |
| 402 | 474 |
} |
| 403 | 475 |
|
| 476 |
+// Add a new default gateway. Identical to: |
|
| 477 |
+// ip route add default via $ip |
|
| 478 |
+func AddDefaultGw(ip, device string) error {
|
|
| 479 |
+ return AddRoute("", "", ip, device)
|
|
| 480 |
+} |
|
| 481 |
+ |
|
| 404 | 482 |
// Bring up a particular network interface |
| 405 | 483 |
func NetworkLinkUp(iface *net.Interface) error {
|
| 406 | 484 |
s, err := getNetlinkSocket() |
| ... | ... |
@@ -27,9 +27,12 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
| 27 | 27 |
return ErrNotImplemented |
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 |
-func AddDefaultGw(ip net.IP) error {
|
|
| 30 |
+func AddRoute(destination, source, gateway, device string) error {
|
|
| 31 | 31 |
return ErrNotImplemented |
| 32 |
+} |
|
| 32 | 33 |
|
| 34 |
+func AddDefaultGw(ip, device string) error {
|
|
| 35 |
+ return ErrNotImplemented |
|
| 33 | 36 |
} |
| 34 | 37 |
|
| 35 | 38 |
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|