diff --git a/pkg/tcpip/link/fdbased/endpoint.go b/pkg/tcpip/link/fdbased/endpoint.go index 4e20cfbf8..152d8f0b2 100644 --- a/pkg/tcpip/link/fdbased/endpoint.go +++ b/pkg/tcpip/link/fdbased/endpoint.go @@ -55,10 +55,15 @@ type endpoint struct { // its end of the communication pipe. closed func(*tcpip.Error) - vv *buffer.VectorisedView - iovecs []syscall.Iovec - views []buffer.View - attached bool + vv *buffer.VectorisedView + iovecs []syscall.Iovec + views []buffer.View + dispatcher stack.NetworkDispatcher + + // handleLocal indicates whether packets destined to itself should be + // handled by the netstack internally (true) or be forwarded to the FD + // endpoint (false). + handleLocal bool } // Options specify the details about the fd-based endpoint to be created. @@ -71,6 +76,7 @@ type Options struct { Address tcpip.LinkAddress SaveRestore bool DisconnectOk bool + HandleLocal bool } // New creates a new fd-based endpoint. @@ -100,14 +106,15 @@ func New(opts *Options) tcpip.LinkEndpointID { } e := &endpoint{ - fd: opts.FD, - mtu: opts.MTU, - caps: caps, - closed: opts.ClosedFunc, - addr: opts.Address, - hdrSize: hdrSize, - views: make([]buffer.View, len(BufConfig)), - iovecs: make([]syscall.Iovec, len(BufConfig)), + fd: opts.FD, + mtu: opts.MTU, + caps: caps, + closed: opts.ClosedFunc, + addr: opts.Address, + hdrSize: hdrSize, + views: make([]buffer.View, len(BufConfig)), + iovecs: make([]syscall.Iovec, len(BufConfig)), + handleLocal: opts.HandleLocal, } vv := buffer.NewVectorisedView(0, e.views) e.vv = &vv @@ -117,16 +124,16 @@ func New(opts *Options) tcpip.LinkEndpointID { // Attach launches the goroutine that reads packets from the file descriptor and // dispatches them via the provided dispatcher. func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) { - e.attached = true + e.dispatcher = dispatcher // Link endpoints are not savable. When transportation endpoints are // saved, they stop sending outgoing packets and all incoming packets // are rejected. - go e.dispatchLoop(dispatcher) // S/R-SAFE: See above. + go e.dispatchLoop() // S/R-SAFE: See above. } // IsAttached implements stack.LinkEndpoint.IsAttached. func (e *endpoint) IsAttached() bool { - return e.attached + return e.dispatcher != nil } // MTU implements stack.LinkEndpoint.MTU. It returns the value initialized @@ -153,6 +160,12 @@ func (e *endpoint) LinkAddress() tcpip.LinkAddress { // WritePacket writes outbound packets to the file descriptor. If it is not // currently writable, the packet is dropped. func (e *endpoint) WritePacket(r *stack.Route, hdr *buffer.Prependable, payload buffer.View, protocol tcpip.NetworkProtocolNumber) *tcpip.Error { + if e.handleLocal && r.LocalAddress != "" && r.LocalAddress == r.RemoteAddress { + hdrView := hdr.View() + vv := buffer.NewVectorisedView(len(hdrView)+len(payload), []buffer.View{hdrView, payload}) + e.dispatcher.DeliverNetworkPacket(e, r.RemoteLinkAddress, protocol, &vv) + return nil + } if e.hdrSize > 0 { // Add ethernet header if needed. eth := header.Ethernet(hdr.Prepend(header.EthernetMinimumSize)) @@ -165,7 +178,6 @@ func (e *endpoint) WritePacket(r *stack.Route, hdr *buffer.Prependable, payload if len(payload) == 0 { return rawfile.NonBlockingWrite(e.fd, hdr.UsedBytes()) - } return rawfile.NonBlockingWrite2(e.fd, hdr.UsedBytes(), payload) @@ -198,7 +210,7 @@ func (e *endpoint) allocateViews(bufConfig []int) { } // dispatch reads one packet from the file descriptor and dispatches it. -func (e *endpoint) dispatch(d stack.NetworkDispatcher, largeV buffer.View) (bool, *tcpip.Error) { +func (e *endpoint) dispatch(largeV buffer.View) (bool, *tcpip.Error) { e.allocateViews(BufConfig) n, err := rawfile.BlockingReadv(e.fd, e.iovecs) @@ -234,7 +246,7 @@ func (e *endpoint) dispatch(d stack.NetworkDispatcher, largeV buffer.View) (bool e.vv.SetSize(n) e.vv.TrimFront(e.hdrSize) - d.DeliverNetworkPacket(e, addr, p, e.vv) + e.dispatcher.DeliverNetworkPacket(e, addr, p, e.vv) // Prepare e.views for another packet: release used views. for i := 0; i < used; i++ { @@ -246,10 +258,10 @@ func (e *endpoint) dispatch(d stack.NetworkDispatcher, largeV buffer.View) (bool // dispatchLoop reads packets from the file descriptor in a loop and dispatches // them to the network stack. -func (e *endpoint) dispatchLoop(d stack.NetworkDispatcher) *tcpip.Error { +func (e *endpoint) dispatchLoop() *tcpip.Error { v := buffer.NewView(header.MaxIPPacketSize) for { - cont, err := e.dispatch(d, v) + cont, err := e.dispatch(v) if err != nil || !cont { if e.closed != nil { e.closed(err) diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 25c06cba5..c1480f97b 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -327,8 +327,7 @@ func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remoteLinkAddr tcpip.Lin return } - r := makeRoute(protocol, dst, src, ref) - r.LocalLinkAddress = linkEP.LinkAddress() + r := makeRoute(protocol, dst, src, linkEP.LinkAddress(), ref) r.RemoteLinkAddress = remoteLinkAddr ref.ep.HandlePacket(&r, vv) ref.decRef() diff --git a/pkg/tcpip/stack/route.go b/pkg/tcpip/stack/route.go index 200c39289..423f428df 100644 --- a/pkg/tcpip/stack/route.go +++ b/pkg/tcpip/stack/route.go @@ -50,12 +50,13 @@ type Route struct { // makeRoute initializes a new route. It takes ownership of the provided // reference to a network endpoint. -func makeRoute(netProto tcpip.NetworkProtocolNumber, localAddr, remoteAddr tcpip.Address, ref *referencedNetworkEndpoint) Route { +func makeRoute(netProto tcpip.NetworkProtocolNumber, localAddr, remoteAddr tcpip.Address, localLinkAddr tcpip.LinkAddress, ref *referencedNetworkEndpoint) Route { return Route{ - NetProto: netProto, - LocalAddress: localAddr, - RemoteAddress: remoteAddr, - ref: ref, + NetProto: netProto, + LocalAddress: localAddr, + LocalLinkAddress: localLinkAddr, + RemoteAddress: remoteAddr, + ref: ref, } } @@ -92,6 +93,11 @@ func (r *Route) Resolve(waker *sleep.Waker) *tcpip.Error { nextAddr := r.NextHop if nextAddr == "" { + // Local link address is already known. + if r.RemoteAddress == r.LocalAddress { + r.RemoteLinkAddress = r.LocalLinkAddress + return nil + } nextAddr = r.RemoteAddress } linkAddr, err := r.ref.linkCache.GetLinkAddress(r.ref.nic.ID(), nextAddr, r.LocalAddress, r.NetProto, waker) diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index fa7aeb051..6c4aa7cc5 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -687,7 +687,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n remoteAddr = ref.ep.ID().LocalAddress } - r := makeRoute(netProto, ref.ep.ID().LocalAddress, remoteAddr, ref) + r := makeRoute(netProto, ref.ep.ID().LocalAddress, remoteAddr, nic.linkEP.LinkAddress(), ref) r.NextHop = s.routeTable[i].Gateway return r, nil } diff --git a/runsc/boot/network.go b/runsc/boot/network.go index d2b52c823..d702ae74e 100644 --- a/runsc/boot/network.go +++ b/runsc/boot/network.go @@ -134,11 +134,11 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct } linkEP := fdbased.New(&fdbased.Options{ - FD: newFD, - MTU: uint32(link.MTU), - ChecksumOffload: false, - EthernetHeader: true, - Address: tcpip.LinkAddress(generateRndMac()), + FD: newFD, + MTU: uint32(link.MTU), + EthernetHeader: true, + HandleLocal: true, + Address: tcpip.LinkAddress(generateRndMac()), }) log.Infof("Enabling interface %q with id %d on addresses %+v", link.Name, nicID, link.Addresses) diff --git a/runsc/test/integration/integration_test.go b/runsc/test/integration/integration_test.go index 67b58523d..c286e48d2 100644 --- a/runsc/test/integration/integration_test.go +++ b/runsc/test/integration/integration_test.go @@ -31,6 +31,7 @@ import ( "net" "net/http" "os" + "strings" "testing" "time" @@ -54,36 +55,36 @@ func httpRequestSucceeds(client http.Client, server string, port int) error { // TestLifeCycle tests a basic Create/Start/Stop docker container life cycle. func TestLifeCycle(t *testing.T) { if err := testutil.Pull("nginx"); err != nil { - t.Fatalf("docker pull failed: %v", err) + t.Fatal("docker pull failed:", err) } d := testutil.MakeDocker("lifecycle-test") if err := d.Create("-p", "80", "nginx"); err != nil { - t.Fatalf("docker create failed: %v", err) + t.Fatal("docker create failed:", err) } if err := d.Start(); err != nil { d.CleanUp() - t.Fatalf("docker start failed: %v", err) + t.Fatal("docker start failed:", err) } // Test that container is working port, err := d.FindPort(80) if err != nil { - t.Fatalf("docker.FindPort(80) failed: %v", err) + t.Fatal("docker.FindPort(80) failed: ", err) } if err := testutil.WaitForHTTP(port, 5*time.Second); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) + t.Fatal("WaitForHTTP() timeout:", err) } client := http.Client{Timeout: time.Duration(2 * time.Second)} if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Errorf("http request failed: %v", err) + t.Error("http request failed:", err) } if err := d.Stop(); err != nil { d.CleanUp() - t.Fatalf("docker stop failed: %v", err) + t.Fatal("docker stop failed:", err) } if err := d.Remove(); err != nil { - t.Fatalf("docker rm failed: %v", err) + t.Fatal("docker rm failed:", err) } } @@ -94,7 +95,7 @@ func TestPauseResume(t *testing.T) { } if err := testutil.Pull("google/python-hello"); err != nil { - t.Fatalf("docker pull failed: %v", err) + t.Fatal("docker pull failed:", err) } d := testutil.MakeDocker("pause-resume-test") if out, err := d.Run("-p", "8080", "google/python-hello"); err != nil { @@ -105,22 +106,22 @@ func TestPauseResume(t *testing.T) { // Find where port 8080 is mapped to. port, err := d.FindPort(8080) if err != nil { - t.Fatalf("docker.FindPort(8080) failed: %v", err) + t.Fatal("docker.FindPort(8080) failed:", err) } // Wait until it's up and running. if err := testutil.WaitForHTTP(port, 20*time.Second); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) + t.Fatal("WaitForHTTP() timeout:", err) } // Check that container is working. client := http.Client{Timeout: time.Duration(2 * time.Second)} if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Errorf("http request failed: %v", err) + t.Error("http request failed:", err) } if err := d.Pause(); err != nil { - t.Fatalf("docker pause failed: %v", err) + t.Fatal("docker pause failed:", err) } // Check if container is paused. @@ -136,17 +137,50 @@ func TestPauseResume(t *testing.T) { } if err := d.Unpause(); err != nil { - t.Fatalf("docker unpause failed: %v", err) + t.Fatal("docker unpause failed:", err) } // Wait until it's up and running. if err := testutil.WaitForHTTP(port, 20*time.Second); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) + t.Fatal("WaitForHTTP() timeout:", err) } // Check if container is working again. if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Errorf("http request failed: %v", err) + t.Error("http request failed:", err) + } +} + +// Create client and server that talk to each other using the local IP. +func TestConnectToSelf(t *testing.T) { + d := testutil.MakeDocker("connect-to-self-test") + + // Creates server that replies "server" and exists. Sleeps at the end because + // 'docker exec' gets killed if the init process exists before it can finish. + if _, err := d.Run("ubuntu:trusty", "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil { + t.Fatal("docker run failed:", err) + } + defer d.CleanUp() + + // Finds IP address for eth0. + ip, err := d.Exec("/bin/sh", "-c", "ifconfig eth0 | grep -E -o \".*inet [^ ]+\" | cut -d: -f2") + if err != nil { + t.Fatal("docker exec failed:", err) + } + ip = strings.TrimRight(ip, "\n") + + // Runs client that sends "client" to the server and exits. + reply, err := d.Exec("/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip)) + if err != nil { + t.Fatal("docker exec failed:", err) + } + + // Ensure both client and server got the message from each other. + if want := "server\n"; reply != want { + t.Errorf("Error on server, want: %q, got: %q", want, reply) + } + if err := d.WaitForOutput("^client\n$", 1*time.Second); err != nil { + t.Fatal("docker.WaitForOutput(client) timeout:", err) } } diff --git a/runsc/test/testutil/docker.go b/runsc/test/testutil/docker.go index 6825ed9ec..b7d60e712 100644 --- a/runsc/test/testutil/docker.go +++ b/runsc/test/testutil/docker.go @@ -162,6 +162,13 @@ func (d *Docker) Run(args ...string) (string, error) { return do(a...) } +// Exec calls 'docker exec' with the arguments provided. +func (d *Docker) Exec(args ...string) (string, error) { + a := []string{"exec", d.Name} + a = append(a, args...) + return do(a...) +} + // Pause calls 'docker pause'. func (d *Docker) Pause() error { if _, err := do("pause", d.Name); err != nil {