blob: 0da690a9b8d3ac27b4fafbaa0d4dbb3c28db76d1 [file] [log] [blame]
// Copyright 2016 The Netstack Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package fdbased provides the implemention of data-link layer endpoints
// backed by boundary-preserving file descriptors (e.g., TUN devices,
// seqpacket/datagram sockets).
//
// FD based endpoints can be used in the networking stack by calling New() to
// create a new endpoint, and then passing it as an argument to
// Stack.CreateNIC().
package fdbased
import (
"syscall"
"github.com/google/netstack/tcpip"
"github.com/google/netstack/tcpip/buffer"
"github.com/google/netstack/tcpip/header"
"github.com/google/netstack/tcpip/link/rawfile"
"github.com/google/netstack/tcpip/stack"
)
// BufConfig defines the shape of the vectorised view used to read packets from the NIC.
var BufConfig = []int{128, 256, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768}
type endpoint struct {
// fd is the file descriptor used to send and receive packets.
fd int
// mtu (maximum transmission unit) is the maximum size of a packet.
mtu uint32
// closed is a function to be called when the FD's peer (if any) closes
// its end of the communication pipe.
closed func(*tcpip.Error)
vv *buffer.VectorisedView
iovecs []syscall.Iovec
views []buffer.View
}
// New creates a new fd-based endpoint.
func New(fd int, mtu uint32, closed func(*tcpip.Error)) tcpip.LinkEndpointID {
syscall.SetNonblock(fd, true)
e := &endpoint{
fd: fd,
mtu: mtu,
closed: closed,
views: make([]buffer.View, len(BufConfig)),
iovecs: make([]syscall.Iovec, len(BufConfig)),
}
vv := buffer.NewVectorisedView(0, e.views)
e.vv = &vv
return stack.RegisterLinkEndpoint(e)
}
// 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) {
go e.dispatchLoop(dispatcher)
}
// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
// during construction.
func (e *endpoint) MTU() uint32 {
return e.mtu
}
// MaxHeaderLength returns the maximum size of the header. Given that it
// doesn't have a header, it just returns 0.
func (*endpoint) MaxHeaderLength() uint16 {
return 0
}
// LinkAddress returns the link address of this endpoint.
func (*endpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
// WritePacket writes outbound packets to the file descriptor. If it is not
// currently writable, the packet is dropped.
func (e *endpoint) WritePacket(_ *stack.Route, hdr *buffer.Prependable, payload buffer.View, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
if payload == nil {
return rawfile.NonBlockingWrite(e.fd, hdr.UsedBytes())
}
return rawfile.NonBlockingWrite2(e.fd, hdr.UsedBytes(), payload)
}
func (e *endpoint) capViews(n int, buffers []int) int {
c := 0
for i, s := range buffers {
c += s
if c >= n {
e.views[i].CapLength(s - (c - n))
return i + 1
}
}
return len(buffers)
}
func (e *endpoint) allocateViews(bufConfig []int) {
for i, v := range e.views {
if v != nil {
break
}
b := buffer.NewView(bufConfig[i])
e.views[i] = b
e.iovecs[i] = syscall.Iovec{
Base: &b[0],
Len: uint64(len(b)),
}
}
}
// dispatch reads one packet from the file descriptor and dispatches it.
func (e *endpoint) dispatch(d stack.NetworkDispatcher, largeV buffer.View) (bool, *tcpip.Error) {
e.allocateViews(BufConfig)
n, err := rawfile.BlockingReadv(e.fd, e.iovecs)
if err != nil {
return false, err
}
if n <= 0 {
return false, nil
}
used := e.capViews(n, BufConfig)
e.vv.SetViews(e.views[:used])
e.vv.SetSize(n)
// We don't get any indication of what the packet is, so try to guess
// if it's an IPv4 or IPv6 packet.
var p tcpip.NetworkProtocolNumber
switch header.IPVersion(e.views[0]) {
case header.IPv4Version:
p = header.IPv4ProtocolNumber
case header.IPv6Version:
p = header.IPv6ProtocolNumber
default:
return true, nil
}
d.DeliverNetworkPacket(e, e.LinkAddress(), "", p, e.vv)
// Prepare e.views for another packet: release used views.
for i := 0; i < used; i++ {
e.views[i] = nil
}
return true, nil
}
// 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 {
v := buffer.NewView(header.MaxIPPacketSize)
for {
cont, err := e.dispatch(d, v)
if err != nil || !cont {
if e.closed != nil {
e.closed(err)
}
return err
}
}
}
// InjectableEndpoint is an injectable fd-based endpoint. The endpoint writes
// to the FD, but does not read from it. All reads come from injected packets.
type InjectableEndpoint struct {
endpoint
dispatcher stack.NetworkDispatcher
}
// Attach saves the stack network-layer dispatcher for use later when packets
// are injected.
func (e *InjectableEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcher = dispatcher
}
// Inject injects an inbound packet.
func (e *InjectableEndpoint) Inject(protocol tcpip.NetworkProtocolNumber, vv *buffer.VectorisedView) {
uu := vv.Clone(nil)
e.dispatcher.DeliverNetworkPacket(e, e.LinkAddress(), "", protocol, &uu)
}
// NewInjectable creates a new fd-based InjectableEndpoint.
func NewInjectable(fd int, mtu uint32) (tcpip.LinkEndpointID, *InjectableEndpoint) {
syscall.SetNonblock(fd, true)
e := &InjectableEndpoint{endpoint: endpoint{
fd: fd,
mtu: mtu,
}}
return stack.RegisterLinkEndpoint(e), e
}