summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--pkg/tcpip/link/fdbased/endpoint.go52
-rw-r--r--pkg/tcpip/stack/nic.go3
-rw-r--r--pkg/tcpip/stack/route.go16
-rw-r--r--pkg/tcpip/stack/stack.go2
-rw-r--r--runsc/boot/network.go10
-rw-r--r--runsc/test/integration/integration_test.go66
-rw-r--r--runsc/test/testutil/docker.go7
7 files changed, 107 insertions, 49 deletions
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 {