Resend packets back to netstack if destined to itself

Add option to redirect packet back to netstack if it's destined to itself.
This fixes the problem where connecting to the local NIC address would
not work, e.g.:
echo bar | nc -l -p 8080 &
echo foo | nc 192.168.0.2 8080

PiperOrigin-RevId: 207995083
Change-Id: I17adc2a04df48bfea711011a5df206326a1fb8ef
This commit is contained in:
Fabricio Voznika 2018-08-08 22:02:09 -07:00 committed by Shentubot
parent dbbe9ec915
commit ea1e39a314
7 changed files with 107 additions and 49 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 {