| // Copyright 2018 The gVisor Authors. |
| // |
| // 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 contains the implementation of the ipv6 network protocol. To use |
| // it in the networking stack, this package must be added to the project, and |
| // activated on the stack by passing ipv6.NewProtocol() as one of the network |
| // protocols when calling stack.New(). Then endpoints can be created by passing |
| // ipv6.ProtocolNumber as the network protocol number when calling |
| // Stack.NewEndpoint(). |
| package ipv6 |
| |
| import ( |
| "fmt" |
| "sync/atomic" |
| |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/buffer" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/network/fragmentation" |
| "gvisor.dev/gvisor/pkg/tcpip/network/hash" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| const ( |
| // ProtocolNumber is the ipv6 protocol number. |
| ProtocolNumber = header.IPv6ProtocolNumber |
| |
| // maxTotalSize is maximum size that can be encoded in the 16-bit |
| // PayloadLength field of the ipv6 header. |
| maxPayloadSize = 0xffff |
| |
| // DefaultTTL is the default hop limit for IPv6 Packets egressed by |
| // Netstack. |
| DefaultTTL = 64 |
| ) |
| |
| type endpoint struct { |
| nicID tcpip.NICID |
| id stack.NetworkEndpointID |
| prefixLen int |
| linkEP stack.LinkEndpoint |
| linkAddrCache stack.LinkAddressCache |
| dispatcher stack.TransportDispatcher |
| fragmentation *fragmentation.Fragmentation |
| protocol *protocol |
| } |
| |
| // DefaultTTL is the default hop limit for this endpoint. |
| func (e *endpoint) DefaultTTL() uint8 { |
| return e.protocol.DefaultTTL() |
| } |
| |
| // MTU implements stack.NetworkEndpoint.MTU. It returns the link-layer MTU minus |
| // the network layer max header length. |
| func (e *endpoint) MTU() uint32 { |
| return calculateMTU(e.linkEP.MTU()) |
| } |
| |
| // NICID returns the ID of the NIC this endpoint belongs to. |
| func (e *endpoint) NICID() tcpip.NICID { |
| return e.nicID |
| } |
| |
| // ID returns the ipv6 endpoint ID. |
| func (e *endpoint) ID() *stack.NetworkEndpointID { |
| return &e.id |
| } |
| |
| // PrefixLen returns the ipv6 endpoint subnet prefix length in bits. |
| func (e *endpoint) PrefixLen() int { |
| return e.prefixLen |
| } |
| |
| // Capabilities implements stack.NetworkEndpoint.Capabilities. |
| func (e *endpoint) Capabilities() stack.LinkEndpointCapabilities { |
| return e.linkEP.Capabilities() |
| } |
| |
| // MaxHeaderLength returns the maximum length needed by ipv6 headers (and |
| // underlying protocols). |
| func (e *endpoint) MaxHeaderLength() uint16 { |
| return e.linkEP.MaxHeaderLength() + header.IPv6MinimumSize |
| } |
| |
| // GSOMaxSize returns the maximum GSO packet size. |
| func (e *endpoint) GSOMaxSize() uint32 { |
| if gso, ok := e.linkEP.(stack.GSOEndpoint); ok { |
| return gso.GSOMaxSize() |
| } |
| return 0 |
| } |
| |
| func (e *endpoint) addIPHeader(r *stack.Route, hdr *buffer.Prependable, payloadSize int, params stack.NetworkHeaderParams) header.IPv6 { |
| length := uint16(hdr.UsedLength() + payloadSize) |
| ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) |
| ip.Encode(&header.IPv6Fields{ |
| PayloadLength: length, |
| NextHeader: uint8(params.Protocol), |
| HopLimit: params.TTL, |
| TrafficClass: params.TOS, |
| SrcAddr: r.LocalAddress, |
| DstAddr: r.RemoteAddress, |
| }) |
| return ip |
| } |
| |
| // WritePacket writes a packet to the given destination address and protocol. |
| func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, params stack.NetworkHeaderParams, pkt stack.PacketBuffer) *tcpip.Error { |
| ip := e.addIPHeader(r, &pkt.Header, pkt.Data.Size(), params) |
| pkt.NetworkHeader = buffer.View(ip) |
| |
| if r.Loop&stack.PacketLoop != 0 { |
| // The inbound path expects the network header to still be in |
| // the PacketBuffer's Data field. |
| views := make([]buffer.View, 1, 1+len(pkt.Data.Views())) |
| views[0] = pkt.Header.View() |
| views = append(views, pkt.Data.Views()...) |
| loopedR := r.MakeLoopedRoute() |
| |
| e.HandlePacket(&loopedR, stack.PacketBuffer{ |
| Data: buffer.NewVectorisedView(len(views[0])+pkt.Data.Size(), views), |
| }) |
| |
| loopedR.Release() |
| } |
| if r.Loop&stack.PacketOut == 0 { |
| return nil |
| } |
| |
| r.Stats().IP.PacketsSent.Increment() |
| return e.linkEP.WritePacket(r, gso, ProtocolNumber, pkt) |
| } |
| |
| // WritePackets implements stack.LinkEndpoint.WritePackets. |
| func (e *endpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.PacketBufferList, params stack.NetworkHeaderParams) (int, *tcpip.Error) { |
| if r.Loop&stack.PacketLoop != 0 { |
| panic("not implemented") |
| } |
| if r.Loop&stack.PacketOut == 0 { |
| return pkts.Len(), nil |
| } |
| |
| for pb := pkts.Front(); pb != nil; pb = pb.Next() { |
| ip := e.addIPHeader(r, &pb.Header, pb.Data.Size(), params) |
| pb.NetworkHeader = buffer.View(ip) |
| } |
| |
| n, err := e.linkEP.WritePackets(r, gso, pkts, ProtocolNumber) |
| r.Stats().IP.PacketsSent.IncrementBy(uint64(n)) |
| return n, err |
| } |
| |
| // WriteHeaderIncludedPacker implements stack.NetworkEndpoint. It is not yet |
| // supported by IPv6. |
| func (*endpoint) WriteHeaderIncludedPacket(r *stack.Route, pkt stack.PacketBuffer) *tcpip.Error { |
| // TODO(b/146666412): Support IPv6 header-included packets. |
| return tcpip.ErrNotSupported |
| } |
| |
| // HandlePacket is called by the link layer when new ipv6 packets arrive for |
| // this endpoint. |
| func (e *endpoint) HandlePacket(r *stack.Route, pkt stack.PacketBuffer) { |
| headerView := pkt.Data.First() |
| h := header.IPv6(headerView) |
| if !h.IsValid(pkt.Data.Size()) { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| return |
| } |
| |
| pkt.NetworkHeader = headerView[:header.IPv6MinimumSize] |
| pkt.Data.TrimFront(header.IPv6MinimumSize) |
| pkt.Data.CapLength(int(h.PayloadLength())) |
| |
| it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(h.NextHeader()), pkt.Data) |
| hasFragmentHeader := false |
| |
| for firstHeader := true; ; firstHeader = false { |
| extHdr, done, err := it.Next() |
| if err != nil { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| return |
| } |
| if done { |
| break |
| } |
| |
| switch extHdr := extHdr.(type) { |
| case header.IPv6HopByHopOptionsExtHdr: |
| // As per RFC 8200 section 4.1, the Hop By Hop extension header is |
| // restricted to appear immediately after an IPv6 fixed header. |
| // |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem, Code 1 |
| // (unrecognized next header) error in response to an extension header's |
| // Next Header field with the Hop By Hop extension header identifier. |
| if !firstHeader { |
| return |
| } |
| |
| optsIt := extHdr.Iter() |
| |
| for { |
| opt, done, err := optsIt.Next() |
| if err != nil { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| return |
| } |
| if done { |
| break |
| } |
| |
| // We currently do not support any IPv6 Hop By Hop extension header |
| // options. |
| switch opt.UnknownAction() { |
| case header.IPv6OptionUnknownActionSkip: |
| case header.IPv6OptionUnknownActionDiscard: |
| return |
| case header.IPv6OptionUnknownActionDiscardSendICMP: |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem Code 2 for |
| // unrecognized IPv6 extension header options. |
| return |
| case header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest: |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem Code 2 for |
| // unrecognized IPv6 extension header options. |
| return |
| default: |
| panic(fmt.Sprintf("unrecognized action for an unrecognized Hop By Hop extension header option = %d", opt)) |
| } |
| } |
| |
| case header.IPv6RoutingExtHdr: |
| // As per RFC 8200 section 4.4, if a node encounters a routing header with |
| // an unrecognized routing type value, with a non-zero Segments Left |
| // value, the node must discard the packet and send an ICMP Parameter |
| // Problem, Code 0. If the Segments Left is 0, the node must ignore the |
| // Routing extension header and process the next header in the packet. |
| // |
| // Note, the stack does not yet handle any type of routing extension |
| // header, so we just make sure Segments Left is zero before processing |
| // the next extension header. |
| // |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem Code 0 for |
| // unrecognized routing types with a non-zero Segments Left value. |
| if extHdr.SegmentsLeft() != 0 { |
| return |
| } |
| |
| case header.IPv6FragmentExtHdr: |
| hasFragmentHeader = true |
| |
| fragmentOffset := extHdr.FragmentOffset() |
| more := extHdr.More() |
| if !more && fragmentOffset == 0 { |
| // This fragment extension header indicates that this packet is an |
| // atomic fragment. An atomic fragment is a fragment that contains |
| // all the data required to reassemble a full packet. As per RFC 6946, |
| // atomic fragments must not interfere with "normal" fragmented traffic |
| // so we skip processing the fragment instead of feeding it through the |
| // reassembly process below. |
| continue |
| } |
| |
| rawPayload := it.AsRawHeader() |
| fragmentPayloadLen := rawPayload.Buf.Size() |
| if fragmentPayloadLen == 0 { |
| // Drop the packet as it's marked as a fragment but has no payload. |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| r.Stats().IP.MalformedFragmentsReceived.Increment() |
| return |
| } |
| |
| // The packet is a fragment, let's try to reassemble it. |
| start := fragmentOffset * header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit |
| last := start + uint16(fragmentPayloadLen) - 1 |
| |
| // Drop the packet if the fragmentOffset is incorrect. i.e the |
| // combination of fragmentOffset and pkt.Data.size() causes a |
| // wrap around resulting in last being less than the offset. |
| if last < start { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| r.Stats().IP.MalformedFragmentsReceived.Increment() |
| return |
| } |
| |
| var ready bool |
| pkt.Data, ready, err = e.fragmentation.Process(hash.IPv6FragmentHash(h, extHdr.ID()), start, last, more, rawPayload.Buf) |
| if err != nil { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| r.Stats().IP.MalformedFragmentsReceived.Increment() |
| return |
| } |
| |
| if ready { |
| // We create a new iterator with the reassembled packet because we could |
| // have more extension headers in the reassembled payload, as per RFC |
| // 8200 section 4.5. |
| it = header.MakeIPv6PayloadIterator(rawPayload.Identifier, pkt.Data) |
| } |
| |
| case header.IPv6DestinationOptionsExtHdr: |
| optsIt := extHdr.Iter() |
| |
| for { |
| opt, done, err := optsIt.Next() |
| if err != nil { |
| r.Stats().IP.MalformedPacketsReceived.Increment() |
| return |
| } |
| if done { |
| break |
| } |
| |
| // We currently do not support any IPv6 Destination extension header |
| // options. |
| switch opt.UnknownAction() { |
| case header.IPv6OptionUnknownActionSkip: |
| case header.IPv6OptionUnknownActionDiscard: |
| return |
| case header.IPv6OptionUnknownActionDiscardSendICMP: |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem Code 2 for |
| // unrecognized IPv6 extension header options. |
| return |
| case header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest: |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem Code 2 for |
| // unrecognized IPv6 extension header options. |
| return |
| default: |
| panic(fmt.Sprintf("unrecognized action for an unrecognized Destination extension header option = %d", opt)) |
| } |
| } |
| |
| case header.IPv6RawPayloadHeader: |
| // If the last header in the payload isn't a known IPv6 extension header, |
| // handle it as if it is transport layer data. |
| pkt.Data = extHdr.Buf |
| |
| if p := tcpip.TransportProtocolNumber(extHdr.Identifier); p == header.ICMPv6ProtocolNumber { |
| e.handleICMP(r, headerView, pkt, hasFragmentHeader) |
| } else { |
| r.Stats().IP.PacketsDelivered.Increment() |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem, Code 1 error |
| // in response to unrecognized next header values. |
| e.dispatcher.DeliverTransportPacket(r, p, pkt) |
| } |
| |
| default: |
| // If we receive a packet for an extension header we do not yet handle, |
| // drop the packet for now. |
| // |
| // TODO(b/152019344): Send an ICMPv6 Parameter Problem, Code 1 error |
| // in response to unrecognized next header values. |
| r.Stats().UnknownProtocolRcvdPackets.Increment() |
| return |
| } |
| } |
| } |
| |
| // Close cleans up resources associated with the endpoint. |
| func (*endpoint) Close() {} |
| |
| type protocol struct { |
| // defaultTTL is the current default TTL for the protocol. Only the |
| // uint8 portion of it is meaningful and it must be accessed |
| // atomically. |
| defaultTTL uint32 |
| } |
| |
| // Number returns the ipv6 protocol number. |
| func (p *protocol) Number() tcpip.NetworkProtocolNumber { |
| return ProtocolNumber |
| } |
| |
| // MinimumPacketSize returns the minimum valid ipv6 packet size. |
| func (p *protocol) MinimumPacketSize() int { |
| return header.IPv6MinimumSize |
| } |
| |
| // DefaultPrefixLen returns the IPv6 default prefix length. |
| func (p *protocol) DefaultPrefixLen() int { |
| return header.IPv6AddressSize * 8 |
| } |
| |
| // ParseAddresses implements NetworkProtocol.ParseAddresses. |
| func (*protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) { |
| h := header.IPv6(v) |
| return h.SourceAddress(), h.DestinationAddress() |
| } |
| |
| // NewEndpoint creates a new ipv6 endpoint. |
| func (p *protocol) NewEndpoint(nicID tcpip.NICID, addrWithPrefix tcpip.AddressWithPrefix, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, linkEP stack.LinkEndpoint, st *stack.Stack) (stack.NetworkEndpoint, *tcpip.Error) { |
| return &endpoint{ |
| nicID: nicID, |
| id: stack.NetworkEndpointID{LocalAddress: addrWithPrefix.Address}, |
| prefixLen: addrWithPrefix.PrefixLen, |
| linkEP: linkEP, |
| linkAddrCache: linkAddrCache, |
| dispatcher: dispatcher, |
| fragmentation: fragmentation.NewFragmentation(fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, fragmentation.DefaultReassembleTimeout), |
| protocol: p, |
| }, nil |
| } |
| |
| // SetOption implements NetworkProtocol.SetOption. |
| func (p *protocol) SetOption(option interface{}) *tcpip.Error { |
| switch v := option.(type) { |
| case tcpip.DefaultTTLOption: |
| p.SetDefaultTTL(uint8(v)) |
| return nil |
| default: |
| return tcpip.ErrUnknownProtocolOption |
| } |
| } |
| |
| // Option implements NetworkProtocol.Option. |
| func (p *protocol) Option(option interface{}) *tcpip.Error { |
| switch v := option.(type) { |
| case *tcpip.DefaultTTLOption: |
| *v = tcpip.DefaultTTLOption(p.DefaultTTL()) |
| return nil |
| default: |
| return tcpip.ErrUnknownProtocolOption |
| } |
| } |
| |
| // SetDefaultTTL sets the default TTL for endpoints created with this protocol. |
| func (p *protocol) SetDefaultTTL(ttl uint8) { |
| atomic.StoreUint32(&p.defaultTTL, uint32(ttl)) |
| } |
| |
| // DefaultTTL returns the default TTL for endpoints created with this protocol. |
| func (p *protocol) DefaultTTL() uint8 { |
| return uint8(atomic.LoadUint32(&p.defaultTTL)) |
| } |
| |
| // Close implements stack.TransportProtocol.Close. |
| func (*protocol) Close() {} |
| |
| // Wait implements stack.TransportProtocol.Wait. |
| func (*protocol) Wait() {} |
| |
| // calculateMTU calculates the network-layer payload MTU based on the link-layer |
| // payload mtu. |
| func calculateMTU(mtu uint32) uint32 { |
| mtu -= header.IPv6MinimumSize |
| if mtu <= maxPayloadSize { |
| return mtu |
| } |
| return maxPayloadSize |
| } |
| |
| // NewProtocol returns an IPv6 network protocol. |
| func NewProtocol() stack.NetworkProtocol { |
| return &protocol{defaultTTL: DefaultTTL} |
| } |