diff options
author | Tamir Duberstein <tamird@google.com> | 2018-08-31 10:22:06 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-08-31 10:23:32 -0700 |
commit | 625edb9f2864c1b3aebc6b4c9828197131c2e003 (patch) | |
tree | 8c68bfa3f7f43c14a5de0206a2d7e66487dc1ae7 | |
parent | 08bfb5643c4d4755d0e982e69d2da99449e25c57 (diff) |
ipv6: ICMP support
This CL does NDP link-address discovery for IPv6.
It includes several small changes necessary to get linux to talk to
this implementation. In particular, a hop limit of 255 is necessary
for ICMPv6.
PiperOrigin-RevId: 211103930
Change-Id: If25370ab84c6b1decfb15de917f3b0020f2c4e0e
-rw-r--r-- | pkg/tcpip/link/channel/channel.go | 6 | ||||
-rw-r--r-- | pkg/tcpip/link/sniffer/sniffer.go | 31 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/BUILD | 19 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp.go | 137 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/icmp_test.go | 236 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv6/ipv6.go | 30 | ||||
-rw-r--r-- | pkg/tcpip/stack/nic.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/transport/ping/endpoint.go | 38 | ||||
-rw-r--r-- | pkg/tcpip/transport/ping/protocol.go | 16 |
10 files changed, 490 insertions, 27 deletions
diff --git a/pkg/tcpip/link/channel/channel.go b/pkg/tcpip/link/channel/channel.go index 9d69f1d45..dd3fe7c87 100644 --- a/pkg/tcpip/link/channel/channel.go +++ b/pkg/tcpip/link/channel/channel.go @@ -71,6 +71,12 @@ func (e *Endpoint) Inject(protocol tcpip.NetworkProtocolNumber, vv *buffer.Vecto e.dispatcher.DeliverNetworkPacket(e, "", protocol, &uu) } +// InjectLinkAddr injects an inbound packet with a remote link address. +func (e *Endpoint) InjectLinkAddr(protocol tcpip.NetworkProtocolNumber, remoteLinkAddr tcpip.LinkAddress, vv *buffer.VectorisedView) { + uu := vv.Clone(nil) + e.dispatcher.DeliverNetworkPacket(e, remoteLinkAddr, protocol, &uu) +} + // Attach saves the stack network-layer dispatcher for use later when packets // are injected. func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) { diff --git a/pkg/tcpip/link/sniffer/sniffer.go b/pkg/tcpip/link/sniffer/sniffer.go index 414a7edb4..1e302f557 100644 --- a/pkg/tcpip/link/sniffer/sniffer.go +++ b/pkg/tcpip/link/sniffer/sniffer.go @@ -303,6 +303,37 @@ func logPacket(prefix string, protocol tcpip.NetworkProtocolNumber, b, plb []byt log.Infof("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code()) return + case header.ICMPv6ProtocolNumber: + transName = "icmp" + icmp := header.ICMPv6(b) + icmpType := "unknown" + switch icmp.Type() { + case header.ICMPv6DstUnreachable: + icmpType = "destination unreachable" + case header.ICMPv6PacketTooBig: + icmpType = "packet too big" + case header.ICMPv6TimeExceeded: + icmpType = "time exceeded" + case header.ICMPv6ParamProblem: + icmpType = "param problem" + case header.ICMPv6EchoRequest: + icmpType = "echo request" + case header.ICMPv6EchoReply: + icmpType = "echo reply" + case header.ICMPv6RouterSolicit: + icmpType = "router solicit" + case header.ICMPv6RouterAdvert: + icmpType = "router advert" + case header.ICMPv6NeighborSolicit: + icmpType = "neighbor solicit" + case header.ICMPv6NeighborAdvert: + icmpType = "neighbor advert" + case header.ICMPv6RedirectMsg: + icmpType = "redirect message" + } + log.Infof("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code()) + return + case header.UDPProtocolNumber: transName = "udp" udp := header.UDP(b) diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index e6b1f5128..512ef0d60 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -67,7 +67,7 @@ func newEndpoint(nicid tcpip.NICID, addr tcpip.Address, dispatcher stack.Transpo fragmentation: fragmentation.NewFragmentation(fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, fragmentation.DefaultReassembleTimeout), } copy(e.address[:], addr) - e.id = stack.NetworkEndpointID{tcpip.Address(e.address[:])} + e.id = stack.NetworkEndpointID{LocalAddress: tcpip.Address(e.address[:])} go e.echoReplier() diff --git a/pkg/tcpip/network/ipv6/BUILD b/pkg/tcpip/network/ipv6/BUILD index 1c3eccae0..2f19a659e 100644 --- a/pkg/tcpip/network/ipv6/BUILD +++ b/pkg/tcpip/network/ipv6/BUILD @@ -1,6 +1,6 @@ package(licenses = ["notice"]) # Apache 2.0 -load("//tools/go_stateify:defs.bzl", "go_library") +load("//tools/go_stateify:defs.bzl", "go_library", "go_test") go_library( name = "ipv6", @@ -19,3 +19,20 @@ go_library( "//pkg/tcpip/stack", ], ) + +go_test( + name = "ipv6_test", + size = "small", + srcs = ["icmp_test.go"], + embed = [":ipv6"], + deps = [ + "//pkg/tcpip", + "//pkg/tcpip/buffer", + "//pkg/tcpip/header", + "//pkg/tcpip/link/channel", + "//pkg/tcpip/link/sniffer", + "//pkg/tcpip/stack", + "//pkg/tcpip/transport/ping", + "//pkg/waiter", + ], +) diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go index 8b8539def..6db004baa 100644 --- a/pkg/tcpip/network/ipv6/icmp.go +++ b/pkg/tcpip/network/ipv6/icmp.go @@ -17,6 +17,7 @@ package ipv6 import ( "encoding/binary" + "gvisor.googlesource.com/gvisor/pkg/tcpip" "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer" "gvisor.googlesource.com/gvisor/pkg/tcpip/header" "gvisor.googlesource.com/gvisor/pkg/tcpip/stack" @@ -86,5 +87,141 @@ func (e *endpoint) handleICMP(r *stack.Route, vv *buffer.VectorisedView) { case header.ICMPv6PortUnreachable: e.handleControl(stack.ControlPortUnreachable, 0, vv) } + + case header.ICMPv6NeighborSolicit: + if len(v) < header.ICMPv6NeighborSolicitMinimumSize { + return + } + targetAddr := tcpip.Address(v[8 : 8+16]) + if e.linkAddrCache.CheckLocalAddress(e.nicid, ProtocolNumber, targetAddr) == 0 { + // We don't have a useful answer; the best we can do is ignore the request. + return + } + hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6NeighborAdvertSize) + pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborAdvertSize)) + pkt.SetType(header.ICMPv6NeighborAdvert) + pkt[icmpV6FlagOffset] = ndpSolicitedFlag | ndpOverrideFlag + copy(pkt[icmpV6OptOffset-len(targetAddr):], targetAddr) + pkt[icmpV6OptOffset] = ndpOptDstLinkAddr + pkt[icmpV6LengthOffset] = 1 + copy(pkt[icmpV6LengthOffset+1:], r.LocalLinkAddress[:]) + pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, nil)) + r.WritePacket(&hdr, nil, header.ICMPv6ProtocolNumber) + + e.linkAddrCache.AddLinkAddress(e.nicid, r.RemoteAddress, r.RemoteLinkAddress) + + case header.ICMPv6NeighborAdvert: + if len(v) < header.ICMPv6NeighborAdvertSize { + return + } + targetAddr := tcpip.Address(v[8 : 8+16]) + e.linkAddrCache.AddLinkAddress(e.nicid, targetAddr, r.RemoteLinkAddress) + if targetAddr != r.RemoteAddress { + e.linkAddrCache.AddLinkAddress(e.nicid, r.RemoteAddress, r.RemoteLinkAddress) + } + + case header.ICMPv6EchoRequest: + if len(v) < header.ICMPv6EchoMinimumSize { + return + } + vv.TrimFront(header.ICMPv6EchoMinimumSize) + data := vv.ToView() + hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6EchoMinimumSize) + pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize)) + copy(pkt, h) + pkt.SetType(header.ICMPv6EchoReply) + pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, data)) + r.WritePacket(&hdr, data, header.ICMPv6ProtocolNumber) + + case header.ICMPv6EchoReply: + if len(v) < header.ICMPv6EchoMinimumSize { + return + } + e.dispatcher.DeliverTransportPacket(r, header.ICMPv6ProtocolNumber, vv) + } } + +const ( + ndpSolicitedFlag = 1 << 6 + ndpOverrideFlag = 1 << 5 + + ndpOptSrcLinkAddr = 1 + ndpOptDstLinkAddr = 2 + + icmpV6FlagOffset = 4 + icmpV6OptOffset = 24 + icmpV6LengthOffset = 25 + + icmpV6HopLimit = 255 +) + +// solicitedNodeAddr computes the solicited-node multicast address. +// This is used for NDP. Described in RFC 4291. +func solicitedNodeAddr(addr tcpip.Address) tcpip.Address { + const solicitedNodeMulticastPrefix = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff" + return solicitedNodeMulticastPrefix + addr[len(addr)-3:] +} + +var broadcastMAC = tcpip.LinkAddress([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + +var _ stack.LinkAddressResolver = (*protocol)(nil) + +// LinkAddressProtocol implements stack.LinkAddressResolver. +func (*protocol) LinkAddressProtocol() tcpip.NetworkProtocolNumber { + return header.IPv6ProtocolNumber +} + +// LinkAddressRequest implements stack.LinkAddressResolver. +func (*protocol) LinkAddressRequest(addr, localAddr tcpip.Address, linkEP stack.LinkEndpoint) *tcpip.Error { + snaddr := solicitedNodeAddr(addr) + r := &stack.Route{ + LocalAddress: localAddr, + RemoteAddress: snaddr, + RemoteLinkAddress: broadcastMAC, + } + hdr := buffer.NewPrependable(int(linkEP.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6NeighborAdvertSize) + pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborAdvertSize)) + pkt.SetType(header.ICMPv6NeighborSolicit) + copy(pkt[icmpV6OptOffset-len(addr):], addr) + pkt[icmpV6OptOffset] = ndpOptSrcLinkAddr + pkt[icmpV6LengthOffset] = 1 + copy(pkt[icmpV6LengthOffset+1:], linkEP.LinkAddress()) + pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, nil)) + + length := uint16(hdr.UsedLength()) + ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) + ip.Encode(&header.IPv6Fields{ + PayloadLength: length, + NextHeader: uint8(header.ICMPv6ProtocolNumber), + HopLimit: icmpV6HopLimit, + SrcAddr: r.LocalAddress, + DstAddr: r.RemoteAddress, + }) + + return linkEP.WritePacket(r, &hdr, nil, ProtocolNumber) +} + +// ResolveStaticAddress implements stack.LinkAddressResolver. +func (*protocol) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) { + return "", false +} + +func icmpChecksum(h header.ICMPv6, src, dst tcpip.Address, data []byte) uint16 { + // Calculate the IPv6 pseudo-header upper-layer checksum. + xsum := header.Checksum([]byte(src), 0) + xsum = header.Checksum([]byte(dst), xsum) + var upperLayerLength [4]byte + binary.BigEndian.PutUint32(upperLayerLength[:], uint32(len(h)+len(data))) + xsum = header.Checksum(upperLayerLength[:], xsum) + xsum = header.Checksum([]byte{0, 0, 0, uint8(header.ICMPv6ProtocolNumber)}, xsum) + xsum = header.Checksum(data, xsum) + + // h[2:4] is the checksum itself, set it aside to avoid checksumming the checksum. + h2, h3 := h[2], h[3] + h[2], h[3] = 0, 0 + xsum = ^header.Checksum(h, xsum) + h[2], h[3] = h2, h3 + + return xsum +} diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go new file mode 100644 index 000000000..582bbb40e --- /dev/null +++ b/pkg/tcpip/network/ipv6/icmp_test.go @@ -0,0 +1,236 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipv6 + +import ( + "context" + "runtime" + "strings" + "testing" + "time" + + "gvisor.googlesource.com/gvisor/pkg/tcpip" + "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer" + "gvisor.googlesource.com/gvisor/pkg/tcpip/header" + "gvisor.googlesource.com/gvisor/pkg/tcpip/link/channel" + "gvisor.googlesource.com/gvisor/pkg/tcpip/link/sniffer" + "gvisor.googlesource.com/gvisor/pkg/tcpip/stack" + "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/ping" + "gvisor.googlesource.com/gvisor/pkg/waiter" +) + +const ( + linkAddr0 = tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06") + linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0f") +) + +// linkLocalAddr computes the default IPv6 link-local address from +// a link-layer (MAC) address. +func linkLocalAddr(linkAddr tcpip.LinkAddress) tcpip.Address { + // Convert a 48-bit MAC to an EUI-64 and then prepend the + // link-local header, FE80::. + // + // The conversion is very nearly: + // aa:bb:cc:dd:ee:ff => FE80::Aabb:ccFF:FEdd:eeff + // Note the capital A. The conversion aa->Aa involves a bit flip. + lladdrb := [16]byte{ + 0: 0xFE, + 1: 0x80, + 8: linkAddr[0] ^ 2, + 9: linkAddr[1], + 10: linkAddr[2], + 11: 0xFF, + 12: 0xFE, + 13: linkAddr[3], + 14: linkAddr[4], + 15: linkAddr[5], + } + return tcpip.Address(lladdrb[:]) +} + +var ( + lladdr0 = linkLocalAddr(linkAddr0) + lladdr1 = linkLocalAddr(linkAddr1) +) + +type testContext struct { + t *testing.T + s0 *stack.Stack + s1 *stack.Stack + + linkEP0 *channel.Endpoint + linkEP1 *channel.Endpoint + + icmpCh chan header.ICMPv6Type +} + +type endpointWithResolutionCapability struct { + stack.LinkEndpoint +} + +func (e endpointWithResolutionCapability) Capabilities() stack.LinkEndpointCapabilities { + return e.LinkEndpoint.Capabilities() | stack.CapabilityResolutionRequired +} + +func newTestContext(t *testing.T) *testContext { + c := &testContext{ + t: t, + s0: stack.New([]string{ProtocolName}, []string{ping.ProtocolName6}, stack.Options{}), + s1: stack.New([]string{ProtocolName}, []string{ping.ProtocolName6}, stack.Options{}), + icmpCh: make(chan header.ICMPv6Type, 10), + } + + const defaultMTU = 65536 + _, linkEP0 := channel.New(256, defaultMTU, linkAddr0) + c.linkEP0 = linkEP0 + wrappedEP0 := endpointWithResolutionCapability{LinkEndpoint: linkEP0} + id0 := stack.RegisterLinkEndpoint(wrappedEP0) + if testing.Verbose() { + id0 = sniffer.New(id0) + } + if err := c.s0.CreateNIC(1, id0); err != nil { + t.Fatalf("CreateNIC s0: %v", err) + } + if err := c.s0.AddAddress(1, ProtocolNumber, lladdr0); err != nil { + t.Fatalf("AddAddress lladdr0: %v", err) + } + if err := c.s0.AddAddress(1, ProtocolNumber, solicitedNodeAddr(lladdr0)); err != nil { + t.Fatalf("AddAddress sn lladdr0: %v", err) + } + + _, linkEP1 := channel.New(256, defaultMTU, linkAddr1) + c.linkEP1 = linkEP1 + wrappedEP1 := endpointWithResolutionCapability{LinkEndpoint: linkEP1} + id1 := stack.RegisterLinkEndpoint(wrappedEP1) + if err := c.s1.CreateNIC(1, id1); err != nil { + t.Fatalf("CreateNIC failed: %v", err) + } + if err := c.s1.AddAddress(1, ProtocolNumber, lladdr1); err != nil { + t.Fatalf("AddAddress lladdr1: %v", err) + } + if err := c.s1.AddAddress(1, ProtocolNumber, solicitedNodeAddr(lladdr1)); err != nil { + t.Fatalf("AddAddress sn lladdr1: %v", err) + } + + c.s0.SetRouteTable( + []tcpip.Route{{ + Destination: lladdr1, + Mask: tcpip.Address(strings.Repeat("\xff", 16)), + NIC: 1, + }}, + ) + c.s1.SetRouteTable( + []tcpip.Route{{ + Destination: lladdr0, + Mask: tcpip.Address(strings.Repeat("\xff", 16)), + NIC: 1, + }}, + ) + + go c.routePackets(linkEP0.C, linkEP1) + go c.routePackets(linkEP1.C, linkEP0) + + return c +} + +func (c *testContext) countPacket(pkt channel.PacketInfo) { + if pkt.Proto != ProtocolNumber { + return + } + ipv6 := header.IPv6(pkt.Header) + transProto := tcpip.TransportProtocolNumber(ipv6.NextHeader()) + if transProto != header.ICMPv6ProtocolNumber { + return + } + b := pkt.Header[header.IPv6MinimumSize:] + icmp := header.ICMPv6(b) + c.icmpCh <- icmp.Type() +} + +func (c *testContext) routePackets(ch <-chan channel.PacketInfo, ep *channel.Endpoint) { + for pkt := range ch { + c.countPacket(pkt) + views := []buffer.View{pkt.Header, pkt.Payload} + size := len(pkt.Header) + len(pkt.Payload) + vv := buffer.NewVectorisedView(size, views) + ep.InjectLinkAddr(pkt.Proto, ep.LinkAddress(), &vv) + } +} + +func (c *testContext) cleanup() { + close(c.linkEP0.C) + close(c.linkEP1.C) +} + +func TestLinkResolution(t *testing.T) { + c := newTestContext(t) + defer c.cleanup() + r, err := c.s0.FindRoute(1, lladdr0, lladdr1, ProtocolNumber) + if err != nil { + t.Fatal(err) + } + defer r.Release() + + hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6EchoMinimumSize) + pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize)) + pkt.SetType(header.ICMPv6EchoRequest) + pkt.SetChecksum(icmpChecksum(pkt, r.LocalAddress, r.RemoteAddress, nil)) + payload := tcpip.SlicePayload(hdr.UsedBytes()) + + // We can't send our payload directly over the route because that + // doesn't provoke NDP discovery. + var wq waiter.Queue + ep, err := c.s0.NewEndpoint(header.ICMPv6ProtocolNumber, ProtocolNumber, &wq) + if err != nil { + t.Fatal(err) + } + + // This actually takes about 10 milliseconds, so no need to wait for + // a multi-minute go test timeout if something is broken. + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + for { + if ctx.Err() != nil { + break + } + if _, err := ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{NIC: 1, Addr: lladdr1}}); err == tcpip.ErrNoLinkAddress { + // There's something asynchronous going on; yield to let it do its thing. + runtime.Gosched() + } else if err == nil { + break + } else { + t.Fatal(err) + } + } + + stats := make(map[header.ICMPv6Type]int) + for { + select { + case <-ctx.Done(): + t.Errorf("timeout waiting for ICMP, got: %#+v", stats) + return + case typ := <-c.icmpCh: + stats[typ]++ + + if stats[header.ICMPv6NeighborSolicit] > 0 && + stats[header.ICMPv6NeighborAdvert] > 0 && + stats[header.ICMPv6EchoRequest] > 0 && + stats[header.ICMPv6EchoReply] > 0 { + return + } + } + } +} diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index f48d120c5..51d34b7ec 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -42,18 +42,12 @@ const ( type address [header.IPv6AddressSize]byte type endpoint struct { - nicid tcpip.NICID - id stack.NetworkEndpointID - address address - linkEP stack.LinkEndpoint - dispatcher stack.TransportDispatcher -} - -func newEndpoint(nicid tcpip.NICID, addr tcpip.Address, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint) *endpoint { - e := &endpoint{nicid: nicid, linkEP: linkEP, dispatcher: dispatcher} - copy(e.address[:], addr) - e.id = stack.NetworkEndpointID{tcpip.Address(e.address[:])} - return e + nicid tcpip.NICID + id stack.NetworkEndpointID + address address + linkEP stack.LinkEndpoint + linkAddrCache stack.LinkAddressCache + dispatcher stack.TransportDispatcher } // MTU implements stack.NetworkEndpoint.MTU. It returns the link-layer MTU minus @@ -93,7 +87,7 @@ func (e *endpoint) WritePacket(r *stack.Route, hdr *buffer.Prependable, payload ip.Encode(&header.IPv6Fields{ PayloadLength: length, NextHeader: uint8(protocol), - HopLimit: 65, + HopLimit: icmpV6HopLimit, SrcAddr: tcpip.Address(e.address[:]), DstAddr: r.RemoteAddress, }) @@ -154,7 +148,15 @@ func (*protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) { // NewEndpoint creates a new ipv6 endpoint. func (p *protocol) NewEndpoint(nicid tcpip.NICID, addr tcpip.Address, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint) (stack.NetworkEndpoint, *tcpip.Error) { - return newEndpoint(nicid, addr, dispatcher, linkEP), nil + e := &endpoint{ + nicid: nicid, + linkEP: linkEP, + linkAddrCache: linkAddrCache, + dispatcher: dispatcher, + } + copy(e.address[:], addr) + e.id = stack.NetworkEndpointID{LocalAddress: tcpip.Address(e.address[:])} + return e, nil } // SetOption implements NetworkProtocol.SetOption. diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index c158e2005..77134c42a 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -165,7 +165,7 @@ func (n *NIC) addAddressLocked(protocol tcpip.NetworkProtocolNumber, addr tcpip. // Set up cache if link address resolution exists for this protocol. if n.linkEP.Capabilities()&CapabilityResolutionRequired != 0 { - if linkRes := n.stack.linkAddrResolvers[protocol]; linkRes != nil { + if _, ok := n.stack.linkAddrResolvers[protocol]; ok { ref.linkCache = n.stack } } diff --git a/pkg/tcpip/transport/ping/endpoint.go b/pkg/tcpip/transport/ping/endpoint.go index f097ac057..7e10c0aae 100644 --- a/pkg/tcpip/transport/ping/endpoint.go +++ b/pkg/tcpip/transport/ping/endpoint.go @@ -56,6 +56,7 @@ type endpoint struct { // change throughout the lifetime of the endpoint. stack *stack.Stack `state:"manual"` netProto tcpip.NetworkProtocolNumber + transProto tcpip.TransportProtocolNumber waiterQueue *waiter.Queue // The following fields are used to manage the receive queue, and are @@ -81,10 +82,11 @@ type endpoint struct { route stack.Route `state:"manual"` } -func newEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) *endpoint { +func newEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, waiterQueue *waiter.Queue) *endpoint { return &endpoint{ stack: stack, netProto: netProto, + transProto: transProto, waiterQueue: waiterQueue, rcvBufSizeMax: 32 * 1024, sndBufSize: 32 * 1024, @@ -98,7 +100,7 @@ func (e *endpoint) Close() { e.shutdownFlags = tcpip.ShutdownRead | tcpip.ShutdownWrite switch e.state { case stateBound, stateConnected: - e.stack.UnregisterTransportEndpoint(e.regNICID, []tcpip.NetworkProtocolNumber{e.netProto}, ProtocolNumber4, e.id) + e.stack.UnregisterTransportEndpoint(e.regNICID, []tcpip.NetworkProtocolNumber{e.netProto}, e.transProto, e.id) } // Close the receive list and drain it. @@ -297,7 +299,7 @@ func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, *tc err = sendPing4(route, e.id.LocalPort, v) case header.IPv6ProtocolNumber: - // TODO: Support IPv6. + err = sendPing6(route, e.id.LocalPort, v) } return uintptr(len(v)), err @@ -385,6 +387,30 @@ func sendPing4(r *stack.Route, ident uint16, data buffer.View) *tcpip.Error { return r.WritePacket(&hdr, data, header.ICMPv4ProtocolNumber) } +func sendPing6(r *stack.Route, ident uint16, data buffer.View) *tcpip.Error { + if len(data) < header.ICMPv6EchoMinimumSize { + return tcpip.ErrInvalidEndpointState + } + + // Set the ident. Sequence number is provided by the user. + binary.BigEndian.PutUint16(data[header.ICMPv6MinimumSize:], ident) + + hdr := buffer.NewPrependable(header.ICMPv6EchoMinimumSize + int(r.MaxHeaderLength())) + + icmpv6 := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize)) + copy(icmpv6, data) + data = data[header.ICMPv6EchoMinimumSize:] + + if icmpv6.Type() != header.ICMPv6EchoRequest || icmpv6.Code() != 0 { + return tcpip.ErrInvalidEndpointState + } + + icmpv6.SetChecksum(0) + icmpv6.SetChecksum(^header.Checksum(icmpv6, header.Checksum(data, 0))) + + return r.WritePacket(&hdr, data, header.ICMPv6ProtocolNumber) +} + func (e *endpoint) checkV4Mapped(addr *tcpip.FullAddress, allowMismatch bool) (tcpip.NetworkProtocolNumber, *tcpip.Error) { netProto := e.netProto if header.IsV4MappedAddress(addr.Addr) { @@ -508,14 +534,14 @@ func (e *endpoint) registerWithStack(nicid tcpip.NICID, netProtos []tcpip.Networ if id.LocalPort != 0 { // The endpoint already has a local port, just attempt to // register it. - err := e.stack.RegisterTransportEndpoint(nicid, netProtos, ProtocolNumber4, id, e) + err := e.stack.RegisterTransportEndpoint(nicid, netProtos, e.transProto, id, e) return id, err } // We need to find a port for the endpoint. _, err := e.stack.PickEphemeralPort(func(p uint16) (bool, *tcpip.Error) { id.LocalPort = p - err := e.stack.RegisterTransportEndpoint(nicid, netProtos, ProtocolNumber4, id, e) + err := e.stack.RegisterTransportEndpoint(nicid, netProtos, e.transProto, id, e) switch err { case nil: return true, nil @@ -564,7 +590,7 @@ func (e *endpoint) bindLocked(addr tcpip.FullAddress, commit func() *tcpip.Error if commit != nil { if err := commit(); err != nil { // Unregister, the commit failed. - e.stack.UnregisterTransportEndpoint(addr.NIC, netProtos, ProtocolNumber4, id) + e.stack.UnregisterTransportEndpoint(addr.NIC, netProtos, e.transProto, id) return err } } diff --git a/pkg/tcpip/transport/ping/protocol.go b/pkg/tcpip/transport/ping/protocol.go index 8a8192064..b885f3627 100644 --- a/pkg/tcpip/transport/ping/protocol.go +++ b/pkg/tcpip/transport/ping/protocol.go @@ -71,7 +71,7 @@ func (p *protocol) NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtoco if netProto != p.netProto() { return nil, tcpip.ErrUnknownProtocol } - return newEndpoint(stack, netProto, waiterQueue), nil + return newEndpoint(stack, netProto, p.number, waiterQueue), nil } // MinimumPacketSize returns the minimum valid ping packet size. @@ -87,8 +87,14 @@ func (p *protocol) MinimumPacketSize() int { // ParsePorts returns the source and destination ports stored in the given ping // packet. -func (*protocol) ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) { - return 0, binary.BigEndian.Uint16(v[header.ICMPv4MinimumSize:]), nil +func (p *protocol) ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) { + switch p.number { + case ProtocolNumber4: + return 0, binary.BigEndian.Uint16(v[header.ICMPv4MinimumSize:]), nil + case ProtocolNumber6: + return 0, binary.BigEndian.Uint16(v[header.ICMPv6MinimumSize:]), nil + } + panic(fmt.Sprint("unknown protocol number: ", p.number)) } // HandleUnknownDestinationPacket handles packets targeted at this protocol but @@ -112,5 +118,7 @@ func init() { return &protocol{ProtocolNumber4} }) - // TODO: Support IPv6. + stack.RegisterTransportProtocolFactory(ProtocolName6, func() stack.TransportProtocol { + return &protocol{ProtocolNumber6} + }) } |