snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для неё не нужна. Module path github.com/sagernet/gvisor сохранён намеренно: на него опирается replace-директива суперпроекта. Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show и переносилась на новый пин копированием. SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
This commit is contained in:
commit
2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions
103
pkg/tcpip/adapters/gonet/err.go
Normal file
103
pkg/tcpip/adapters/gonet/err.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package gonet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
func TranslateNetstackError(err tcpip.Error) error {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *tcpip.ErrUnknownProtocol:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrUnknownNICID:
|
||||
return syscall.ENODEV
|
||||
case *tcpip.ErrUnknownDevice:
|
||||
return syscall.ENODEV
|
||||
case *tcpip.ErrUnknownProtocolOption:
|
||||
return syscall.ENOPROTOOPT
|
||||
case *tcpip.ErrDuplicateNICID:
|
||||
return syscall.EEXIST
|
||||
case *tcpip.ErrDuplicateAddress:
|
||||
return syscall.EEXIST
|
||||
case *tcpip.ErrHostUnreachable:
|
||||
return syscall.EHOSTUNREACH
|
||||
case *tcpip.ErrHostDown:
|
||||
return syscall.EHOSTDOWN
|
||||
case *tcpip.ErrNoNet:
|
||||
return errNoNet
|
||||
case *tcpip.ErrAlreadyBound:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrInvalidEndpointState:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrAlreadyConnecting:
|
||||
return syscall.EALREADY
|
||||
case *tcpip.ErrAlreadyConnected:
|
||||
return syscall.EISCONN
|
||||
case *tcpip.ErrNoPortAvailable:
|
||||
return syscall.EAGAIN
|
||||
case *tcpip.ErrPortInUse:
|
||||
return syscall.EADDRINUSE
|
||||
case *tcpip.ErrBadLocalAddress:
|
||||
return syscall.EADDRNOTAVAIL
|
||||
case *tcpip.ErrClosedForSend:
|
||||
return syscall.EPIPE
|
||||
case *tcpip.ErrClosedForReceive:
|
||||
return syscall.ENOTCONN
|
||||
case *tcpip.ErrWouldBlock:
|
||||
return syscall.EWOULDBLOCK
|
||||
case *tcpip.ErrConnectionRefused:
|
||||
return syscall.ECONNREFUSED
|
||||
case *tcpip.ErrTimeout:
|
||||
return syscall.ETIMEDOUT
|
||||
case *tcpip.ErrAborted:
|
||||
return syscall.EPIPE
|
||||
case *tcpip.ErrConnectStarted:
|
||||
return syscall.EINPROGRESS
|
||||
case *tcpip.ErrDestinationRequired:
|
||||
return syscall.EDESTADDRREQ
|
||||
case *tcpip.ErrNotSupported:
|
||||
return syscall.EOPNOTSUPP
|
||||
case *tcpip.ErrQueueSizeNotSupported:
|
||||
return syscall.ENOTTY
|
||||
case *tcpip.ErrNotConnected:
|
||||
return syscall.ENOTCONN
|
||||
case *tcpip.ErrConnectionReset:
|
||||
return syscall.ECONNRESET
|
||||
case *tcpip.ErrConnectionAborted:
|
||||
return syscall.ECONNABORTED
|
||||
case *tcpip.ErrNoSuchFile:
|
||||
return syscall.ENOENT
|
||||
case *tcpip.ErrInvalidOptionValue:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrBadAddress:
|
||||
return syscall.EFAULT
|
||||
case *tcpip.ErrNetworkUnreachable:
|
||||
return syscall.ENETUNREACH
|
||||
case *tcpip.ErrMessageTooLong:
|
||||
return syscall.EMSGSIZE
|
||||
case *tcpip.ErrNoBufferSpace:
|
||||
return syscall.ENOBUFS
|
||||
case *tcpip.ErrBroadcastDisabled:
|
||||
return syscall.EACCES
|
||||
case *tcpip.ErrNotPermitted:
|
||||
return syscall.EPERM
|
||||
case *tcpip.ErrAddressFamilyNotSupported:
|
||||
return syscall.EAFNOSUPPORT
|
||||
case *tcpip.ErrBadBuffer:
|
||||
return syscall.EFAULT
|
||||
case *tcpip.ErrMalformedHeader:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrInvalidPortRange:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrMulticastInputCannotBeOutput:
|
||||
return syscall.EINVAL
|
||||
case *tcpip.ErrMissingRequiredFields:
|
||||
return syscall.EINVAL
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown error %T", err))
|
||||
}
|
||||
}
|
||||
7
pkg/tcpip/adapters/gonet/err_linuxwindows.go
Normal file
7
pkg/tcpip/adapters/gonet/err_linuxwindows.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//go:build linux || windows
|
||||
|
||||
package gonet
|
||||
|
||||
import "syscall"
|
||||
|
||||
var errNoNet = syscall.ENONET
|
||||
7
pkg/tcpip/adapters/gonet/err_stub.go
Normal file
7
pkg/tcpip/adapters/gonet/err_stub.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//go:build !(linux || windows)
|
||||
|
||||
package gonet
|
||||
|
||||
import "errors"
|
||||
|
||||
var errNoNet = errors.New("machine is not on the network")
|
||||
714
pkg/tcpip/adapters/gonet/gonet.go
Normal file
714
pkg/tcpip/adapters/gonet/gonet.go
Normal file
|
|
@ -0,0 +1,714 @@
|
|||
// 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 gonet provides a Go net package compatible wrapper for a tcpip stack.
|
||||
package gonet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||
"github.com/sagernet/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
var (
|
||||
errCanceled = errors.New("operation canceled")
|
||||
errWouldBlock = errors.New("operation would block")
|
||||
)
|
||||
|
||||
// timeoutError is how the net package reports timeouts.
|
||||
type timeoutError struct{}
|
||||
|
||||
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||
func (e *timeoutError) Timeout() bool { return true }
|
||||
func (e *timeoutError) Temporary() bool { return true }
|
||||
|
||||
// A TCPListener is a wrapper around a TCP tcpip.Endpoint that implements
|
||||
// net.Listener.
|
||||
type TCPListener struct {
|
||||
stack *stack.Stack
|
||||
ep tcpip.Endpoint
|
||||
wq *waiter.Queue
|
||||
cancelOnce sync.Once
|
||||
cancel chan struct{}
|
||||
}
|
||||
|
||||
// NewTCPListener creates a new TCPListener from a listening tcpip.Endpoint.
|
||||
func NewTCPListener(s *stack.Stack, wq *waiter.Queue, ep tcpip.Endpoint) *TCPListener {
|
||||
return &TCPListener{
|
||||
stack: s,
|
||||
ep: ep,
|
||||
wq: wq,
|
||||
cancel: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// maxListenBacklog is set to be reasonably high for most uses of gonet. Go net
|
||||
// package uses the value in /proc/sys/net/core/somaxconn file in Linux as the
|
||||
// default listen backlog. The value below matches the default in common linux
|
||||
// distros.
|
||||
//
|
||||
// See: https://cs.opensource.google/go/go/+/refs/tags/go1.18.1:src/net/sock_linux.go;drc=refs%2Ftags%2Fgo1.18.1;l=66
|
||||
const maxListenBacklog = 4096
|
||||
|
||||
// ListenTCP creates a new TCPListener.
|
||||
func ListenTCP(s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*TCPListener, error) {
|
||||
// Create a TCP endpoint, bind it, then start listening.
|
||||
var wq waiter.Queue
|
||||
ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
|
||||
if err != nil {
|
||||
return nil, TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
if err := ep.Bind(addr); err != nil {
|
||||
ep.Close()
|
||||
return nil, &net.OpError{
|
||||
Op: "bind",
|
||||
Net: "tcp",
|
||||
Addr: fullToTCPAddr(addr),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
|
||||
if err := ep.Listen(maxListenBacklog); err != nil {
|
||||
ep.Close()
|
||||
return nil, &net.OpError{
|
||||
Op: "listen",
|
||||
Net: "tcp",
|
||||
Addr: fullToTCPAddr(addr),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
|
||||
return NewTCPListener(s, &wq, ep), nil
|
||||
}
|
||||
|
||||
// Close implements net.Listener.Close.
|
||||
func (l *TCPListener) Close() error {
|
||||
l.ep.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown stops the HTTP server.
|
||||
func (l *TCPListener) Shutdown() {
|
||||
l.ep.Shutdown(tcpip.ShutdownWrite | tcpip.ShutdownRead)
|
||||
l.cancelOnce.Do(func() {
|
||||
close(l.cancel) // broadcast cancellation
|
||||
})
|
||||
}
|
||||
|
||||
// Addr implements net.Listener.Addr.
|
||||
func (l *TCPListener) Addr() net.Addr {
|
||||
a, err := l.ep.GetLocalAddress()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fullToTCPAddr(a)
|
||||
}
|
||||
|
||||
type deadlineTimer struct {
|
||||
// mu protects the fields below.
|
||||
mu sync.Mutex
|
||||
|
||||
readTimer *time.Timer
|
||||
readCancelCh chan struct{}
|
||||
writeTimer *time.Timer
|
||||
writeCancelCh chan struct{}
|
||||
}
|
||||
|
||||
func (d *deadlineTimer) init() {
|
||||
d.readCancelCh = make(chan struct{})
|
||||
d.writeCancelCh = make(chan struct{})
|
||||
}
|
||||
|
||||
func (d *deadlineTimer) readCancel() <-chan struct{} {
|
||||
d.mu.Lock()
|
||||
c := d.readCancelCh
|
||||
d.mu.Unlock()
|
||||
return c
|
||||
}
|
||||
|
||||
func (d *deadlineTimer) writeCancel() <-chan struct{} {
|
||||
d.mu.Lock()
|
||||
c := d.writeCancelCh
|
||||
d.mu.Unlock()
|
||||
return c
|
||||
}
|
||||
|
||||
// setDeadline contains the shared logic for setting a deadline.
|
||||
//
|
||||
// cancelCh and timer must be pointers to deadlineTimer.readCancelCh and
|
||||
// deadlineTimer.readTimer or deadlineTimer.writeCancelCh and
|
||||
// deadlineTimer.writeTimer.
|
||||
//
|
||||
// setDeadline must only be called while holding d.mu.
|
||||
func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer, t time.Time) {
|
||||
if *timer != nil && !(*timer).Stop() {
|
||||
*cancelCh = make(chan struct{})
|
||||
}
|
||||
|
||||
// Create a new channel if we already closed it due to setting an already
|
||||
// expired time. We won't race with the timer because we already handled
|
||||
// that above.
|
||||
select {
|
||||
case <-*cancelCh:
|
||||
*cancelCh = make(chan struct{})
|
||||
default:
|
||||
}
|
||||
|
||||
// "A zero value for t means I/O operations will not time out."
|
||||
// - net.Conn.SetDeadline
|
||||
if t.IsZero() {
|
||||
*timer = nil
|
||||
return
|
||||
}
|
||||
|
||||
timeout := t.Sub(time.Now())
|
||||
if timeout <= 0 {
|
||||
close(*cancelCh)
|
||||
return
|
||||
}
|
||||
|
||||
// Timer.Stop returns whether or not the AfterFunc has started, but
|
||||
// does not indicate whether or not it has completed. Make a copy of
|
||||
// the cancel channel to prevent this code from racing with the next
|
||||
// call of setDeadline replacing *cancelCh.
|
||||
ch := *cancelCh
|
||||
*timer = time.AfterFunc(timeout, func() {
|
||||
close(ch)
|
||||
})
|
||||
}
|
||||
|
||||
// SetReadDeadline implements net.Conn.SetReadDeadline and
|
||||
// net.PacketConn.SetReadDeadline.
|
||||
func (d *deadlineTimer) SetReadDeadline(t time.Time) error {
|
||||
d.mu.Lock()
|
||||
d.setDeadline(&d.readCancelCh, &d.readTimer, t)
|
||||
d.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements net.Conn.SetWriteDeadline and
|
||||
// net.PacketConn.SetWriteDeadline.
|
||||
func (d *deadlineTimer) SetWriteDeadline(t time.Time) error {
|
||||
d.mu.Lock()
|
||||
d.setDeadline(&d.writeCancelCh, &d.writeTimer, t)
|
||||
d.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeadline implements net.Conn.SetDeadline and net.PacketConn.SetDeadline.
|
||||
func (d *deadlineTimer) SetDeadline(t time.Time) error {
|
||||
d.mu.Lock()
|
||||
d.setDeadline(&d.readCancelCh, &d.readTimer, t)
|
||||
d.setDeadline(&d.writeCancelCh, &d.writeTimer, t)
|
||||
d.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// A TCPConn is a wrapper around a TCP tcpip.Endpoint that implements the net.Conn
|
||||
// interface.
|
||||
type TCPConn struct {
|
||||
deadlineTimer
|
||||
|
||||
wq *waiter.Queue
|
||||
ep tcpip.Endpoint
|
||||
|
||||
// readMu serializes reads and implicitly protects read.
|
||||
//
|
||||
// Lock ordering:
|
||||
// If both readMu and deadlineTimer.mu are to be used in a single
|
||||
// request, readMu must be acquired before deadlineTimer.mu.
|
||||
readMu sync.Mutex
|
||||
|
||||
// read contains bytes that have been read from the endpoint,
|
||||
// but haven't yet been returned.
|
||||
read []byte
|
||||
}
|
||||
|
||||
// NewTCPConn creates a new TCPConn.
|
||||
func NewTCPConn(wq *waiter.Queue, ep tcpip.Endpoint) *TCPConn {
|
||||
c := &TCPConn{
|
||||
wq: wq,
|
||||
ep: ep,
|
||||
}
|
||||
c.deadlineTimer.init()
|
||||
return c
|
||||
}
|
||||
|
||||
// Accept implements net.Conn.Accept.
|
||||
func (l *TCPListener) Accept() (net.Conn, error) {
|
||||
n, wq, err := l.ep.Accept(nil)
|
||||
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
|
||||
// Create wait queue entry that notifies a channel.
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
|
||||
l.wq.EventRegister(&waitEntry)
|
||||
defer l.wq.EventUnregister(&waitEntry)
|
||||
|
||||
for {
|
||||
n, wq, err = l.ep.Accept(nil)
|
||||
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-l.cancel:
|
||||
return nil, errCanceled
|
||||
case <-notifyCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, &net.OpError{
|
||||
Op: "accept",
|
||||
Net: "tcp",
|
||||
Addr: l.Addr(),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
|
||||
return NewTCPConn(wq, n), nil
|
||||
}
|
||||
|
||||
type opErrorer interface {
|
||||
newOpError(op string, err error) *net.OpError
|
||||
}
|
||||
|
||||
// commonRead implements the common logic between net.Conn.Read and
|
||||
// net.PacketConn.ReadFrom.
|
||||
func commonRead(b []byte, ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan struct{}, addr *tcpip.FullAddress, errorer opErrorer) (int, error) {
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, errorer.newOpError("read", &timeoutError{})
|
||||
default:
|
||||
}
|
||||
|
||||
w := tcpip.SliceWriter(b)
|
||||
opts := tcpip.ReadOptions{NeedRemoteAddr: addr != nil}
|
||||
res, err := ep.Read(&w, opts)
|
||||
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
|
||||
// Create wait queue entry that notifies a channel.
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
|
||||
wq.EventRegister(&waitEntry)
|
||||
defer wq.EventUnregister(&waitEntry)
|
||||
for {
|
||||
res, err = ep.Read(&w, opts)
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, errorer.newOpError("read", &timeoutError{})
|
||||
case <-notifyCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := err.(*tcpip.ErrClosedForReceive); ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, errorer.newOpError("read", TranslateNetstackError(err))
|
||||
}
|
||||
|
||||
if addr != nil {
|
||||
*addr = res.RemoteAddr
|
||||
}
|
||||
return res.Count, nil
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read.
|
||||
func (c *TCPConn) Read(b []byte) (int, error) {
|
||||
c.readMu.Lock()
|
||||
defer c.readMu.Unlock()
|
||||
|
||||
deadline := c.readCancel()
|
||||
|
||||
n, err := commonRead(b, c.ep, c.wq, deadline, nil, c)
|
||||
if n != 0 {
|
||||
c.ep.ModerateRecvBuf(n)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements net.Conn.Write.
|
||||
func (c *TCPConn) Write(b []byte) (int, error) {
|
||||
deadline := c.writeCancel()
|
||||
|
||||
// Check if deadlineTimer has already expired.
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, c.newOpError("write", &timeoutError{})
|
||||
default:
|
||||
}
|
||||
|
||||
// We must handle two soft failure conditions simultaneously:
|
||||
// 1. Write may write nothing and return *tcpip.ErrWouldBlock.
|
||||
// If this happens, we need to register for notifications if we have
|
||||
// not already and wait to try again.
|
||||
// 2. Write may write fewer than the full number of bytes and return
|
||||
// without error. In this case we need to try writing the remaining
|
||||
// bytes again. I do not need to register for notifications.
|
||||
//
|
||||
// What is more, these two soft failure conditions can be interspersed.
|
||||
// There is no guarantee that all of the condition #1s will occur before
|
||||
// all of the condition #2s or visa-versa.
|
||||
var (
|
||||
r bytes.Reader
|
||||
nbytes int
|
||||
entry waiter.Entry
|
||||
ch <-chan struct{}
|
||||
)
|
||||
for nbytes != len(b) {
|
||||
r.Reset(b[nbytes:])
|
||||
n, err := c.ep.Write(&r, tcpip.WriteOptions{})
|
||||
nbytes += int(n)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
case *tcpip.ErrWouldBlock:
|
||||
if ch == nil {
|
||||
entry, ch = waiter.NewChannelEntry(waiter.WritableEvents)
|
||||
c.wq.EventRegister(&entry)
|
||||
defer c.wq.EventUnregister(&entry)
|
||||
} else {
|
||||
// Don't wait immediately after registration in case more data
|
||||
// became available between when we last checked and when we setup
|
||||
// the notification.
|
||||
select {
|
||||
case <-deadline:
|
||||
return nbytes, c.newOpError("write", &timeoutError{})
|
||||
case <-ch:
|
||||
continue
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nbytes, c.newOpError("write", TranslateNetstackError(err))
|
||||
}
|
||||
}
|
||||
return nbytes, nil
|
||||
}
|
||||
|
||||
// Close implements net.Conn.Close.
|
||||
func (c *TCPConn) Close() error {
|
||||
c.ep.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseRead shuts down the reading side of the TCP connection. Most callers
|
||||
// should just use Close.
|
||||
//
|
||||
// A TCP Half-Close is performed the same as CloseRead for *net.TCPConn.
|
||||
func (c *TCPConn) CloseRead() error {
|
||||
if terr := c.ep.Shutdown(tcpip.ShutdownRead); terr != nil {
|
||||
return c.newOpError("close", errors.New(terr.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWrite shuts down the writing side of the TCP connection. Most callers
|
||||
// should just use Close.
|
||||
//
|
||||
// A TCP Half-Close is performed the same as CloseWrite for *net.TCPConn.
|
||||
func (c *TCPConn) CloseWrite() error {
|
||||
if terr := c.ep.Shutdown(tcpip.ShutdownWrite); terr != nil {
|
||||
return c.newOpError("close", errors.New(terr.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr implements net.Conn.LocalAddr.
|
||||
func (c *TCPConn) LocalAddr() net.Addr {
|
||||
a, err := c.ep.GetLocalAddress()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fullToTCPAddr(a)
|
||||
}
|
||||
|
||||
// RemoteAddr implements net.Conn.RemoteAddr.
|
||||
func (c *TCPConn) RemoteAddr() net.Addr {
|
||||
a, err := c.ep.GetRemoteAddress()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fullToTCPAddr(a)
|
||||
}
|
||||
|
||||
func (c *TCPConn) newOpError(op string, err error) *net.OpError {
|
||||
return &net.OpError{
|
||||
Op: op,
|
||||
Net: "tcp",
|
||||
Source: c.LocalAddr(),
|
||||
Addr: c.RemoteAddr(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func fullToTCPAddr(addr tcpip.FullAddress) *net.TCPAddr {
|
||||
return &net.TCPAddr{IP: net.IP(addr.Addr.AsSlice()), Port: int(addr.Port)}
|
||||
}
|
||||
|
||||
func fullToUDPAddr(addr tcpip.FullAddress) *net.UDPAddr {
|
||||
return &net.UDPAddr{IP: net.IP(addr.Addr.AsSlice()), Port: int(addr.Port)}
|
||||
}
|
||||
|
||||
// DialTCP creates a new TCPConn connected to the specified address.
|
||||
func DialTCP(s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*TCPConn, error) {
|
||||
return DialContextTCP(context.Background(), s, addr, network)
|
||||
}
|
||||
|
||||
// DialTCPWithBind creates a new TCPConn connected to the specified
|
||||
// remoteAddress with its local address bound to localAddr.
|
||||
func DialTCPWithBind(ctx context.Context, s *stack.Stack, localAddr, remoteAddr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*TCPConn, error) {
|
||||
// Create TCP endpoint, then connect.
|
||||
var wq waiter.Queue
|
||||
ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
|
||||
if err != nil {
|
||||
return nil, TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
// Create wait queue entry that notifies a channel.
|
||||
//
|
||||
// We do this unconditionally as Connect will always return an error.
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
|
||||
wq.EventRegister(&waitEntry)
|
||||
defer wq.EventUnregister(&waitEntry)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Bind before connect if requested.
|
||||
if localAddr != (tcpip.FullAddress{}) {
|
||||
if err = ep.Bind(localAddr); err != nil {
|
||||
return nil, fmt.Errorf("ep.Bind(%+v) = %s", localAddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = ep.Connect(remoteAddr)
|
||||
if _, ok := err.(*tcpip.ErrConnectStarted); ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ep.Close()
|
||||
return nil, ctx.Err()
|
||||
case <-notifyCh:
|
||||
}
|
||||
|
||||
err = ep.LastError()
|
||||
}
|
||||
if err != nil {
|
||||
ep.Close()
|
||||
return nil, &net.OpError{
|
||||
Op: "connect",
|
||||
Net: "tcp",
|
||||
Addr: fullToTCPAddr(remoteAddr),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
|
||||
return NewTCPConn(&wq, ep), nil
|
||||
}
|
||||
|
||||
// DialContextTCP creates a new TCPConn connected to the specified address
|
||||
// with the option of adding cancellation and timeouts.
|
||||
func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*TCPConn, error) {
|
||||
return DialTCPWithBind(ctx, s, tcpip.FullAddress{} /* localAddr */, addr /* remoteAddr */, network)
|
||||
}
|
||||
|
||||
// A UDPConn is a wrapper around a UDP tcpip.Endpoint that implements
|
||||
// net.Conn and net.PacketConn.
|
||||
type UDPConn struct {
|
||||
deadlineTimer
|
||||
|
||||
ep tcpip.Endpoint
|
||||
wq *waiter.Queue
|
||||
}
|
||||
|
||||
// NewUDPConn creates a new UDPConn.
|
||||
func NewUDPConn(wq *waiter.Queue, ep tcpip.Endpoint) *UDPConn {
|
||||
c := &UDPConn{
|
||||
ep: ep,
|
||||
wq: wq,
|
||||
}
|
||||
c.deadlineTimer.init()
|
||||
return c
|
||||
}
|
||||
|
||||
// DialUDP creates a new UDPConn.
|
||||
//
|
||||
// If laddr is nil, a local address is automatically chosen.
|
||||
//
|
||||
// If raddr is nil, the UDPConn is left unconnected.
|
||||
func DialUDP(s *stack.Stack, laddr, raddr *tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*UDPConn, error) {
|
||||
var wq waiter.Queue
|
||||
ep, err := s.NewEndpoint(udp.ProtocolNumber, network, &wq)
|
||||
if err != nil {
|
||||
return nil, TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
if laddr != nil {
|
||||
if err := ep.Bind(*laddr); err != nil {
|
||||
ep.Close()
|
||||
return nil, &net.OpError{
|
||||
Op: "bind",
|
||||
Net: "udp",
|
||||
Addr: fullToUDPAddr(*laddr),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c := NewUDPConn(&wq, ep)
|
||||
|
||||
if raddr != nil {
|
||||
if err := c.ep.Connect(*raddr); err != nil {
|
||||
c.ep.Close()
|
||||
return nil, &net.OpError{
|
||||
Op: "connect",
|
||||
Net: "udp",
|
||||
Addr: fullToUDPAddr(*raddr),
|
||||
Err: TranslateNetstackError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *UDPConn) newOpError(op string, err error) *net.OpError {
|
||||
return c.newRemoteOpError(op, nil, err)
|
||||
}
|
||||
|
||||
func (c *UDPConn) newRemoteOpError(op string, remote net.Addr, err error) *net.OpError {
|
||||
return &net.OpError{
|
||||
Op: op,
|
||||
Net: "udp",
|
||||
Source: c.LocalAddr(),
|
||||
Addr: remote,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAddr implements net.Conn.RemoteAddr.
|
||||
func (c *UDPConn) RemoteAddr() net.Addr {
|
||||
a, err := c.ep.GetRemoteAddress()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fullToUDPAddr(a)
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read
|
||||
func (c *UDPConn) Read(b []byte) (int, error) {
|
||||
bytesRead, _, err := c.ReadFrom(b)
|
||||
return bytesRead, err
|
||||
}
|
||||
|
||||
// ReadFrom implements net.PacketConn.ReadFrom.
|
||||
func (c *UDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
deadline := c.readCancel()
|
||||
|
||||
var addr tcpip.FullAddress
|
||||
n, err := commonRead(b, c.ep, c.wq, deadline, &addr, c)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return n, fullToUDPAddr(addr), nil
|
||||
}
|
||||
|
||||
func (c *UDPConn) Write(b []byte) (int, error) {
|
||||
return c.WriteTo(b, nil)
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn.WriteTo.
|
||||
func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
deadline := c.writeCancel()
|
||||
|
||||
// Check if deadline has already expired.
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, c.newRemoteOpError("write", addr, &timeoutError{})
|
||||
default:
|
||||
}
|
||||
|
||||
// If we're being called by Write, there is no addr
|
||||
writeOptions := tcpip.WriteOptions{}
|
||||
if addr != nil {
|
||||
ua := addr.(*net.UDPAddr)
|
||||
writeOptions.To = &tcpip.FullAddress{
|
||||
Addr: tcpip.AddrFromSlice(ua.IP),
|
||||
Port: uint16(ua.Port),
|
||||
}
|
||||
}
|
||||
|
||||
var r bytes.Reader
|
||||
r.Reset(b)
|
||||
n, err := c.ep.Write(&r, writeOptions)
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
|
||||
// Create wait queue entry that notifies a channel.
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
|
||||
c.wq.EventRegister(&waitEntry)
|
||||
defer c.wq.EventUnregister(&waitEntry)
|
||||
for {
|
||||
select {
|
||||
case <-deadline:
|
||||
return int(n), c.newRemoteOpError("write", addr, &timeoutError{})
|
||||
case <-notifyCh:
|
||||
}
|
||||
|
||||
n, err = c.ep.Write(&r, writeOptions)
|
||||
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
return int(n), c.newRemoteOpError("write", addr, TranslateNetstackError(err))
|
||||
}
|
||||
|
||||
// Close implements net.PacketConn.Close.
|
||||
func (c *UDPConn) Close() error {
|
||||
c.ep.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr implements net.PacketConn.LocalAddr.
|
||||
func (c *UDPConn) LocalAddr() net.Addr {
|
||||
a, err := c.ep.GetLocalAddress()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fullToUDPAddr(a)
|
||||
}
|
||||
3
pkg/tcpip/adapters/gonet/gonet_state_autogen.go
Normal file
3
pkg/tcpip/adapters/gonet/gonet_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package gonet
|
||||
68
pkg/tcpip/checksum/checksum.go
Normal file
68
pkg/tcpip/checksum/checksum.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// 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 checksum provides the implementation of the encoding and decoding of
|
||||
// network protocol headers.
|
||||
package checksum
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Size is the size of a checksum.
|
||||
//
|
||||
// The checksum is held in a uint16 which is 2 bytes.
|
||||
const Size = 2
|
||||
|
||||
// Put puts the checksum in the provided byte slice.
|
||||
func Put(b []byte, xsum uint16) {
|
||||
binary.BigEndian.PutUint16(b, xsum)
|
||||
}
|
||||
|
||||
// Checksum calculates the checksum (as defined in RFC 1071) of the bytes in the
|
||||
// given byte array. This function uses an optimized version of the checksum
|
||||
// algorithm.
|
||||
//
|
||||
// The initial checksum must have been computed on an even number of bytes.
|
||||
func Checksum(buf []byte, initial uint16) uint16 {
|
||||
s, _ := calculateChecksum(buf, false, initial)
|
||||
return s
|
||||
}
|
||||
|
||||
// Checksumer calculates checksum defined in RFC 1071.
|
||||
type Checksumer struct {
|
||||
sum uint16
|
||||
odd bool
|
||||
}
|
||||
|
||||
// Add adds b to checksum.
|
||||
func (c *Checksumer) Add(b []byte) {
|
||||
if len(b) > 0 {
|
||||
c.sum, c.odd = calculateChecksum(b, c.odd, c.sum)
|
||||
}
|
||||
}
|
||||
|
||||
// Checksum returns the latest checksum value.
|
||||
func (c *Checksumer) Checksum() uint16 {
|
||||
return c.sum
|
||||
}
|
||||
|
||||
// Combine combines the two uint16 to form their checksum. This is done
|
||||
// by adding them and the carry.
|
||||
//
|
||||
// Note that checksum a must have been computed on an even number of bytes.
|
||||
func Combine(a, b uint16) uint16 {
|
||||
v := uint32(a) + uint32(b)
|
||||
return uint16(v + v>>16)
|
||||
}
|
||||
3
pkg/tcpip/checksum/checksum_state_autogen.go
Normal file
3
pkg/tcpip/checksum/checksum_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package checksum
|
||||
182
pkg/tcpip/checksum/checksum_unsafe.go
Normal file
182
pkg/tcpip/checksum/checksum_unsafe.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2023 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 checksum
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Note: odd indicates whether initial is a partial checksum over an odd number
|
||||
// of bytes.
|
||||
func calculateChecksum(buf []byte, odd bool, initial uint16) (uint16, bool) {
|
||||
// Use a larger-than-uint16 accumulator to benefit from parallel summation
|
||||
// as described in RFC 1071 1.2.C.
|
||||
acc := uint64(initial)
|
||||
|
||||
// Handle an odd number of previously-summed bytes, and get the return
|
||||
// value for odd.
|
||||
if odd {
|
||||
acc += uint64(buf[0])
|
||||
buf = buf[1:]
|
||||
}
|
||||
odd = len(buf)&1 != 0
|
||||
|
||||
// Aligning &buf[0] below is much simpler if len(buf) >= 8; special-case
|
||||
// smaller bufs.
|
||||
if len(buf) < 8 {
|
||||
if len(buf) >= 4 {
|
||||
acc += (uint64(buf[0]) << 8) + uint64(buf[1])
|
||||
acc += (uint64(buf[2]) << 8) + uint64(buf[3])
|
||||
buf = buf[4:]
|
||||
}
|
||||
if len(buf) >= 2 {
|
||||
acc += (uint64(buf[0]) << 8) + uint64(buf[1])
|
||||
buf = buf[2:]
|
||||
}
|
||||
if len(buf) >= 1 {
|
||||
acc += uint64(buf[0]) << 8
|
||||
// buf = buf[1:] is skipped because it's unused and nogo will
|
||||
// complain.
|
||||
}
|
||||
return reduce(acc), odd
|
||||
}
|
||||
|
||||
// On little-endian architectures, multi-byte loads from buf will load
|
||||
// bytes in the wrong order. Rather than byte-swap after each load (slow),
|
||||
// we byte-swap the accumulator before summing any bytes and byte-swap it
|
||||
// back before returning, which still produces the correct result as
|
||||
// described in RFC 1071 1.2.B "Byte Order Independence".
|
||||
//
|
||||
// acc is at most a uint16 + a uint8, so its upper 32 bits must be 0s. We
|
||||
// preserve this property by byte-swapping only the lower 32 bits of acc,
|
||||
// so that additions to acc performed during alignment can't overflow.
|
||||
acc = uint64(bswapIfLittleEndian32(uint32(acc)))
|
||||
|
||||
// Align &buf[0] to an 8-byte boundary.
|
||||
bswapped := false
|
||||
if sliceAddr(buf)&1 != 0 {
|
||||
// Compute the rest of the partial checksum with bytes swapped, and
|
||||
// swap back before returning; see the last paragraph of
|
||||
// RFC 1071 1.2.B.
|
||||
acc = uint64(bits.ReverseBytes32(uint32(acc)))
|
||||
bswapped = true
|
||||
// No `<< 8` here due to the byte swap we just did.
|
||||
acc += uint64(bswapIfLittleEndian16(uint16(buf[0])))
|
||||
buf = buf[1:]
|
||||
}
|
||||
if sliceAddr(buf)&2 != 0 {
|
||||
acc += uint64(*(*uint16)(unsafe.Pointer(&buf[0])))
|
||||
buf = buf[2:]
|
||||
}
|
||||
if sliceAddr(buf)&4 != 0 {
|
||||
acc += uint64(*(*uint32)(unsafe.Pointer(&buf[0])))
|
||||
buf = buf[4:]
|
||||
}
|
||||
|
||||
// Sum 64 bytes at a time. Beyond this point, additions to acc may
|
||||
// overflow, so we have to handle carrying.
|
||||
for len(buf) >= 64 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[0])), 0)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[8])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[16])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[24])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[32])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[40])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[48])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[56])), carry)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[64:]
|
||||
}
|
||||
|
||||
// Sum the remaining 0-63 bytes.
|
||||
if len(buf) >= 32 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[0])), 0)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[8])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[16])), carry)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[24])), carry)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[32:]
|
||||
}
|
||||
if len(buf) >= 16 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[0])), 0)
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[8])), carry)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[16:]
|
||||
}
|
||||
if len(buf) >= 8 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, *(*uint64)(unsafe.Pointer(&buf[0])), 0)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[8:]
|
||||
}
|
||||
if len(buf) >= 4 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, uint64(*(*uint32)(unsafe.Pointer(&buf[0]))), 0)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[4:]
|
||||
}
|
||||
if len(buf) >= 2 {
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, uint64(*(*uint16)(unsafe.Pointer(&buf[0]))), 0)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
buf = buf[2:]
|
||||
}
|
||||
if len(buf) >= 1 {
|
||||
// bswapIfBigEndian16(buf[0]) == bswapIfLittleEndian16(buf[0]<<8).
|
||||
var carry uint64
|
||||
acc, carry = bits.Add64(acc, uint64(bswapIfBigEndian16(uint16(buf[0]))), 0)
|
||||
acc, _ = bits.Add64(acc, 0, carry)
|
||||
// buf = buf[1:] is skipped because it's unused and nogo will complain.
|
||||
}
|
||||
|
||||
// Reduce the checksum to 16 bits and undo byte swaps before returning.
|
||||
acc16 := bswapIfLittleEndian16(reduce(acc))
|
||||
if bswapped {
|
||||
acc16 = bits.ReverseBytes16(acc16)
|
||||
}
|
||||
return acc16, odd
|
||||
}
|
||||
|
||||
func reduce(acc uint64) uint16 {
|
||||
// Ideally we would do:
|
||||
// return uint16(acc>>48) +' uint16(acc>>32) +' uint16(acc>>16) +' uint16(acc)
|
||||
// for more instruction-level parallelism; however, there is no
|
||||
// bits.Add16().
|
||||
acc = (acc >> 32) + (acc & 0xffff_ffff) // at most 0x1_ffff_fffe
|
||||
acc32 := uint32(acc>>32 + acc) // at most 0xffff_ffff
|
||||
acc32 = (acc32 >> 16) + (acc32 & 0xffff) // at most 0x1_fffe
|
||||
return uint16(acc32>>16 + acc32) // at most 0xffff
|
||||
}
|
||||
|
||||
func bswapIfLittleEndian32(val uint32) uint32 {
|
||||
return binary.BigEndian.Uint32((*[4]byte)(unsafe.Pointer(&val))[:])
|
||||
}
|
||||
|
||||
func bswapIfLittleEndian16(val uint16) uint16 {
|
||||
return binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&val))[:])
|
||||
}
|
||||
|
||||
func bswapIfBigEndian16(val uint16) uint16 {
|
||||
return binary.LittleEndian.Uint16((*[2]byte)(unsafe.Pointer(&val))[:])
|
||||
}
|
||||
|
||||
func sliceAddr(buf []byte) uintptr {
|
||||
return uintptr(unsafe.Pointer(unsafe.SliceData(buf)))
|
||||
}
|
||||
3
pkg/tcpip/checksum/checksum_unsafe_state_autogen.go
Normal file
3
pkg/tcpip/checksum/checksum_unsafe_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package checksum
|
||||
643
pkg/tcpip/errors.go
Normal file
643
pkg/tcpip/errors.go
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
// Copyright 2021 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 tcpip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error represents an error in the netstack error space.
|
||||
//
|
||||
// The error interface is intentionally omitted to avoid loss of type
|
||||
// information that would occur if these errors were passed as error.
|
||||
type Error interface {
|
||||
isError()
|
||||
|
||||
// IgnoreStats indicates whether this error should be included in failure
|
||||
// counts in tcpip.Stats structs.
|
||||
IgnoreStats() bool
|
||||
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
const maxErrno = 134
|
||||
|
||||
// LINT.IfChange
|
||||
|
||||
// ErrAborted indicates the operation was aborted.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrAborted struct{}
|
||||
|
||||
func (*ErrAborted) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrAborted) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*ErrAborted) String() string {
|
||||
return "operation aborted"
|
||||
}
|
||||
|
||||
// ErrAddressFamilyNotSupported indicates the operation does not support the
|
||||
// given address family.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrAddressFamilyNotSupported struct{}
|
||||
|
||||
func (*ErrAddressFamilyNotSupported) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrAddressFamilyNotSupported) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*ErrAddressFamilyNotSupported) String() string {
|
||||
return "address family not supported by protocol"
|
||||
}
|
||||
|
||||
// ErrAlreadyBound indicates the endpoint is already bound.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrAlreadyBound struct{}
|
||||
|
||||
func (*ErrAlreadyBound) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrAlreadyBound) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrAlreadyBound) String() string { return "endpoint already bound" }
|
||||
|
||||
// ErrAlreadyConnected indicates the endpoint is already connected.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrAlreadyConnected struct{}
|
||||
|
||||
func (*ErrAlreadyConnected) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrAlreadyConnected) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrAlreadyConnected) String() string { return "endpoint is already connected" }
|
||||
|
||||
// ErrAlreadyConnecting indicates the endpoint is already connecting.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrAlreadyConnecting struct{}
|
||||
|
||||
func (*ErrAlreadyConnecting) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrAlreadyConnecting) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrAlreadyConnecting) String() string { return "endpoint is already connecting" }
|
||||
|
||||
// ErrBadAddress indicates a bad address was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrBadAddress struct{}
|
||||
|
||||
func (*ErrBadAddress) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrBadAddress) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrBadAddress) String() string { return "bad address" }
|
||||
|
||||
// ErrBadBuffer indicates a bad buffer was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrBadBuffer struct{}
|
||||
|
||||
func (*ErrBadBuffer) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrBadBuffer) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrBadBuffer) String() string { return "bad buffer" }
|
||||
|
||||
// ErrBadLocalAddress indicates a bad local address was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrBadLocalAddress struct{}
|
||||
|
||||
func (*ErrBadLocalAddress) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrBadLocalAddress) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrBadLocalAddress) String() string { return "bad local address" }
|
||||
|
||||
// ErrBroadcastDisabled indicates broadcast is not enabled on the endpoint.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrBroadcastDisabled struct{}
|
||||
|
||||
func (*ErrBroadcastDisabled) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrBroadcastDisabled) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrBroadcastDisabled) String() string { return "broadcast socket option disabled" }
|
||||
|
||||
// ErrClosedForReceive indicates the endpoint is closed for incoming data.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrClosedForReceive struct{}
|
||||
|
||||
func (*ErrClosedForReceive) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrClosedForReceive) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrClosedForReceive) String() string { return "endpoint is closed for receive" }
|
||||
|
||||
// ErrClosedForSend indicates the endpoint is closed for outgoing data.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrClosedForSend struct{}
|
||||
|
||||
func (*ErrClosedForSend) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrClosedForSend) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrClosedForSend) String() string { return "endpoint is closed for send" }
|
||||
|
||||
// ErrConnectStarted indicates the endpoint is connecting asynchronously.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrConnectStarted struct{}
|
||||
|
||||
func (*ErrConnectStarted) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrConnectStarted) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrConnectStarted) String() string { return "connection attempt started" }
|
||||
|
||||
// ErrConnectionAborted indicates the connection was aborted.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrConnectionAborted struct{}
|
||||
|
||||
func (*ErrConnectionAborted) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrConnectionAborted) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrConnectionAborted) String() string { return "connection aborted" }
|
||||
|
||||
// ErrConnectionRefused indicates the connection was refused.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrConnectionRefused struct{}
|
||||
|
||||
func (*ErrConnectionRefused) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrConnectionRefused) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrConnectionRefused) String() string { return "connection was refused" }
|
||||
|
||||
// ErrConnectionReset indicates the connection was reset.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrConnectionReset struct{}
|
||||
|
||||
func (*ErrConnectionReset) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrConnectionReset) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrConnectionReset) String() string { return "connection reset by peer" }
|
||||
|
||||
// ErrDestinationRequired indicates the operation requires a destination
|
||||
// address, and one was not provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrDestinationRequired struct{}
|
||||
|
||||
func (*ErrDestinationRequired) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrDestinationRequired) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrDestinationRequired) String() string { return "destination address is required" }
|
||||
|
||||
// ErrDuplicateAddress indicates the operation encountered a duplicate address.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrDuplicateAddress struct{}
|
||||
|
||||
func (*ErrDuplicateAddress) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrDuplicateAddress) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrDuplicateAddress) String() string { return "duplicate address" }
|
||||
|
||||
// ErrDuplicateNICID indicates the operation encountered a duplicate NIC ID.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrDuplicateNICID struct{}
|
||||
|
||||
func (*ErrDuplicateNICID) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrDuplicateNICID) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrDuplicateNICID) String() string { return "duplicate nic id" }
|
||||
|
||||
// ErrInvalidNICID indicates the operation used an invalid NIC ID.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrInvalidNICID struct{}
|
||||
|
||||
func (*ErrInvalidNICID) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrInvalidNICID) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrInvalidNICID) String() string { return "invalid nic id" }
|
||||
|
||||
// ErrInvalidEndpointState indicates the endpoint is in an invalid state.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrInvalidEndpointState struct{}
|
||||
|
||||
func (*ErrInvalidEndpointState) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrInvalidEndpointState) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrInvalidEndpointState) String() string { return "endpoint is in invalid state" }
|
||||
|
||||
// ErrInvalidOptionValue indicates an invalid option value was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrInvalidOptionValue struct{}
|
||||
|
||||
func (*ErrInvalidOptionValue) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrInvalidOptionValue) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrInvalidOptionValue) String() string { return "invalid option value specified" }
|
||||
|
||||
// ErrInvalidPortRange indicates an attempt to set an invalid port range.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrInvalidPortRange struct{}
|
||||
|
||||
func (*ErrInvalidPortRange) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrInvalidPortRange) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrInvalidPortRange) String() string { return "invalid port range" }
|
||||
|
||||
// ErrMalformedHeader indicates the operation encountered a malformed header.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrMalformedHeader struct{}
|
||||
|
||||
func (*ErrMalformedHeader) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrMalformedHeader) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrMalformedHeader) String() string { return "header is malformed" }
|
||||
|
||||
// ErrMessageTooLong indicates the operation encountered a message whose length
|
||||
// exceeds the maximum permitted.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrMessageTooLong struct{}
|
||||
|
||||
func (*ErrMessageTooLong) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrMessageTooLong) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrMessageTooLong) String() string { return "message too long" }
|
||||
|
||||
// ErrNetworkUnreachable indicates the operation is not able to reach the
|
||||
// destination network.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNetworkUnreachable struct{}
|
||||
|
||||
func (*ErrNetworkUnreachable) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNetworkUnreachable) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNetworkUnreachable) String() string { return "network is unreachable" }
|
||||
|
||||
// ErrNoBufferSpace indicates no buffer space is available.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNoBufferSpace struct{}
|
||||
|
||||
func (*ErrNoBufferSpace) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNoBufferSpace) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNoBufferSpace) String() string { return "no buffer space available" }
|
||||
|
||||
// ErrNoPortAvailable indicates no port could be allocated for the operation.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNoPortAvailable struct{}
|
||||
|
||||
func (*ErrNoPortAvailable) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNoPortAvailable) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNoPortAvailable) String() string { return "no ports are available" }
|
||||
|
||||
// ErrHostUnreachable indicates that a destination host could not be
|
||||
// reached.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrHostUnreachable struct{}
|
||||
|
||||
func (*ErrHostUnreachable) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrHostUnreachable) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrHostUnreachable) String() string { return "no route to host" }
|
||||
|
||||
// ErrHostDown indicates that a destination host is down.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrHostDown struct{}
|
||||
|
||||
func (*ErrHostDown) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrHostDown) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrHostDown) String() string { return "host is down" }
|
||||
|
||||
// ErrNoNet indicates that the host is not on the network.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNoNet struct{}
|
||||
|
||||
func (*ErrNoNet) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNoNet) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNoNet) String() string { return "machine is not on the network" }
|
||||
|
||||
// ErrNoSuchFile is used to indicate that ENOENT should be returned the to
|
||||
// calling application.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNoSuchFile struct{}
|
||||
|
||||
func (*ErrNoSuchFile) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNoSuchFile) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNoSuchFile) String() string { return "no such file" }
|
||||
|
||||
// ErrNotConnected indicates the endpoint is not connected.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNotConnected struct{}
|
||||
|
||||
func (*ErrNotConnected) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNotConnected) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNotConnected) String() string { return "endpoint not connected" }
|
||||
|
||||
// ErrNotPermitted indicates the operation is not permitted.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNotPermitted struct{}
|
||||
|
||||
func (*ErrNotPermitted) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNotPermitted) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNotPermitted) String() string { return "operation not permitted" }
|
||||
|
||||
// ErrNotSupported indicates the operation is not supported.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrNotSupported struct{}
|
||||
|
||||
func (*ErrNotSupported) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrNotSupported) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrNotSupported) String() string { return "operation not supported" }
|
||||
|
||||
// ErrPortInUse indicates the provided port is in use.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrPortInUse struct{}
|
||||
|
||||
func (*ErrPortInUse) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrPortInUse) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrPortInUse) String() string { return "port is in use" }
|
||||
|
||||
// ErrQueueSizeNotSupported indicates the endpoint does not allow queue size
|
||||
// operation.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrQueueSizeNotSupported struct{}
|
||||
|
||||
func (*ErrQueueSizeNotSupported) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrQueueSizeNotSupported) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrQueueSizeNotSupported) String() string { return "queue size querying not supported" }
|
||||
|
||||
// ErrTimeout indicates the operation timed out.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrTimeout struct{}
|
||||
|
||||
func (*ErrTimeout) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrTimeout) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrTimeout) String() string { return "operation timed out" }
|
||||
|
||||
// ErrUnknownDevice indicates an unknown device identifier was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrUnknownDevice struct{}
|
||||
|
||||
func (*ErrUnknownDevice) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrUnknownDevice) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrUnknownDevice) String() string { return "unknown device" }
|
||||
|
||||
// ErrUnknownNICID indicates an unknown NIC ID was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrUnknownNICID struct{}
|
||||
|
||||
func (*ErrUnknownNICID) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrUnknownNICID) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrUnknownNICID) String() string { return "unknown nic id" }
|
||||
|
||||
// ErrUnknownProtocol indicates an unknown protocol was requested.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrUnknownProtocol struct{}
|
||||
|
||||
func (*ErrUnknownProtocol) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrUnknownProtocol) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrUnknownProtocol) String() string { return "unknown protocol" }
|
||||
|
||||
// ErrUnknownProtocolOption indicates an unknown protocol option was provided.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrUnknownProtocolOption struct{}
|
||||
|
||||
func (*ErrUnknownProtocolOption) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrUnknownProtocolOption) IgnoreStats() bool {
|
||||
return false
|
||||
}
|
||||
func (*ErrUnknownProtocolOption) String() string { return "unknown option for protocol" }
|
||||
|
||||
// ErrWouldBlock indicates the operation would block.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrWouldBlock struct{}
|
||||
|
||||
func (*ErrWouldBlock) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrWouldBlock) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrWouldBlock) String() string { return "operation would block" }
|
||||
|
||||
// ErrMissingRequiredFields indicates that a required field is missing.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrMissingRequiredFields struct{}
|
||||
|
||||
func (*ErrMissingRequiredFields) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrMissingRequiredFields) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrMissingRequiredFields) String() string { return "missing required fields" }
|
||||
|
||||
// ErrMulticastInputCannotBeOutput indicates that an input interface matches an
|
||||
// output interface in the same multicast route.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrMulticastInputCannotBeOutput struct{}
|
||||
|
||||
func (*ErrMulticastInputCannotBeOutput) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrMulticastInputCannotBeOutput) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
func (*ErrMulticastInputCannotBeOutput) String() string { return "output cannot contain input" }
|
||||
|
||||
// ErrEndpointBusy indicates that the operation cannot be completed because the
|
||||
// endpoint is busy.
|
||||
//
|
||||
// +stateify savable
|
||||
type ErrEndpointBusy struct{}
|
||||
|
||||
// isError implements Error.
|
||||
func (*ErrEndpointBusy) isError() {}
|
||||
|
||||
// IgnoreStats implements Error.
|
||||
func (*ErrEndpointBusy) IgnoreStats() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (*ErrEndpointBusy) String() string {
|
||||
return "operation cannot be completed because the endpoint is busy"
|
||||
}
|
||||
|
||||
// LINT.ThenChange(../syserr/netstack.go)
|
||||
74
pkg/tcpip/errors_linux.go
Normal file
74
pkg/tcpip/errors_linux.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2024 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package tcpip
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// TranslateErrno translate an errno from the syscall package into a
|
||||
// tcpip Error.
|
||||
//
|
||||
// Valid, but unrecognized errnos will be translated to
|
||||
// *ErrInvalidEndpointState (EINVAL). This includes the "zero" value.
|
||||
func TranslateErrno(e unix.Errno) Error {
|
||||
switch e {
|
||||
case unix.EEXIST:
|
||||
return &ErrDuplicateAddress{}
|
||||
case unix.ENETUNREACH:
|
||||
return &ErrHostUnreachable{}
|
||||
case unix.EINVAL:
|
||||
return &ErrInvalidEndpointState{}
|
||||
case unix.EALREADY:
|
||||
return &ErrAlreadyConnecting{}
|
||||
case unix.EISCONN:
|
||||
return &ErrAlreadyConnected{}
|
||||
case unix.EADDRINUSE:
|
||||
return &ErrPortInUse{}
|
||||
case unix.EADDRNOTAVAIL:
|
||||
return &ErrBadLocalAddress{}
|
||||
case unix.EPIPE:
|
||||
return &ErrClosedForSend{}
|
||||
case unix.EWOULDBLOCK:
|
||||
return &ErrWouldBlock{}
|
||||
case unix.ECONNREFUSED:
|
||||
return &ErrConnectionRefused{}
|
||||
case unix.ETIMEDOUT:
|
||||
return &ErrTimeout{}
|
||||
case unix.EINPROGRESS:
|
||||
return &ErrConnectStarted{}
|
||||
case unix.EDESTADDRREQ:
|
||||
return &ErrDestinationRequired{}
|
||||
case unix.ENOTSUP:
|
||||
return &ErrNotSupported{}
|
||||
case unix.ENOTTY:
|
||||
return &ErrQueueSizeNotSupported{}
|
||||
case unix.ENOTCONN:
|
||||
return &ErrNotConnected{}
|
||||
case unix.ECONNRESET:
|
||||
return &ErrConnectionReset{}
|
||||
case unix.ECONNABORTED:
|
||||
return &ErrConnectionAborted{}
|
||||
case unix.EMSGSIZE:
|
||||
return &ErrMessageTooLong{}
|
||||
case unix.ENOBUFS:
|
||||
return &ErrNoBufferSpace{}
|
||||
default:
|
||||
return &ErrInvalidEndpointState{}
|
||||
}
|
||||
}
|
||||
392
pkg/tcpip/faketime/faketime.go
Normal file
392
pkg/tcpip/faketime/faketime.go
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
// Copyright 2020 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 faketime provides a fake clock that implements tcpip.Clock interface.
|
||||
package faketime
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
// NullClock implements a clock that never advances.
|
||||
//
|
||||
// +stateify savable
|
||||
type NullClock struct{}
|
||||
|
||||
var _ tcpip.Clock = (*NullClock)(nil)
|
||||
|
||||
// Now implements tcpip.Clock.Now.
|
||||
func (*NullClock) Now() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// NowMonotonic implements tcpip.Clock.NowMonotonic.
|
||||
func (*NullClock) NowMonotonic() tcpip.MonotonicTime {
|
||||
return tcpip.MonotonicTime{}
|
||||
}
|
||||
|
||||
// nullTimer implements a timer that never fires.
|
||||
//
|
||||
// +stateify savable
|
||||
type nullTimer struct{}
|
||||
|
||||
var _ tcpip.Timer = (*nullTimer)(nil)
|
||||
|
||||
// Stop implements tcpip.Timer.
|
||||
func (*nullTimer) Stop() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Reset implements tcpip.Timer.
|
||||
func (*nullTimer) Reset(time.Duration) {}
|
||||
|
||||
// AfterFunc implements tcpip.Clock.AfterFunc.
|
||||
func (*NullClock) AfterFunc(time.Duration, func()) tcpip.Timer {
|
||||
return &nullTimer{}
|
||||
}
|
||||
|
||||
type notificationChannels struct {
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
|
||||
ch []<-chan struct{}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *notificationChannels) add(ch <-chan struct{}) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
n.mu.ch = append(n.mu.ch, ch)
|
||||
}
|
||||
|
||||
// wait returns once all the notification channels are readable.
|
||||
//
|
||||
// Channels that are added while waiting on existing channels will be waited on
|
||||
// as well.
|
||||
func (n *notificationChannels) wait() {
|
||||
for {
|
||||
n.mu.Lock()
|
||||
ch := n.mu.ch
|
||||
n.mu.ch = nil
|
||||
n.mu.Unlock()
|
||||
|
||||
if len(ch) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, c := range ch {
|
||||
<-c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualClockMutex struct {
|
||||
sync.RWMutex `state:"nosave"`
|
||||
|
||||
// now is the current (fake) time of the clock.
|
||||
now time.Time
|
||||
|
||||
// times is min-heap of times.
|
||||
times timeHeap
|
||||
|
||||
// timers holds the timers scheduled for each time.
|
||||
timers map[time.Time]map[*manualTimer]struct{}
|
||||
}
|
||||
|
||||
// ManualClock implements tcpip.Clock and only advances manually with Advance
|
||||
// method.
|
||||
//
|
||||
// +stateify savable
|
||||
type ManualClock struct {
|
||||
// runningTimers tracks the completion of timer callbacks that began running
|
||||
// immediately upon their scheduling. It is used to ensure the proper ordering
|
||||
// of timer callback dispatch.
|
||||
runningTimers notificationChannels
|
||||
|
||||
mu manualClockMutex
|
||||
}
|
||||
|
||||
// NewManualClock creates a new ManualClock instance.
|
||||
func NewManualClock() *ManualClock {
|
||||
c := &ManualClock{}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Set the initial time to a non-zero value since the zero value is used to
|
||||
// detect inactive timers.
|
||||
c.mu.now = time.Unix(0, 0)
|
||||
c.mu.timers = make(map[time.Time]map[*manualTimer]struct{})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
var _ tcpip.Clock = (*ManualClock)(nil)
|
||||
|
||||
// Now implements tcpip.Clock.Now.
|
||||
func (mc *ManualClock) Now() time.Time {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
return mc.mu.now
|
||||
}
|
||||
|
||||
// NowMonotonic implements tcpip.Clock.NowMonotonic.
|
||||
func (mc *ManualClock) NowMonotonic() tcpip.MonotonicTime {
|
||||
var mt tcpip.MonotonicTime
|
||||
return mt.Add(mc.Now().Sub(time.Unix(0, 0)))
|
||||
}
|
||||
|
||||
// AfterFunc implements tcpip.Clock.AfterFunc.
|
||||
func (mc *ManualClock) AfterFunc(d time.Duration, f func()) tcpip.Timer {
|
||||
mt := &manualTimer{
|
||||
clock: mc,
|
||||
f: f,
|
||||
}
|
||||
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
mt.mu.Lock()
|
||||
defer mt.mu.Unlock()
|
||||
|
||||
mc.resetTimerLocked(mt, d)
|
||||
return mt
|
||||
}
|
||||
|
||||
// resetTimerLocked schedules a timer to be fired after the given duration.
|
||||
//
|
||||
// Precondition: mc.mu and mt.mu must be locked.
|
||||
func (mc *ManualClock) resetTimerLocked(mt *manualTimer, d time.Duration) {
|
||||
if !mt.mu.firesAt.IsZero() {
|
||||
panic("tried to reset an active timer")
|
||||
}
|
||||
|
||||
t := mc.mu.now.Add(d)
|
||||
|
||||
if !mc.mu.now.Before(t) {
|
||||
// If the timer is scheduled to fire immediately, call its callback
|
||||
// in a new goroutine immediately.
|
||||
//
|
||||
// It needs to be called in its own goroutine to escape its current
|
||||
// execution context - like an actual timer.
|
||||
ch := make(chan struct{})
|
||||
mc.runningTimers.add(ch)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
|
||||
mt.f()
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mt.mu.firesAt = t
|
||||
|
||||
timers, ok := mc.mu.timers[t]
|
||||
if !ok {
|
||||
timers = make(map[*manualTimer]struct{})
|
||||
mc.mu.timers[t] = timers
|
||||
heap.Push(&mc.mu.times, t)
|
||||
}
|
||||
|
||||
timers[mt] = struct{}{}
|
||||
}
|
||||
|
||||
// stopTimerLocked stops a timer from firing.
|
||||
//
|
||||
// Precondition: mc.mu and mt.mu must be locked.
|
||||
func (mc *ManualClock) stopTimerLocked(mt *manualTimer) {
|
||||
t := mt.mu.firesAt
|
||||
mt.mu.firesAt = time.Time{}
|
||||
|
||||
if t.IsZero() {
|
||||
panic("tried to stop an inactive timer")
|
||||
}
|
||||
|
||||
timers, ok := mc.mu.timers[t]
|
||||
if !ok {
|
||||
err := fmt.Sprintf("tried to stop an active timer but the clock does not have anything scheduled for the timer @ t = %s %p\nScheduled timers @:", t.UTC(), mt)
|
||||
for t := range mc.mu.timers {
|
||||
err += fmt.Sprintf("%s\n", t.UTC())
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, ok := timers[mt]; !ok {
|
||||
panic(fmt.Sprintf("did not have an entry in timers for an active timer @ t = %s", t.UTC()))
|
||||
}
|
||||
|
||||
delete(timers, mt)
|
||||
|
||||
if len(timers) == 0 {
|
||||
delete(mc.mu.timers, t)
|
||||
}
|
||||
}
|
||||
|
||||
// RunImmediatelyScheduledJobs runs all jobs scheduled to run at the current
|
||||
// time.
|
||||
func (mc *ManualClock) RunImmediatelyScheduledJobs() {
|
||||
mc.Advance(0)
|
||||
}
|
||||
|
||||
// Advance executes all work that have been scheduled to execute within d from
|
||||
// the current time. Blocks until all work has completed execution.
|
||||
func (mc *ManualClock) Advance(d time.Duration) {
|
||||
// We spawn goroutines for timers that were scheduled to fire at the time of
|
||||
// being reset. Wait for those goroutines to complete before proceeding so
|
||||
// that timer callbacks are called in the right order.
|
||||
mc.runningTimers.wait()
|
||||
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
until := mc.mu.now.Add(d)
|
||||
for mc.mu.times.Len() > 0 {
|
||||
t := heap.Pop(&mc.mu.times).(time.Time)
|
||||
if t.After(until) {
|
||||
// No work to do
|
||||
heap.Push(&mc.mu.times, t)
|
||||
break
|
||||
}
|
||||
|
||||
timers := mc.mu.timers[t]
|
||||
delete(mc.mu.timers, t)
|
||||
|
||||
mc.mu.now = t
|
||||
|
||||
// Mark the timers as inactive since they will be fired.
|
||||
//
|
||||
// This needs to be done while holding mc's lock because we remove the entry
|
||||
// in the map of timers for the current time. If an attempt to stop a
|
||||
// timer is made after mc's lock was dropped but before the timer is
|
||||
// marked inactive, we would panic since no entry exists for the time when
|
||||
// the timer was expected to fire.
|
||||
for mt := range timers {
|
||||
mt.mu.Lock()
|
||||
mt.mu.firesAt = time.Time{}
|
||||
mt.mu.Unlock()
|
||||
}
|
||||
|
||||
// Release the lock before calling the timer's callback fn since the
|
||||
// callback fn might try to schedule a timer which requires obtaining
|
||||
// mc's lock.
|
||||
mc.mu.Unlock()
|
||||
|
||||
for mt := range timers {
|
||||
mt.f()
|
||||
}
|
||||
|
||||
// The timer callbacks may have scheduled a timer to fire immediately.
|
||||
// We spawn goroutines for these timers and need to wait for them to
|
||||
// finish before proceeding so that timer callbacks are called in the
|
||||
// right order.
|
||||
mc.runningTimers.wait()
|
||||
mc.mu.Lock()
|
||||
}
|
||||
|
||||
mc.mu.now = until
|
||||
}
|
||||
|
||||
func (mc *ManualClock) resetTimer(mt *manualTimer, d time.Duration) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
mt.mu.Lock()
|
||||
defer mt.mu.Unlock()
|
||||
|
||||
if !mt.mu.firesAt.IsZero() {
|
||||
mc.stopTimerLocked(mt)
|
||||
}
|
||||
|
||||
mc.resetTimerLocked(mt, d)
|
||||
}
|
||||
|
||||
func (mc *ManualClock) stopTimer(mt *manualTimer) bool {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
mt.mu.Lock()
|
||||
defer mt.mu.Unlock()
|
||||
|
||||
if mt.mu.firesAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
mc.stopTimerLocked(mt)
|
||||
return true
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualTimerMu struct {
|
||||
sync.Mutex `state:"nosave"`
|
||||
|
||||
// firesAt is the time when the timer will fire.
|
||||
//
|
||||
// Zero only when the timer is not active.
|
||||
firesAt time.Time
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualTimer struct {
|
||||
clock *ManualClock
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
f func() `state:"nosave"`
|
||||
|
||||
mu manualTimerMu
|
||||
}
|
||||
|
||||
var _ tcpip.Timer = (*manualTimer)(nil)
|
||||
|
||||
// Reset implements tcpip.Timer.Reset.
|
||||
func (mt *manualTimer) Reset(d time.Duration) {
|
||||
mt.clock.resetTimer(mt, d)
|
||||
}
|
||||
|
||||
// Stop implements tcpip.Timer.Stop.
|
||||
func (mt *manualTimer) Stop() bool {
|
||||
return mt.clock.stopTimer(mt)
|
||||
}
|
||||
|
||||
type timeHeap []time.Time
|
||||
|
||||
var _ heap.Interface = (*timeHeap)(nil)
|
||||
|
||||
func (h timeHeap) Len() int {
|
||||
return len(h)
|
||||
}
|
||||
|
||||
func (h timeHeap) Less(i, j int) bool {
|
||||
return h[i].Before(h[j])
|
||||
}
|
||||
|
||||
func (h timeHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
}
|
||||
|
||||
func (h *timeHeap) Push(x any) {
|
||||
*h = append(*h, x.(time.Time))
|
||||
}
|
||||
|
||||
func (h *timeHeap) Pop() any {
|
||||
last := (*h)[len(*h)-1]
|
||||
*h = (*h)[:len(*h)-1]
|
||||
return last
|
||||
}
|
||||
172
pkg/tcpip/faketime/faketime_state_autogen.go
Normal file
172
pkg/tcpip/faketime/faketime_state_autogen.go
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package faketime
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (n *NullClock) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.NullClock"
|
||||
}
|
||||
|
||||
func (n *NullClock) StateFields() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (n *NullClock) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NullClock) StateSave(stateSinkObject state.Sink) {
|
||||
n.beforeSave()
|
||||
}
|
||||
|
||||
func (n *NullClock) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NullClock) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
}
|
||||
|
||||
func (n *nullTimer) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.nullTimer"
|
||||
}
|
||||
|
||||
func (n *nullTimer) StateFields() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (n *nullTimer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *nullTimer) StateSave(stateSinkObject state.Sink) {
|
||||
n.beforeSave()
|
||||
}
|
||||
|
||||
func (n *nullTimer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *nullTimer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualClockMutex"
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) StateFields() []string {
|
||||
return []string{
|
||||
"now",
|
||||
"times",
|
||||
"timers",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualClockMutex) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.now)
|
||||
stateSinkObject.Save(1, &m.times)
|
||||
stateSinkObject.Save(2, &m.timers)
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualClockMutex) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.now)
|
||||
stateSourceObject.Load(1, &m.times)
|
||||
stateSourceObject.Load(2, &m.timers)
|
||||
}
|
||||
|
||||
func (mc *ManualClock) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.ManualClock"
|
||||
}
|
||||
|
||||
func (mc *ManualClock) StateFields() []string {
|
||||
return []string{
|
||||
"runningTimers",
|
||||
"mu",
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *ManualClock) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mc *ManualClock) StateSave(stateSinkObject state.Sink) {
|
||||
mc.beforeSave()
|
||||
stateSinkObject.Save(0, &mc.runningTimers)
|
||||
stateSinkObject.Save(1, &mc.mu)
|
||||
}
|
||||
|
||||
func (mc *ManualClock) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mc *ManualClock) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &mc.runningTimers)
|
||||
stateSourceObject.Load(1, &mc.mu)
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualTimerMu"
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) StateFields() []string {
|
||||
return []string{
|
||||
"firesAt",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualTimerMu) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.firesAt)
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualTimerMu) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.firesAt)
|
||||
}
|
||||
|
||||
func (mt *manualTimer) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualTimer"
|
||||
}
|
||||
|
||||
func (mt *manualTimer) StateFields() []string {
|
||||
return []string{
|
||||
"clock",
|
||||
"mu",
|
||||
}
|
||||
}
|
||||
|
||||
func (mt *manualTimer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mt *manualTimer) StateSave(stateSinkObject state.Sink) {
|
||||
mt.beforeSave()
|
||||
stateSinkObject.Save(0, &mt.clock)
|
||||
stateSinkObject.Save(1, &mt.mu)
|
||||
}
|
||||
|
||||
func (mt *manualTimer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mt *manualTimer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &mt.clock)
|
||||
stateSourceObject.Load(1, &mt.mu)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*NullClock)(nil))
|
||||
state.Register((*nullTimer)(nil))
|
||||
state.Register((*manualClockMutex)(nil))
|
||||
state.Register((*ManualClock)(nil))
|
||||
state.Register((*manualTimerMu)(nil))
|
||||
state.Register((*manualTimer)(nil))
|
||||
}
|
||||
79
pkg/tcpip/hash/jenkins/jenkins.go
Normal file
79
pkg/tcpip/hash/jenkins/jenkins.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// 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 jenkins implements Jenkins's one_at_a_time, non-cryptographic hash
|
||||
// functions created by by Bob Jenkins.
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Jenkins_hash_function#cite_note-dobbsx-1
|
||||
package jenkins
|
||||
|
||||
import (
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Sum32 represents Jenkins's one_at_a_time hash.
|
||||
//
|
||||
// Use the Sum32 type directly (as opposed to New32 below)
|
||||
// to avoid allocations.
|
||||
type Sum32 uint32
|
||||
|
||||
// New32 returns a new 32-bit Jenkins's one_at_a_time hash.Hash.
|
||||
//
|
||||
// Its Sum method will lay the value out in big-endian byte order.
|
||||
func New32() hash.Hash32 {
|
||||
var s Sum32
|
||||
return &s
|
||||
}
|
||||
|
||||
// Reset resets the hash to its initial state.
|
||||
func (s *Sum32) Reset() { *s = 0 }
|
||||
|
||||
// Sum32 returns the hash value
|
||||
func (s *Sum32) Sum32() uint32 {
|
||||
sCopy := *s
|
||||
|
||||
sCopy += sCopy << 3
|
||||
sCopy ^= sCopy >> 11
|
||||
sCopy += sCopy << 15
|
||||
|
||||
return uint32(sCopy)
|
||||
}
|
||||
|
||||
// Write adds more data to the running hash.
|
||||
//
|
||||
// It never returns an error.
|
||||
func (s *Sum32) Write(data []byte) (int, error) {
|
||||
sCopy := *s
|
||||
for _, b := range data {
|
||||
sCopy += Sum32(b)
|
||||
sCopy += sCopy << 10
|
||||
sCopy ^= sCopy >> 6
|
||||
}
|
||||
*s = sCopy
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// Size returns the number of bytes Sum will return.
|
||||
func (s *Sum32) Size() int { return 4 }
|
||||
|
||||
// BlockSize returns the hash's underlying block size.
|
||||
func (s *Sum32) BlockSize() int { return 1 }
|
||||
|
||||
// Sum appends the current hash to in and returns the resulting slice.
|
||||
//
|
||||
// It does not change the underlying hash state.
|
||||
func (s *Sum32) Sum(in []byte) []byte {
|
||||
v := s.Sum32()
|
||||
return append(in, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
3
pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
Normal file
3
pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package jenkins
|
||||
127
pkg/tcpip/header/arp.go
Normal file
127
pkg/tcpip/header/arp.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
// ARPProtocolNumber is the ARP network protocol number.
|
||||
ARPProtocolNumber tcpip.NetworkProtocolNumber = 0x0806
|
||||
|
||||
// ARPSize is the size of an IPv4-over-Ethernet ARP packet.
|
||||
ARPSize = 28
|
||||
)
|
||||
|
||||
// ARPHardwareType is the hardware type for LinkEndpoint in an ARP header.
|
||||
type ARPHardwareType uint16
|
||||
|
||||
// Typical ARP HardwareType values. Some of the constants have to be specific
|
||||
// values as they are egressed on the wire in the HTYPE field of an ARP header.
|
||||
const (
|
||||
ARPHardwareNone ARPHardwareType = 0
|
||||
// ARPHardwareEther specifically is the HTYPE for Ethernet as specified
|
||||
// in the IANA list here:
|
||||
//
|
||||
// https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2
|
||||
ARPHardwareEther ARPHardwareType = 1
|
||||
ARPHardwareLoopback ARPHardwareType = 2
|
||||
)
|
||||
|
||||
// ARPOp is an ARP opcode.
|
||||
type ARPOp uint16
|
||||
|
||||
// Typical ARP opcodes defined in RFC 826.
|
||||
const (
|
||||
ARPRequest ARPOp = 1
|
||||
ARPReply ARPOp = 2
|
||||
)
|
||||
|
||||
// ARP is an ARP packet stored in a byte array as described in RFC 826.
|
||||
type ARP []byte
|
||||
|
||||
const (
|
||||
hTypeOffset = 0
|
||||
protocolOffset = 2
|
||||
haAddressSizeOffset = 4
|
||||
protoAddressSizeOffset = 5
|
||||
opCodeOffset = 6
|
||||
senderHAAddressOffset = 8
|
||||
senderProtocolAddressOffset = senderHAAddressOffset + EthernetAddressSize
|
||||
targetHAAddressOffset = senderProtocolAddressOffset + IPv4AddressSize
|
||||
targetProtocolAddressOffset = targetHAAddressOffset + EthernetAddressSize
|
||||
)
|
||||
|
||||
func (a ARP) hardwareAddressType() ARPHardwareType {
|
||||
return ARPHardwareType(binary.BigEndian.Uint16(a[hTypeOffset:]))
|
||||
}
|
||||
|
||||
func (a ARP) protocolAddressSpace() uint16 { return binary.BigEndian.Uint16(a[protocolOffset:]) }
|
||||
func (a ARP) hardwareAddressSize() int { return int(a[haAddressSizeOffset]) }
|
||||
func (a ARP) protocolAddressSize() int { return int(a[protoAddressSizeOffset]) }
|
||||
|
||||
// Op is the ARP opcode.
|
||||
func (a ARP) Op() ARPOp { return ARPOp(binary.BigEndian.Uint16(a[opCodeOffset:])) }
|
||||
|
||||
// SetOp sets the ARP opcode.
|
||||
func (a ARP) SetOp(op ARPOp) {
|
||||
binary.BigEndian.PutUint16(a[opCodeOffset:], uint16(op))
|
||||
}
|
||||
|
||||
// SetIPv4OverEthernet configures the ARP packet for IPv4-over-Ethernet.
|
||||
func (a ARP) SetIPv4OverEthernet() {
|
||||
binary.BigEndian.PutUint16(a[hTypeOffset:], uint16(ARPHardwareEther))
|
||||
binary.BigEndian.PutUint16(a[protocolOffset:], uint16(IPv4ProtocolNumber))
|
||||
a[haAddressSizeOffset] = EthernetAddressSize
|
||||
a[protoAddressSizeOffset] = uint8(IPv4AddressSize)
|
||||
}
|
||||
|
||||
// HardwareAddressSender is the link address of the sender.
|
||||
// It is a view on to the ARP packet so it can be used to set the value.
|
||||
func (a ARP) HardwareAddressSender() []byte {
|
||||
return a[senderHAAddressOffset : senderHAAddressOffset+EthernetAddressSize]
|
||||
}
|
||||
|
||||
// ProtocolAddressSender is the protocol address of the sender.
|
||||
// It is a view on to the ARP packet so it can be used to set the value.
|
||||
func (a ARP) ProtocolAddressSender() []byte {
|
||||
return a[senderProtocolAddressOffset : senderProtocolAddressOffset+IPv4AddressSize]
|
||||
}
|
||||
|
||||
// HardwareAddressTarget is the link address of the target.
|
||||
// It is a view on to the ARP packet so it can be used to set the value.
|
||||
func (a ARP) HardwareAddressTarget() []byte {
|
||||
return a[targetHAAddressOffset : targetHAAddressOffset+EthernetAddressSize]
|
||||
}
|
||||
|
||||
// ProtocolAddressTarget is the protocol address of the target.
|
||||
// It is a view on to the ARP packet so it can be used to set the value.
|
||||
func (a ARP) ProtocolAddressTarget() []byte {
|
||||
return a[targetProtocolAddressOffset : targetProtocolAddressOffset+IPv4AddressSize]
|
||||
}
|
||||
|
||||
// IsValid reports whether this is an ARP packet for IPv4 over Ethernet.
|
||||
func (a ARP) IsValid() bool {
|
||||
if len(a) < ARPSize {
|
||||
return false
|
||||
}
|
||||
return a.hardwareAddressType() == ARPHardwareEther &&
|
||||
a.protocolAddressSpace() == uint16(IPv4ProtocolNumber) &&
|
||||
a.hardwareAddressSize() == EthernetAddressSize &&
|
||||
a.protocolAddressSize() == IPv4AddressSize
|
||||
}
|
||||
107
pkg/tcpip/header/checksum.go
Normal file
107
pkg/tcpip/header/checksum.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// 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 header provides the implementation of the encoding and decoding of
|
||||
// network protocol headers.
|
||||
package header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
)
|
||||
|
||||
// PseudoHeaderChecksum calculates the pseudo-header checksum for the given
|
||||
// destination protocol and network address. Pseudo-headers are needed by
|
||||
// transport layers when calculating their own checksum.
|
||||
func PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, srcAddr tcpip.Address, dstAddr tcpip.Address, totalLen uint16) uint16 {
|
||||
xsum := checksum.Checksum(srcAddr.AsSlice(), 0)
|
||||
xsum = checksum.Checksum(dstAddr.AsSlice(), xsum)
|
||||
|
||||
// Add the length portion of the checksum to the pseudo-checksum.
|
||||
var tmp [2]byte
|
||||
binary.BigEndian.PutUint16(tmp[:], totalLen)
|
||||
xsum = checksum.Checksum(tmp[:], xsum)
|
||||
|
||||
return checksum.Checksum([]byte{0, uint8(protocol)}, xsum)
|
||||
}
|
||||
|
||||
// checksumUpdate2ByteAlignedUint16 updates a uint16 value in a calculated
|
||||
// checksum.
|
||||
//
|
||||
// The value MUST begin at a 2-byte boundary in the original buffer.
|
||||
func checksumUpdate2ByteAlignedUint16(xsum, old, new uint16) uint16 {
|
||||
// As per RFC 1071 page 4,
|
||||
// (4) Incremental Update
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// To update the checksum, simply add the differences of the
|
||||
// sixteen bit integers that have been changed. To see why this
|
||||
// works, observe that every 16-bit integer has an additive inverse
|
||||
// and that addition is associative. From this it follows that
|
||||
// given the original value m, the new value m', and the old
|
||||
// checksum C, the new checksum C' is:
|
||||
//
|
||||
// C' = C + (-m) + m' = C + (m' - m)
|
||||
if old == new {
|
||||
return xsum
|
||||
}
|
||||
return checksum.Combine(xsum, checksum.Combine(new, ^old))
|
||||
}
|
||||
|
||||
// checksumUpdate2ByteAlignedAddress updates an address in a calculated
|
||||
// checksum.
|
||||
//
|
||||
// The addresses must have the same length and must contain an even number
|
||||
// of bytes. The address MUST begin at a 2-byte boundary in the original buffer.
|
||||
func checksumUpdate2ByteAlignedAddress(xsum uint16, old, new tcpip.Address) uint16 {
|
||||
const uint16Bytes = 2
|
||||
|
||||
if old.BitLen() != new.BitLen() {
|
||||
panic(fmt.Sprintf("buffer lengths are different; old = %d, new = %d", old.BitLen()/8, new.BitLen()/8))
|
||||
}
|
||||
|
||||
if oldBytes := old.BitLen() % 16; oldBytes != 0 {
|
||||
panic(fmt.Sprintf("buffer has an odd number of bytes; got = %d", oldBytes))
|
||||
}
|
||||
|
||||
oldAddr := old.AsSlice()
|
||||
newAddr := new.AsSlice()
|
||||
|
||||
// As per RFC 1071 page 4,
|
||||
// (4) Incremental Update
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// To update the checksum, simply add the differences of the
|
||||
// sixteen bit integers that have been changed. To see why this
|
||||
// works, observe that every 16-bit integer has an additive inverse
|
||||
// and that addition is associative. From this it follows that
|
||||
// given the original value m, the new value m', and the old
|
||||
// checksum C, the new checksum C' is:
|
||||
//
|
||||
// C' = C + (-m) + m' = C + (m' - m)
|
||||
for len(oldAddr) != 0 {
|
||||
// Convert the 2 byte sequences to uint16 values then apply the increment
|
||||
// update.
|
||||
xsum = checksumUpdate2ByteAlignedUint16(xsum, (uint16(oldAddr[0])<<8)+uint16(oldAddr[1]), (uint16(newAddr[0])<<8)+uint16(newAddr[1]))
|
||||
oldAddr = oldAddr[uint16Bytes:]
|
||||
newAddr = newAddr[uint16Bytes:]
|
||||
}
|
||||
|
||||
return xsum
|
||||
}
|
||||
18
pkg/tcpip/header/datagram.go
Normal file
18
pkg/tcpip/header/datagram.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2022 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 header
|
||||
|
||||
// DatagramMaximumSize is the maximum supported size of a single datagram.
|
||||
const DatagramMaximumSize = 0xffff // 65KB.
|
||||
192
pkg/tcpip/header/eth.go
Normal file
192
pkg/tcpip/header/eth.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
dstMAC = 0
|
||||
srcMAC = 6
|
||||
ethType = 12
|
||||
)
|
||||
|
||||
// EthernetFields contains the fields of an ethernet frame header. It is used to
|
||||
// describe the fields of a frame that needs to be encoded.
|
||||
type EthernetFields struct {
|
||||
// SrcAddr is the "MAC source" field of an ethernet frame header.
|
||||
SrcAddr tcpip.LinkAddress
|
||||
|
||||
// DstAddr is the "MAC destination" field of an ethernet frame header.
|
||||
DstAddr tcpip.LinkAddress
|
||||
|
||||
// Type is the "ethertype" field of an ethernet frame header.
|
||||
Type tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// Ethernet represents an ethernet frame header stored in a byte array.
|
||||
type Ethernet []byte
|
||||
|
||||
const (
|
||||
// EthernetMinimumSize is the minimum size of a valid ethernet frame.
|
||||
EthernetMinimumSize = 14
|
||||
|
||||
// EthernetMaximumSize is the maximum size of a valid ethernet frame.
|
||||
EthernetMaximumSize = 18
|
||||
|
||||
// EthernetAddressSize is the size, in bytes, of an ethernet address.
|
||||
EthernetAddressSize = 6
|
||||
|
||||
// UnspecifiedEthernetAddress is the unspecified ethernet address
|
||||
// (all bits set to 0).
|
||||
UnspecifiedEthernetAddress = tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00")
|
||||
|
||||
// EthernetBroadcastAddress is an ethernet address that addresses every node
|
||||
// on a local link.
|
||||
EthernetBroadcastAddress = tcpip.LinkAddress("\xff\xff\xff\xff\xff\xff")
|
||||
|
||||
// unicastMulticastFlagMask is the mask of the least significant bit in
|
||||
// the first octet (in network byte order) of an ethernet address that
|
||||
// determines whether the ethernet address is a unicast or multicast. If
|
||||
// the masked bit is a 1, then the address is a multicast, unicast
|
||||
// otherwise.
|
||||
//
|
||||
// See the IEEE Std 802-2001 document for more details. Specifically,
|
||||
// section 9.2.1 of http://ieee802.org/secmail/pdfocSP2xXA6d.pdf:
|
||||
// "A 48-bit universal address consists of two parts. The first 24 bits
|
||||
// correspond to the OUI as assigned by the IEEE, expect that the
|
||||
// assignee may set the LSB of the first octet to 1 for group addresses
|
||||
// or set it to 0 for individual addresses."
|
||||
unicastMulticastFlagMask = 1
|
||||
|
||||
// unicastMulticastFlagByteIdx is the byte that holds the
|
||||
// unicast/multicast flag. See unicastMulticastFlagMask.
|
||||
unicastMulticastFlagByteIdx = 0
|
||||
)
|
||||
|
||||
const (
|
||||
// EthernetProtocolAll is a catch-all for all protocols carried inside
|
||||
// an ethernet frame. It is mainly used to create packet sockets that
|
||||
// capture all traffic.
|
||||
EthernetProtocolAll tcpip.NetworkProtocolNumber = 0x0003
|
||||
|
||||
// EthernetProtocolPUP is the PARC Universal Packet protocol ethertype.
|
||||
EthernetProtocolPUP tcpip.NetworkProtocolNumber = 0x0200
|
||||
)
|
||||
|
||||
// Ethertypes holds the protocol numbers describing the payload of an ethernet
|
||||
// frame. These types aren't necessarily supported by netstack, but can be used
|
||||
// to catch all traffic of a type via packet endpoints.
|
||||
var Ethertypes = []tcpip.NetworkProtocolNumber{
|
||||
EthernetProtocolAll,
|
||||
EthernetProtocolPUP,
|
||||
}
|
||||
|
||||
// SourceAddress returns the "MAC source" field of the ethernet frame header.
|
||||
func (b Ethernet) SourceAddress() tcpip.LinkAddress {
|
||||
return tcpip.LinkAddress(b[srcMAC:][:EthernetAddressSize])
|
||||
}
|
||||
|
||||
// DestinationAddress returns the "MAC destination" field of the ethernet frame
|
||||
// header.
|
||||
func (b Ethernet) DestinationAddress() tcpip.LinkAddress {
|
||||
return tcpip.LinkAddress(b[dstMAC:][:EthernetAddressSize])
|
||||
}
|
||||
|
||||
// Type returns the "ethertype" field of the ethernet frame header.
|
||||
func (b Ethernet) Type() tcpip.NetworkProtocolNumber {
|
||||
return tcpip.NetworkProtocolNumber(binary.BigEndian.Uint16(b[ethType:]))
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the ethernet frame header.
|
||||
func (b Ethernet) Encode(e *EthernetFields) {
|
||||
binary.BigEndian.PutUint16(b[ethType:], uint16(e.Type))
|
||||
copy(b[srcMAC:][:EthernetAddressSize], e.SrcAddr)
|
||||
copy(b[dstMAC:][:EthernetAddressSize], e.DstAddr)
|
||||
}
|
||||
|
||||
// IsMulticastEthernetAddress returns true if the address is a multicast
|
||||
// ethernet address.
|
||||
func IsMulticastEthernetAddress(addr tcpip.LinkAddress) bool {
|
||||
if len(addr) != EthernetAddressSize {
|
||||
return false
|
||||
}
|
||||
|
||||
return addr[unicastMulticastFlagByteIdx]&unicastMulticastFlagMask != 0
|
||||
}
|
||||
|
||||
// IsValidUnicastEthernetAddress returns true if the address is a unicast
|
||||
// ethernet address.
|
||||
func IsValidUnicastEthernetAddress(addr tcpip.LinkAddress) bool {
|
||||
if len(addr) != EthernetAddressSize {
|
||||
return false
|
||||
}
|
||||
|
||||
if addr == UnspecifiedEthernetAddress {
|
||||
return false
|
||||
}
|
||||
|
||||
if addr[unicastMulticastFlagByteIdx]&unicastMulticastFlagMask != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// EthernetAddressFromMulticastIPv4Address returns a multicast Ethernet address
|
||||
// for a multicast IPv4 address.
|
||||
//
|
||||
// addr MUST be a multicast IPv4 address.
|
||||
func EthernetAddressFromMulticastIPv4Address(addr tcpip.Address) tcpip.LinkAddress {
|
||||
var linkAddrBytes [EthernetAddressSize]byte
|
||||
// RFC 1112 Host Extensions for IP Multicasting
|
||||
//
|
||||
// 6.4. Extensions to an Ethernet Local Network Module:
|
||||
//
|
||||
// An IP host group address is mapped to an Ethernet multicast
|
||||
// address by placing the low-order 23-bits of the IP address
|
||||
// into the low-order 23 bits of the Ethernet multicast address
|
||||
// 01-00-5E-00-00-00 (hex).
|
||||
addrBytes := addr.As4()
|
||||
linkAddrBytes[0] = 0x1
|
||||
linkAddrBytes[2] = 0x5e
|
||||
linkAddrBytes[3] = addrBytes[1] & 0x7F
|
||||
copy(linkAddrBytes[4:], addrBytes[IPv4AddressSize-2:])
|
||||
return tcpip.LinkAddress(linkAddrBytes[:])
|
||||
}
|
||||
|
||||
// EthernetAddressFromMulticastIPv6Address returns a multicast Ethernet address
|
||||
// for a multicast IPv6 address.
|
||||
//
|
||||
// addr MUST be a multicast IPv6 address.
|
||||
func EthernetAddressFromMulticastIPv6Address(addr tcpip.Address) tcpip.LinkAddress {
|
||||
// RFC 2464 Transmission of IPv6 Packets over Ethernet Networks
|
||||
//
|
||||
// 7. Address Mapping -- Multicast
|
||||
//
|
||||
// An IPv6 packet with a multicast destination address DST,
|
||||
// consisting of the sixteen octets DST[1] through DST[16], is
|
||||
// transmitted to the Ethernet multicast address whose first
|
||||
// two octets are the value 3333 hexadecimal and whose last
|
||||
// four octets are the last four octets of DST.
|
||||
addrBytes := addr.As16()
|
||||
linkAddrBytes := []byte(addrBytes[IPv6AddressSize-EthernetAddressSize:])
|
||||
linkAddrBytes[0] = 0x33
|
||||
linkAddrBytes[1] = 0x33
|
||||
return tcpip.LinkAddress(linkAddrBytes[:])
|
||||
}
|
||||
73
pkg/tcpip/header/gue.go
Normal file
73
pkg/tcpip/header/gue.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// 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 header
|
||||
|
||||
const (
|
||||
typeHLen = 0
|
||||
encapProto = 1
|
||||
)
|
||||
|
||||
// GUEFields contains the fields of a GUE packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type GUEFields struct {
|
||||
// Type is the "type" field of the GUE header.
|
||||
Type uint8
|
||||
|
||||
// Control is the "control" field of the GUE header.
|
||||
Control bool
|
||||
|
||||
// HeaderLength is the "header length" field of the GUE header. It must
|
||||
// be at least 4 octets, and a multiple of 4 as well.
|
||||
HeaderLength uint8
|
||||
|
||||
// Protocol is the "protocol" field of the GUE header. This is one of
|
||||
// the IPPROTO_* values.
|
||||
Protocol uint8
|
||||
}
|
||||
|
||||
// GUE represents a Generic UDP Encapsulation header stored in a byte array, the
|
||||
// fields are described in https://tools.ietf.org/html/draft-ietf-nvo3-gue-01.
|
||||
type GUE []byte
|
||||
|
||||
const (
|
||||
// GUEMinimumSize is the minimum size of a valid GUE packet.
|
||||
GUEMinimumSize = 4
|
||||
)
|
||||
|
||||
// TypeAndControl returns the GUE packet type (top 3 bits of the first byte,
|
||||
// which includes the control bit).
|
||||
func (b GUE) TypeAndControl() uint8 {
|
||||
return b[typeHLen] >> 5
|
||||
}
|
||||
|
||||
// HeaderLength returns the total length of the GUE header.
|
||||
func (b GUE) HeaderLength() uint8 {
|
||||
return 4 + 4*(b[typeHLen]&0x1f)
|
||||
}
|
||||
|
||||
// Protocol returns the protocol field of the GUE header.
|
||||
func (b GUE) Protocol() uint8 {
|
||||
return b[encapProto]
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the GUE header.
|
||||
func (b GUE) Encode(i *GUEFields) {
|
||||
ctl := uint8(0)
|
||||
if i.Control {
|
||||
ctl = 1 << 5
|
||||
}
|
||||
b[typeHLen] = ctl | i.Type<<6 | (i.HeaderLength-4)/4
|
||||
b[encapProto] = i.Protocol
|
||||
}
|
||||
120
pkg/tcpip/header/header_state_autogen.go
Normal file
120
pkg/tcpip/header/header_state_autogen.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package header
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (t *TCPSynOptions) StateTypeName() string {
|
||||
return "pkg/tcpip/header.TCPSynOptions"
|
||||
}
|
||||
|
||||
func (t *TCPSynOptions) StateFields() []string {
|
||||
return []string{
|
||||
"MSS",
|
||||
"WS",
|
||||
"TS",
|
||||
"TSVal",
|
||||
"TSEcr",
|
||||
"SACKPermitted",
|
||||
"Flags",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPSynOptions) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *TCPSynOptions) StateSave(stateSinkObject state.Sink) {
|
||||
t.beforeSave()
|
||||
stateSinkObject.Save(0, &t.MSS)
|
||||
stateSinkObject.Save(1, &t.WS)
|
||||
stateSinkObject.Save(2, &t.TS)
|
||||
stateSinkObject.Save(3, &t.TSVal)
|
||||
stateSinkObject.Save(4, &t.TSEcr)
|
||||
stateSinkObject.Save(5, &t.SACKPermitted)
|
||||
stateSinkObject.Save(6, &t.Flags)
|
||||
}
|
||||
|
||||
func (t *TCPSynOptions) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *TCPSynOptions) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &t.MSS)
|
||||
stateSourceObject.Load(1, &t.WS)
|
||||
stateSourceObject.Load(2, &t.TS)
|
||||
stateSourceObject.Load(3, &t.TSVal)
|
||||
stateSourceObject.Load(4, &t.TSEcr)
|
||||
stateSourceObject.Load(5, &t.SACKPermitted)
|
||||
stateSourceObject.Load(6, &t.Flags)
|
||||
}
|
||||
|
||||
func (r *SACKBlock) StateTypeName() string {
|
||||
return "pkg/tcpip/header.SACKBlock"
|
||||
}
|
||||
|
||||
func (r *SACKBlock) StateFields() []string {
|
||||
return []string{
|
||||
"Start",
|
||||
"End",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SACKBlock) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *SACKBlock) StateSave(stateSinkObject state.Sink) {
|
||||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.Start)
|
||||
stateSinkObject.Save(1, &r.End)
|
||||
}
|
||||
|
||||
func (r *SACKBlock) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *SACKBlock) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.Start)
|
||||
stateSourceObject.Load(1, &r.End)
|
||||
}
|
||||
|
||||
func (t *TCPOptions) StateTypeName() string {
|
||||
return "pkg/tcpip/header.TCPOptions"
|
||||
}
|
||||
|
||||
func (t *TCPOptions) StateFields() []string {
|
||||
return []string{
|
||||
"TS",
|
||||
"TSVal",
|
||||
"TSEcr",
|
||||
"SACKBlocks",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPOptions) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *TCPOptions) StateSave(stateSinkObject state.Sink) {
|
||||
t.beforeSave()
|
||||
stateSinkObject.Save(0, &t.TS)
|
||||
stateSinkObject.Save(1, &t.TSVal)
|
||||
stateSinkObject.Save(2, &t.TSEcr)
|
||||
stateSinkObject.Save(3, &t.SACKBlocks)
|
||||
}
|
||||
|
||||
func (t *TCPOptions) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *TCPOptions) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &t.TS)
|
||||
stateSourceObject.Load(1, &t.TSVal)
|
||||
stateSourceObject.Load(2, &t.TSEcr)
|
||||
stateSourceObject.Load(3, &t.SACKBlocks)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*TCPSynOptions)(nil))
|
||||
state.Register((*SACKBlock)(nil))
|
||||
state.Register((*TCPOptions)(nil))
|
||||
}
|
||||
228
pkg/tcpip/header/icmpv4.go
Normal file
228
pkg/tcpip/header/icmpv4.go
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
)
|
||||
|
||||
// ICMPv4 represents an ICMPv4 header stored in a byte array.
|
||||
type ICMPv4 []byte
|
||||
|
||||
const (
|
||||
// ICMPv4PayloadOffset defines the start of ICMP payload.
|
||||
ICMPv4PayloadOffset = 8
|
||||
|
||||
// ICMPv4MinimumSize is the minimum size of a valid ICMP packet.
|
||||
ICMPv4MinimumSize = 8
|
||||
|
||||
// ICMPv4MinimumErrorPayloadSize Is the smallest number of bytes of an
|
||||
// errant packet's transport layer that an ICMP error type packet should
|
||||
// attempt to send as per RFC 792 (see each type) and RFC 1122
|
||||
// section 3.2.2 which states:
|
||||
// Every ICMP error message includes the Internet header and at
|
||||
// least the first 8 data octets of the datagram that triggered
|
||||
// the error; more than 8 octets MAY be sent; this header and data
|
||||
// MUST be unchanged from the received datagram.
|
||||
//
|
||||
// RFC 792 shows:
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | unused |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Internet Header + 64 bits of Original Data Datagram |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
ICMPv4MinimumErrorPayloadSize = 8
|
||||
|
||||
// ICMPv4ProtocolNumber is the ICMP transport protocol number.
|
||||
ICMPv4ProtocolNumber tcpip.TransportProtocolNumber = 1
|
||||
|
||||
// icmpv4ChecksumOffset is the offset of the checksum field
|
||||
// in an ICMPv4 message.
|
||||
icmpv4ChecksumOffset = 2
|
||||
|
||||
// icmpv4MTUOffset is the offset of the MTU field
|
||||
// in an ICMPv4FragmentationNeeded message.
|
||||
icmpv4MTUOffset = 6
|
||||
|
||||
// icmpv4IdentOffset is the offset of the ident field
|
||||
// in an ICMPv4EchoRequest/Reply message.
|
||||
icmpv4IdentOffset = 4
|
||||
|
||||
// icmpv4PointerOffset is the offset of the pointer field
|
||||
// in an ICMPv4ParamProblem message.
|
||||
icmpv4PointerOffset = 4
|
||||
|
||||
// icmpv4SequenceOffset is the offset of the sequence field
|
||||
// in an ICMPv4EchoRequest/Reply message.
|
||||
icmpv4SequenceOffset = 6
|
||||
)
|
||||
|
||||
// ICMPv4Type is the ICMP type field described in RFC 792.
|
||||
type ICMPv4Type byte
|
||||
|
||||
// ICMPv4Code is the ICMP code field described in RFC 792.
|
||||
type ICMPv4Code byte
|
||||
|
||||
// Typical values of ICMPv4Type defined in RFC 792.
|
||||
const (
|
||||
ICMPv4EchoReply ICMPv4Type = 0
|
||||
ICMPv4DstUnreachable ICMPv4Type = 3
|
||||
ICMPv4SrcQuench ICMPv4Type = 4
|
||||
ICMPv4Redirect ICMPv4Type = 5
|
||||
ICMPv4Echo ICMPv4Type = 8
|
||||
ICMPv4TimeExceeded ICMPv4Type = 11
|
||||
ICMPv4ParamProblem ICMPv4Type = 12
|
||||
ICMPv4Timestamp ICMPv4Type = 13
|
||||
ICMPv4TimestampReply ICMPv4Type = 14
|
||||
ICMPv4InfoRequest ICMPv4Type = 15
|
||||
ICMPv4InfoReply ICMPv4Type = 16
|
||||
)
|
||||
|
||||
// ICMP codes for ICMPv4 Time Exceeded messages as defined in RFC 792.
|
||||
const (
|
||||
ICMPv4TTLExceeded ICMPv4Code = 0
|
||||
ICMPv4ReassemblyTimeout ICMPv4Code = 1
|
||||
)
|
||||
|
||||
// ICMP codes for ICMPv4 Destination Unreachable messages as defined in RFC 792,
|
||||
// RFC 1122 section 3.2.2.1 and RFC 1812 section 5.2.7.1.
|
||||
const (
|
||||
ICMPv4NetUnreachable ICMPv4Code = 0
|
||||
ICMPv4HostUnreachable ICMPv4Code = 1
|
||||
ICMPv4ProtoUnreachable ICMPv4Code = 2
|
||||
ICMPv4PortUnreachable ICMPv4Code = 3
|
||||
ICMPv4FragmentationNeeded ICMPv4Code = 4
|
||||
ICMPv4SourceRouteFailed ICMPv4Code = 5
|
||||
ICMPv4DestinationNetworkUnknown ICMPv4Code = 6
|
||||
ICMPv4DestinationHostUnknown ICMPv4Code = 7
|
||||
ICMPv4SourceHostIsolated ICMPv4Code = 8
|
||||
ICMPv4NetProhibited ICMPv4Code = 9
|
||||
ICMPv4HostProhibited ICMPv4Code = 10
|
||||
ICMPv4NetUnreachableForTos ICMPv4Code = 11
|
||||
ICMPv4HostUnreachableForTos ICMPv4Code = 12
|
||||
ICMPv4AdminProhibited ICMPv4Code = 13
|
||||
ICMPv4HostPrecedenceViolation ICMPv4Code = 14
|
||||
ICMPv4PrecedenceCutInEffect ICMPv4Code = 15
|
||||
)
|
||||
|
||||
// ICMPv4UnusedCode is a code to use in ICMP messages where no code is needed.
|
||||
const ICMPv4UnusedCode ICMPv4Code = 0
|
||||
|
||||
// Type is the ICMP type field.
|
||||
func (b ICMPv4) Type() ICMPv4Type { return ICMPv4Type(b[0]) }
|
||||
|
||||
// SetType sets the ICMP type field.
|
||||
func (b ICMPv4) SetType(t ICMPv4Type) { b[0] = byte(t) }
|
||||
|
||||
// Code is the ICMP code field. Its meaning depends on the value of Type.
|
||||
func (b ICMPv4) Code() ICMPv4Code { return ICMPv4Code(b[1]) }
|
||||
|
||||
// SetCode sets the ICMP code field.
|
||||
func (b ICMPv4) SetCode(c ICMPv4Code) { b[1] = byte(c) }
|
||||
|
||||
// Pointer returns the pointer field in a Parameter Problem packet.
|
||||
func (b ICMPv4) Pointer() byte { return b[icmpv4PointerOffset] }
|
||||
|
||||
// SetPointer sets the pointer field in a Parameter Problem packet.
|
||||
func (b ICMPv4) SetPointer(c byte) { b[icmpv4PointerOffset] = c }
|
||||
|
||||
// Checksum is the ICMP checksum field.
|
||||
func (b ICMPv4) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv4ChecksumOffset:])
|
||||
}
|
||||
|
||||
// SetChecksum sets the ICMP checksum field.
|
||||
func (b ICMPv4) SetChecksum(cs uint16) {
|
||||
checksum.Put(b[icmpv4ChecksumOffset:], cs)
|
||||
}
|
||||
|
||||
// SourcePort implements Transport.SourcePort.
|
||||
func (ICMPv4) SourcePort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DestinationPort implements Transport.DestinationPort.
|
||||
func (ICMPv4) DestinationPort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetSourcePort implements Transport.SetSourcePort.
|
||||
func (ICMPv4) SetSourcePort(uint16) {
|
||||
}
|
||||
|
||||
// SetDestinationPort implements Transport.SetDestinationPort.
|
||||
func (ICMPv4) SetDestinationPort(uint16) {
|
||||
}
|
||||
|
||||
// Payload implements Transport.Payload.
|
||||
func (b ICMPv4) Payload() []byte {
|
||||
return b[ICMPv4PayloadOffset:]
|
||||
}
|
||||
|
||||
// MTU retrieves the MTU field from an ICMPv4 message.
|
||||
func (b ICMPv4) MTU() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv4MTUOffset:])
|
||||
}
|
||||
|
||||
// SetMTU sets the MTU field from an ICMPv4 message.
|
||||
func (b ICMPv4) SetMTU(mtu uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv4MTUOffset:], mtu)
|
||||
}
|
||||
|
||||
// Ident retrieves the Ident field from an ICMPv4 message.
|
||||
func (b ICMPv4) Ident() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv4IdentOffset:])
|
||||
}
|
||||
|
||||
// SetIdent sets the Ident field from an ICMPv4 message.
|
||||
func (b ICMPv4) SetIdent(ident uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv4IdentOffset:], ident)
|
||||
}
|
||||
|
||||
// SetIdentWithChecksumUpdate sets the Ident field and updates the checksum.
|
||||
func (b ICMPv4) SetIdentWithChecksumUpdate(new uint16) {
|
||||
old := b.Ident()
|
||||
b.SetIdent(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// Sequence retrieves the Sequence field from an ICMPv4 message.
|
||||
func (b ICMPv4) Sequence() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv4SequenceOffset:])
|
||||
}
|
||||
|
||||
// SetSequence sets the Sequence field from an ICMPv4 message.
|
||||
func (b ICMPv4) SetSequence(sequence uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv4SequenceOffset:], sequence)
|
||||
}
|
||||
|
||||
// ICMPv4Checksum calculates the ICMP checksum over the provided ICMP header,
|
||||
// and payload.
|
||||
func ICMPv4Checksum(h ICMPv4, payloadCsum uint16) uint16 {
|
||||
xsum := payloadCsum
|
||||
|
||||
// h[2:4] is the checksum itself, skip it to avoid checksumming the checksum.
|
||||
xsum = checksum.Checksum(h[:2], xsum)
|
||||
xsum = checksum.Checksum(h[4:], xsum)
|
||||
|
||||
return ^xsum
|
||||
}
|
||||
304
pkg/tcpip/header/icmpv6.go
Normal file
304
pkg/tcpip/header/icmpv6.go
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
)
|
||||
|
||||
// ICMPv6 represents an ICMPv6 header stored in a byte array.
|
||||
type ICMPv6 []byte
|
||||
|
||||
const (
|
||||
// ICMPv6HeaderSize is the size of the ICMPv6 header. That is, the
|
||||
// sum of the size of the ICMPv6 Type, Code and Checksum fields, as
|
||||
// per RFC 4443 section 2.1. After the ICMPv6 header, the ICMPv6
|
||||
// message body begins.
|
||||
ICMPv6HeaderSize = 4
|
||||
|
||||
// ICMPv6MinimumSize is the minimum size of a valid ICMP packet.
|
||||
ICMPv6MinimumSize = 8
|
||||
|
||||
// ICMPv6PayloadOffset is the offset of the payload in an
|
||||
// ICMP packet.
|
||||
ICMPv6PayloadOffset = 8
|
||||
|
||||
// ICMPv6ProtocolNumber is the ICMP transport protocol number.
|
||||
ICMPv6ProtocolNumber tcpip.TransportProtocolNumber = 58
|
||||
|
||||
// ICMPv6NeighborSolicitMinimumSize is the minimum size of a
|
||||
// neighbor solicitation packet.
|
||||
ICMPv6NeighborSolicitMinimumSize = ICMPv6HeaderSize + NDPNSMinimumSize
|
||||
|
||||
// ICMPv6NeighborAdvertMinimumSize is the minimum size of a
|
||||
// neighbor advertisement packet.
|
||||
ICMPv6NeighborAdvertMinimumSize = ICMPv6HeaderSize + NDPNAMinimumSize
|
||||
|
||||
// ICMPv6EchoMinimumSize is the minimum size of a valid echo packet.
|
||||
ICMPv6EchoMinimumSize = 8
|
||||
|
||||
// ICMPv6ErrorHeaderSize is the size of an ICMP error packet header,
|
||||
// as per RFC 4443, Appendix A, item 4 and the errata.
|
||||
// ... all ICMP error messages shall have exactly
|
||||
// 32 bits of type-specific data, so that receivers can reliably find
|
||||
// the embedded invoking packet even when they don't recognize the
|
||||
// ICMP message Type.
|
||||
ICMPv6ErrorHeaderSize = 8
|
||||
|
||||
// ICMPv6DstUnreachableMinimumSize is the minimum size of a valid ICMP
|
||||
// destination unreachable packet.
|
||||
ICMPv6DstUnreachableMinimumSize = ICMPv6MinimumSize
|
||||
|
||||
// ICMPv6PacketTooBigMinimumSize is the minimum size of a valid ICMP
|
||||
// packet-too-big packet.
|
||||
ICMPv6PacketTooBigMinimumSize = ICMPv6MinimumSize
|
||||
|
||||
// ICMPv6ChecksumOffset is the offset of the checksum field
|
||||
// in an ICMPv6 message.
|
||||
ICMPv6ChecksumOffset = 2
|
||||
|
||||
// icmpv6PointerOffset is the offset of the pointer
|
||||
// in an ICMPv6 Parameter problem message.
|
||||
icmpv6PointerOffset = 4
|
||||
|
||||
// icmpv6MTUOffset is the offset of the MTU field in an ICMPv6
|
||||
// PacketTooBig message.
|
||||
icmpv6MTUOffset = 4
|
||||
|
||||
// icmpv6IdentOffset is the offset of the ident field
|
||||
// in a ICMPv6 Echo Request/Reply message.
|
||||
icmpv6IdentOffset = 4
|
||||
|
||||
// icmpv6SequenceOffset is the offset of the sequence field
|
||||
// in a ICMPv6 Echo Request/Reply message.
|
||||
icmpv6SequenceOffset = 6
|
||||
|
||||
// NDPHopLimit is the expected IP hop limit value of 255 for received
|
||||
// NDP packets, as per RFC 4861 sections 4.1 - 4.5, 6.1.1, 6.1.2, 7.1.1,
|
||||
// 7.1.2 and 8.1. If the hop limit value is not 255, nodes MUST silently
|
||||
// drop the NDP packet. All outgoing NDP packets must use this value for
|
||||
// its IP hop limit field.
|
||||
NDPHopLimit = 255
|
||||
)
|
||||
|
||||
// ICMPv6Type is the ICMP type field described in RFC 4443.
|
||||
type ICMPv6Type byte
|
||||
|
||||
// Values for use in the Type field of ICMPv6 packet from RFC 4433.
|
||||
const (
|
||||
ICMPv6DstUnreachable ICMPv6Type = 1
|
||||
ICMPv6PacketTooBig ICMPv6Type = 2
|
||||
ICMPv6TimeExceeded ICMPv6Type = 3
|
||||
ICMPv6ParamProblem ICMPv6Type = 4
|
||||
ICMPv6EchoRequest ICMPv6Type = 128
|
||||
ICMPv6EchoReply ICMPv6Type = 129
|
||||
|
||||
// Neighbor Discovery Protocol (NDP) messages, see RFC 4861.
|
||||
|
||||
ICMPv6RouterSolicit ICMPv6Type = 133
|
||||
ICMPv6RouterAdvert ICMPv6Type = 134
|
||||
ICMPv6NeighborSolicit ICMPv6Type = 135
|
||||
ICMPv6NeighborAdvert ICMPv6Type = 136
|
||||
ICMPv6RedirectMsg ICMPv6Type = 137
|
||||
|
||||
// Multicast Listener Discovery (MLD) messages, see RFC 2710.
|
||||
|
||||
ICMPv6MulticastListenerQuery ICMPv6Type = 130
|
||||
ICMPv6MulticastListenerReport ICMPv6Type = 131
|
||||
ICMPv6MulticastListenerDone ICMPv6Type = 132
|
||||
|
||||
// Multicast Listener Discovert Version 2 (MLDv2) messages, see RFC 3810.
|
||||
|
||||
ICMPv6MulticastListenerV2Report ICMPv6Type = 143
|
||||
)
|
||||
|
||||
// IsErrorType returns true if the receiver is an ICMP error type.
|
||||
func (typ ICMPv6Type) IsErrorType() bool {
|
||||
// Per RFC 4443 section 2.1:
|
||||
// ICMPv6 messages are grouped into two classes: error messages and
|
||||
// informational messages. Error messages are identified as such by a
|
||||
// zero in the high-order bit of their message Type field values. Thus,
|
||||
// error messages have message types from 0 to 127; informational
|
||||
// messages have message types from 128 to 255.
|
||||
return typ&0x80 == 0
|
||||
}
|
||||
|
||||
// ICMPv6Code is the ICMP Code field described in RFC 4443.
|
||||
type ICMPv6Code byte
|
||||
|
||||
// ICMP codes used with Destination Unreachable (Type 1). As per RFC 4443
|
||||
// section 3.1.
|
||||
const (
|
||||
ICMPv6NetworkUnreachable ICMPv6Code = 0
|
||||
ICMPv6Prohibited ICMPv6Code = 1
|
||||
ICMPv6BeyondScope ICMPv6Code = 2
|
||||
ICMPv6AddressUnreachable ICMPv6Code = 3
|
||||
ICMPv6PortUnreachable ICMPv6Code = 4
|
||||
ICMPv6Policy ICMPv6Code = 5
|
||||
ICMPv6RejectRoute ICMPv6Code = 6
|
||||
)
|
||||
|
||||
// ICMP codes used with Time Exceeded (Type 3). As per RFC 4443 section 3.3.
|
||||
const (
|
||||
ICMPv6HopLimitExceeded ICMPv6Code = 0
|
||||
ICMPv6ReassemblyTimeout ICMPv6Code = 1
|
||||
)
|
||||
|
||||
// ICMP codes used with Parameter Problem (Type 4). As per RFC 4443 section 3.4.
|
||||
const (
|
||||
// ICMPv6ErroneousHeader indicates an erroneous header field was encountered.
|
||||
ICMPv6ErroneousHeader ICMPv6Code = 0
|
||||
|
||||
// ICMPv6UnknownHeader indicates an unrecognized Next Header type encountered.
|
||||
ICMPv6UnknownHeader ICMPv6Code = 1
|
||||
|
||||
// ICMPv6UnknownOption indicates an unrecognized IPv6 option was encountered.
|
||||
ICMPv6UnknownOption ICMPv6Code = 2
|
||||
)
|
||||
|
||||
// ICMPv6UnusedCode is the code value used with ICMPv6 messages which don't use
|
||||
// the code field. (Types not mentioned above.)
|
||||
const ICMPv6UnusedCode ICMPv6Code = 0
|
||||
|
||||
// Type is the ICMP type field.
|
||||
func (b ICMPv6) Type() ICMPv6Type { return ICMPv6Type(b[0]) }
|
||||
|
||||
// SetType sets the ICMP type field.
|
||||
func (b ICMPv6) SetType(t ICMPv6Type) { b[0] = byte(t) }
|
||||
|
||||
// Code is the ICMP code field. Its meaning depends on the value of Type.
|
||||
func (b ICMPv6) Code() ICMPv6Code { return ICMPv6Code(b[1]) }
|
||||
|
||||
// SetCode sets the ICMP code field.
|
||||
func (b ICMPv6) SetCode(c ICMPv6Code) { b[1] = byte(c) }
|
||||
|
||||
// TypeSpecific returns the type specific data field.
|
||||
func (b ICMPv6) TypeSpecific() uint32 {
|
||||
return binary.BigEndian.Uint32(b[icmpv6PointerOffset:])
|
||||
}
|
||||
|
||||
// SetTypeSpecific sets the type specific data field.
|
||||
func (b ICMPv6) SetTypeSpecific(val uint32) {
|
||||
binary.BigEndian.PutUint32(b[icmpv6PointerOffset:], val)
|
||||
}
|
||||
|
||||
// Checksum is the ICMP checksum field.
|
||||
func (b ICMPv6) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[ICMPv6ChecksumOffset:])
|
||||
}
|
||||
|
||||
// SetChecksum sets the ICMP checksum field.
|
||||
func (b ICMPv6) SetChecksum(cs uint16) {
|
||||
checksum.Put(b[ICMPv6ChecksumOffset:], cs)
|
||||
}
|
||||
|
||||
// SourcePort implements Transport.SourcePort.
|
||||
func (ICMPv6) SourcePort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DestinationPort implements Transport.DestinationPort.
|
||||
func (ICMPv6) DestinationPort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetSourcePort implements Transport.SetSourcePort.
|
||||
func (ICMPv6) SetSourcePort(uint16) {
|
||||
}
|
||||
|
||||
// SetDestinationPort implements Transport.SetDestinationPort.
|
||||
func (ICMPv6) SetDestinationPort(uint16) {
|
||||
}
|
||||
|
||||
// MTU retrieves the MTU field from an ICMPv6 message.
|
||||
func (b ICMPv6) MTU() uint32 {
|
||||
return binary.BigEndian.Uint32(b[icmpv6MTUOffset:])
|
||||
}
|
||||
|
||||
// SetMTU sets the MTU field from an ICMPv6 message.
|
||||
func (b ICMPv6) SetMTU(mtu uint32) {
|
||||
binary.BigEndian.PutUint32(b[icmpv6MTUOffset:], mtu)
|
||||
}
|
||||
|
||||
// Ident retrieves the Ident field from an ICMPv6 message.
|
||||
func (b ICMPv6) Ident() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv6IdentOffset:])
|
||||
}
|
||||
|
||||
// SetIdent sets the Ident field from an ICMPv6 message.
|
||||
func (b ICMPv6) SetIdent(ident uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv6IdentOffset:], ident)
|
||||
}
|
||||
|
||||
// SetIdentWithChecksumUpdate sets the Ident field and updates the checksum.
|
||||
func (b ICMPv6) SetIdentWithChecksumUpdate(new uint16) {
|
||||
old := b.Ident()
|
||||
b.SetIdent(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// Sequence retrieves the Sequence field from an ICMPv6 message.
|
||||
func (b ICMPv6) Sequence() uint16 {
|
||||
return binary.BigEndian.Uint16(b[icmpv6SequenceOffset:])
|
||||
}
|
||||
|
||||
// SetSequence sets the Sequence field from an ICMPv6 message.
|
||||
func (b ICMPv6) SetSequence(sequence uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv6SequenceOffset:], sequence)
|
||||
}
|
||||
|
||||
// MessageBody returns the message body as defined by RFC 4443 section 2.1; the
|
||||
// portion of the ICMPv6 buffer after the first ICMPv6HeaderSize bytes.
|
||||
func (b ICMPv6) MessageBody() []byte {
|
||||
return b[ICMPv6HeaderSize:]
|
||||
}
|
||||
|
||||
// Payload implements Transport.Payload.
|
||||
func (b ICMPv6) Payload() []byte {
|
||||
return b[ICMPv6PayloadOffset:]
|
||||
}
|
||||
|
||||
// ICMPv6ChecksumParams contains parameters to calculate ICMPv6 checksum.
|
||||
type ICMPv6ChecksumParams struct {
|
||||
Header ICMPv6
|
||||
Src tcpip.Address
|
||||
Dst tcpip.Address
|
||||
PayloadCsum uint16
|
||||
PayloadLen int
|
||||
}
|
||||
|
||||
// ICMPv6Checksum calculates the ICMP checksum over the provided ICMPv6 header,
|
||||
// IPv6 src/dst addresses and the payload.
|
||||
func ICMPv6Checksum(params ICMPv6ChecksumParams) uint16 {
|
||||
h := params.Header
|
||||
|
||||
xsum := PseudoHeaderChecksum(ICMPv6ProtocolNumber, params.Src, params.Dst, uint16(len(h)+params.PayloadLen))
|
||||
xsum = checksum.Combine(xsum, params.PayloadCsum)
|
||||
|
||||
// h[2:4] is the checksum itself, skip it to avoid checksumming the checksum.
|
||||
xsum = checksum.Checksum(h[:2], xsum)
|
||||
xsum = checksum.Checksum(h[4:], xsum)
|
||||
|
||||
return ^xsum
|
||||
}
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress updates the checksum to reflect an
|
||||
// updated address in the pseudo header.
|
||||
func (b ICMPv6) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address) {
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedAddress(^b.Checksum(), old, new))
|
||||
}
|
||||
185
pkg/tcpip/header/igmp.go
Normal file
185
pkg/tcpip/header/igmp.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2020 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
)
|
||||
|
||||
// IGMP represents an IGMP header stored in a byte array.
|
||||
type IGMP []byte
|
||||
|
||||
// IGMP implements `Transport`.
|
||||
var _ Transport = (*IGMP)(nil)
|
||||
|
||||
const (
|
||||
// IGMPMinimumSize is the minimum size of a valid IGMP packet in bytes,
|
||||
// as per RFC 2236, Section 2, Page 2.
|
||||
IGMPMinimumSize = 8
|
||||
|
||||
// IGMPQueryMinimumSize is the minimum size of a valid Membership Query
|
||||
// Message in bytes, as per RFC 2236, Section 2, Page 2.
|
||||
IGMPQueryMinimumSize = 8
|
||||
|
||||
// IGMPReportMinimumSize is the minimum size of a valid Report Message in
|
||||
// bytes, as per RFC 2236, Section 2, Page 2.
|
||||
IGMPReportMinimumSize = 8
|
||||
|
||||
// IGMPLeaveMessageMinimumSize is the minimum size of a valid Leave Message
|
||||
// in bytes, as per RFC 2236, Section 2, Page 2.
|
||||
IGMPLeaveMessageMinimumSize = 8
|
||||
|
||||
// IGMPTTL is the TTL for all IGMP messages, as per RFC 2236, Section 3, Page
|
||||
// 3.
|
||||
IGMPTTL = 1
|
||||
|
||||
// igmpTypeOffset defines the offset of the type field in an IGMP message.
|
||||
igmpTypeOffset = 0
|
||||
|
||||
// igmpMaxRespTimeOffset defines the offset of the MaxRespTime field in an
|
||||
// IGMP message.
|
||||
igmpMaxRespTimeOffset = 1
|
||||
|
||||
// igmpChecksumOffset defines the offset of the checksum field in an IGMP
|
||||
// message.
|
||||
igmpChecksumOffset = 2
|
||||
|
||||
// igmpGroupAddressOffset defines the offset of the Group Address field in an
|
||||
// IGMP message.
|
||||
igmpGroupAddressOffset = 4
|
||||
|
||||
// IGMPProtocolNumber is IGMP's transport protocol number.
|
||||
IGMPProtocolNumber tcpip.TransportProtocolNumber = 2
|
||||
)
|
||||
|
||||
// IGMPType is the IGMP type field as per RFC 2236.
|
||||
type IGMPType byte
|
||||
|
||||
// Values for the IGMP Type described in RFC 2236 Section 2.1, Page 2.
|
||||
// Descriptions below come from there.
|
||||
const (
|
||||
// IGMPMembershipQuery indicates that the message type is Membership Query.
|
||||
// "There are two sub-types of Membership Query messages:
|
||||
// - General Query, used to learn which groups have members on an
|
||||
// attached network.
|
||||
// - Group-Specific Query, used to learn if a particular group
|
||||
// has any members on an attached network.
|
||||
// These two messages are differentiated by the Group Address, as
|
||||
// described in section 1.4 ."
|
||||
IGMPMembershipQuery IGMPType = 0x11
|
||||
// IGMPv1MembershipReport indicates that the message is a Membership Report
|
||||
// generated by a host using the IGMPv1 protocol: "an additional type of
|
||||
// message, for backwards-compatibility with IGMPv1"
|
||||
IGMPv1MembershipReport IGMPType = 0x12
|
||||
// IGMPv2MembershipReport indicates that the Message type is a Membership
|
||||
// Report generated by a host using the IGMPv2 protocol.
|
||||
IGMPv2MembershipReport IGMPType = 0x16
|
||||
// IGMPLeaveGroup indicates that the message type is a Leave Group
|
||||
// notification message.
|
||||
IGMPLeaveGroup IGMPType = 0x17
|
||||
// IGMPv3MembershipReport indicates that the message type is a IGMPv3 report.
|
||||
IGMPv3MembershipReport IGMPType = 0x22
|
||||
)
|
||||
|
||||
// Type is the IGMP type field.
|
||||
func (b IGMP) Type() IGMPType { return IGMPType(b[igmpTypeOffset]) }
|
||||
|
||||
// SetType sets the IGMP type field.
|
||||
func (b IGMP) SetType(t IGMPType) { b[igmpTypeOffset] = byte(t) }
|
||||
|
||||
// MaxRespTime gets the MaxRespTimeField. This is meaningful only in Membership
|
||||
// Query messages, in other cases it is set to 0 by the sender and ignored by
|
||||
// the receiver.
|
||||
func (b IGMP) MaxRespTime() time.Duration {
|
||||
// As per RFC 2236 section 2.2,
|
||||
//
|
||||
// The Max Response Time field is meaningful only in Membership Query
|
||||
// messages, and specifies the maximum allowed time before sending a
|
||||
// responding report in units of 1/10 second. In all other messages, it
|
||||
// is set to zero by the sender and ignored by receivers.
|
||||
return DecisecondToDuration(uint16(b[igmpMaxRespTimeOffset]))
|
||||
}
|
||||
|
||||
// SetMaxRespTime sets the MaxRespTimeField.
|
||||
func (b IGMP) SetMaxRespTime(m byte) { b[igmpMaxRespTimeOffset] = m }
|
||||
|
||||
// Checksum is the IGMP checksum field.
|
||||
func (b IGMP) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[igmpChecksumOffset:])
|
||||
}
|
||||
|
||||
// SetChecksum sets the IGMP checksum field.
|
||||
func (b IGMP) SetChecksum(checksum uint16) {
|
||||
binary.BigEndian.PutUint16(b[igmpChecksumOffset:], checksum)
|
||||
}
|
||||
|
||||
// GroupAddress gets the Group Address field.
|
||||
func (b IGMP) GroupAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom4([4]byte(b[igmpGroupAddressOffset:][:IPv4AddressSize]))
|
||||
}
|
||||
|
||||
// SetGroupAddress sets the Group Address field.
|
||||
func (b IGMP) SetGroupAddress(address tcpip.Address) {
|
||||
addrBytes := address.As4()
|
||||
if n := copy(b[igmpGroupAddressOffset:], addrBytes[:]); n != IPv4AddressSize {
|
||||
panic(fmt.Sprintf("copied %d bytes, expected %d", n, IPv4AddressSize))
|
||||
}
|
||||
}
|
||||
|
||||
// SourcePort implements Transport.SourcePort.
|
||||
func (IGMP) SourcePort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DestinationPort implements Transport.DestinationPort.
|
||||
func (IGMP) DestinationPort() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetSourcePort implements Transport.SetSourcePort.
|
||||
func (IGMP) SetSourcePort(uint16) {
|
||||
}
|
||||
|
||||
// SetDestinationPort implements Transport.SetDestinationPort.
|
||||
func (IGMP) SetDestinationPort(uint16) {
|
||||
}
|
||||
|
||||
// Payload implements Transport.Payload.
|
||||
func (IGMP) Payload() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IGMPCalculateChecksum calculates the IGMP checksum over the provided IGMP
|
||||
// header.
|
||||
func IGMPCalculateChecksum(h IGMP) uint16 {
|
||||
// The header contains a checksum itself, set it aside to avoid checksumming
|
||||
// the checksum and replace it afterwards.
|
||||
existingXsum := h.Checksum()
|
||||
h.SetChecksum(0)
|
||||
xsum := ^checksum.Checksum(h, 0)
|
||||
h.SetChecksum(existingXsum)
|
||||
return xsum
|
||||
}
|
||||
|
||||
// DecisecondToDuration converts a value representing deci-seconds to a
|
||||
// time.Duration.
|
||||
func DecisecondToDuration(ds uint16) time.Duration {
|
||||
return time.Duration(ds) * time.Second / 10
|
||||
}
|
||||
500
pkg/tcpip/header/igmpv3.go
Normal file
500
pkg/tcpip/header/igmpv3.go
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
// Copyright 2022 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 header
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
// IGMPv3RoutersAddress is the address to send IGMPv3 reports to.
|
||||
//
|
||||
// As per RFC 3376 section 4.2.14,
|
||||
//
|
||||
// Version 3 Reports are sent with an IP destination address of
|
||||
// 224.0.0.22, to which all IGMPv3-capable multicast routers listen.
|
||||
var IGMPv3RoutersAddress = tcpip.AddrFrom4([4]byte{0xe0, 0x00, 0x00, 0x16})
|
||||
|
||||
const (
|
||||
// IGMPv3QueryMinimumSize is the mimum size of a valid IGMPv3 query,
|
||||
// as per RFC 3376 section 4.1.
|
||||
IGMPv3QueryMinimumSize = 12
|
||||
|
||||
igmpv3QueryMaxRespCodeOffset = 1
|
||||
igmpv3QueryGroupAddressOffset = 4
|
||||
igmpv3QueryResvSQRVOffset = 8
|
||||
igmpv3QueryQRVMask = 0b111
|
||||
igmpv3QueryQQICOffset = 9
|
||||
igmpv3QueryNumberOfSourcesOffset = 10
|
||||
igmpv3QuerySourcesOffset = 12
|
||||
)
|
||||
|
||||
// IGMPv3Query is an IGMPv3 query message.
|
||||
//
|
||||
// As per RFC 3376 section 4.1,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 0x11 | Max Resp Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Group Address |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Resv |S| QRV | QQIC | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Source Address [1] |
|
||||
// +- -+
|
||||
// | Source Address [2] |
|
||||
// +- . -+
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | Source Address [N] |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3Query IGMP
|
||||
|
||||
// MaximumResponseCode returns the Maximum Response Code.
|
||||
func (i IGMPv3Query) MaximumResponseCode() uint8 {
|
||||
return i[igmpv3QueryMaxRespCodeOffset]
|
||||
}
|
||||
|
||||
// IGMPv3MaximumResponseDelay returns the Maximum Response Delay in an IGMPv3
|
||||
// Maximum Response Code.
|
||||
//
|
||||
// As per RFC 3376 section 4.1.1,
|
||||
//
|
||||
// The Max Resp Code field specifies the maximum time allowed before
|
||||
// sending a responding report. The actual time allowed, called the Max
|
||||
// Resp Time, is represented in units of 1/10 second and is derived from
|
||||
// the Max Resp Code as follows:
|
||||
//
|
||||
// If Max Resp Code < 128, Max Resp Time = Max Resp Code
|
||||
//
|
||||
// If Max Resp Code >= 128, Max Resp Code represents a floating-point
|
||||
// value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Max Resp Time = (mant | 0x10) << (exp + 3)
|
||||
//
|
||||
// Small values of Max Resp Time allow IGMPv3 routers to tune the "leave
|
||||
// latency" (the time between the moment the last host leaves a group
|
||||
// and the moment the routing protocol is notified that there are no
|
||||
// more members). Larger values, especially in the exponential range,
|
||||
// allow tuning of the burstiness of IGMP traffic on a network.
|
||||
func IGMPv3MaximumResponseDelay(codeRaw uint8) time.Duration {
|
||||
code := uint16(codeRaw)
|
||||
if code < 128 {
|
||||
return DecisecondToDuration(code)
|
||||
}
|
||||
|
||||
const mantBits = 4
|
||||
const expMask = 0b111
|
||||
exp := (code >> mantBits) & expMask
|
||||
mant := code & ((1 << mantBits) - 1)
|
||||
return DecisecondToDuration((mant | 0x10) << (exp + 3))
|
||||
}
|
||||
|
||||
// GroupAddress returns the group address.
|
||||
func (i IGMPv3Query) GroupAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom4([4]byte(i[igmpv3QueryGroupAddressOffset:][:IPv4AddressSize]))
|
||||
}
|
||||
|
||||
// QuerierRobustnessVariable returns the querier's robustness variable.
|
||||
func (i IGMPv3Query) QuerierRobustnessVariable() uint8 {
|
||||
return i[igmpv3QueryResvSQRVOffset] & igmpv3QueryQRVMask
|
||||
}
|
||||
|
||||
// QuerierQueryInterval returns the querier's query interval.
|
||||
func (i IGMPv3Query) QuerierQueryInterval() time.Duration {
|
||||
return mldv2AndIGMPv3QuerierQueryCodeToInterval(i[igmpv3QueryQQICOffset])
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (i IGMPv3Query) Sources() (AddressIterator, bool) {
|
||||
return makeAddressIterator(
|
||||
i[igmpv3QuerySourcesOffset:],
|
||||
binary.BigEndian.Uint16(i[igmpv3QueryNumberOfSourcesOffset:]),
|
||||
IPv4AddressSize,
|
||||
)
|
||||
}
|
||||
|
||||
// IGMPv3ReportRecordType is the type of an IGMPv3 multicast address record
|
||||
// found in an IGMPv3 report, as per RFC 3810 section 5.2.12.
|
||||
type IGMPv3ReportRecordType int
|
||||
|
||||
// IGMPv3 multicast address record types, as per RFC 3810 section 5.2.12.
|
||||
const (
|
||||
IGMPv3ReportRecordModeIsInclude IGMPv3ReportRecordType = 1
|
||||
IGMPv3ReportRecordModeIsExclude IGMPv3ReportRecordType = 2
|
||||
IGMPv3ReportRecordChangeToIncludeMode IGMPv3ReportRecordType = 3
|
||||
IGMPv3ReportRecordChangeToExcludeMode IGMPv3ReportRecordType = 4
|
||||
IGMPv3ReportRecordAllowNewSources IGMPv3ReportRecordType = 5
|
||||
IGMPv3ReportRecordBlockOldSources IGMPv3ReportRecordType = 6
|
||||
)
|
||||
|
||||
const (
|
||||
igmpv3ReportGroupAddressRecordMinimumSize = 8
|
||||
igmpv3ReportGroupAddressRecordTypeOffset = 0
|
||||
igmpv3ReportGroupAddressRecordAuxDataLenOffset = 1
|
||||
igmpv3ReportGroupAddressRecordAuxDataLenUnits = 4
|
||||
igmpv3ReportGroupAddressRecordNumberOfSourcesOffset = 2
|
||||
igmpv3ReportGroupAddressRecordGroupAddressOffset = 4
|
||||
igmpv3ReportGroupAddressRecordSourcesOffset = 8
|
||||
)
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordSerializer is an IGMPv3 Multicast Address
|
||||
// Record serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportGroupAddressRecordSerializer struct {
|
||||
RecordType IGMPv3ReportRecordType
|
||||
GroupAddress tcpip.Address
|
||||
Sources []tcpip.Address
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *IGMPv3ReportGroupAddressRecordSerializer) Length() int {
|
||||
return igmpv3ReportGroupAddressRecordSourcesOffset + len(s.Sources)*IPv4AddressSize
|
||||
}
|
||||
|
||||
func copyIPv4Address(dst []byte, src tcpip.Address) {
|
||||
srcBytes := src.As4()
|
||||
if n := copy(dst, srcBytes[:]); n != IPv4AddressSize {
|
||||
panic(fmt.Sprintf("got copy(...) = %d, want = %d", n, IPv4AddressSize))
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeInto serializes the record into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the record.
|
||||
func (s *IGMPv3ReportGroupAddressRecordSerializer) SerializeInto(b []byte) {
|
||||
b[igmpv3ReportGroupAddressRecordTypeOffset] = byte(s.RecordType)
|
||||
b[igmpv3ReportGroupAddressRecordAuxDataLenOffset] = 0
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportGroupAddressRecordNumberOfSourcesOffset:], uint16(len(s.Sources)))
|
||||
copyIPv4Address(b[igmpv3ReportGroupAddressRecordGroupAddressOffset:], s.GroupAddress)
|
||||
b = b[igmpv3ReportGroupAddressRecordSourcesOffset:]
|
||||
for _, source := range s.Sources {
|
||||
copyIPv4Address(b, source)
|
||||
b = b[IPv4AddressSize:]
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
igmpv3ReportTypeOffset = 0
|
||||
igmpv3ReportReserved1Offset = 1
|
||||
igmpv3ReportReserved2Offset = 4
|
||||
igmpv3ReportNumberOfGroupAddressRecordsOffset = 6
|
||||
igmpv3ReportGroupAddressRecordsOffset = 8
|
||||
)
|
||||
|
||||
// IGMPv3ReportSerializer is an MLD Version 2 Report serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportSerializer struct {
|
||||
Records []IGMPv3ReportGroupAddressRecordSerializer
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *IGMPv3ReportSerializer) Length() int {
|
||||
ret := igmpv3ReportGroupAddressRecordsOffset
|
||||
for _, record := range s.Records {
|
||||
ret += record.Length()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SerializeInto serializes the report into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the report.
|
||||
func (s *IGMPv3ReportSerializer) SerializeInto(b []byte) {
|
||||
b[igmpv3ReportTypeOffset] = byte(IGMPv3MembershipReport)
|
||||
b[igmpv3ReportReserved1Offset] = 0
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportReserved2Offset:], 0)
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportNumberOfGroupAddressRecordsOffset:], uint16(len(s.Records)))
|
||||
recordsBytes := b[igmpv3ReportGroupAddressRecordsOffset:]
|
||||
for _, record := range s.Records {
|
||||
len := record.Length()
|
||||
record.SerializeInto(recordsBytes[:len])
|
||||
recordsBytes = recordsBytes[len:]
|
||||
}
|
||||
binary.BigEndian.PutUint16(b[igmpChecksumOffset:], IGMPCalculateChecksum(b))
|
||||
}
|
||||
|
||||
// IGMPv3ReportGroupAddressRecord is an IGMPv3 record.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportGroupAddressRecord []byte
|
||||
|
||||
// RecordType returns the type of this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) RecordType() IGMPv3ReportRecordType {
|
||||
return IGMPv3ReportRecordType(r[igmpv3ReportGroupAddressRecordTypeOffset])
|
||||
}
|
||||
|
||||
// AuxDataLen returns the length of the auxiliary data in this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) AuxDataLen() int {
|
||||
return int(r[igmpv3ReportGroupAddressRecordAuxDataLenOffset]) * igmpv3ReportGroupAddressRecordAuxDataLenUnits
|
||||
}
|
||||
|
||||
// numberOfSources returns the number of sources in this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) numberOfSources() uint16 {
|
||||
return binary.BigEndian.Uint16(r[igmpv3ReportGroupAddressRecordNumberOfSourcesOffset:])
|
||||
}
|
||||
|
||||
// GroupAddress returns the multicast address this record targets.
|
||||
func (r IGMPv3ReportGroupAddressRecord) GroupAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom4([4]byte(r[igmpv3ReportGroupAddressRecordGroupAddressOffset:][:IPv4AddressSize]))
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (r IGMPv3ReportGroupAddressRecord) Sources() (AddressIterator, bool) {
|
||||
expectedLen := int(r.numberOfSources()) * IPv4AddressSize
|
||||
b := r[igmpv3ReportGroupAddressRecordSourcesOffset:]
|
||||
if len(b) < expectedLen {
|
||||
return AddressIterator{}, false
|
||||
}
|
||||
return AddressIterator{addressSize: IPv4AddressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true
|
||||
}
|
||||
|
||||
// IGMPv3Report is an IGMPv3 Report.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3Report []byte
|
||||
|
||||
// Checksum returns the checksum.
|
||||
func (i IGMPv3Report) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(i[igmpChecksumOffset:])
|
||||
}
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIterator is an iterator over IGMPv3 Multicast
|
||||
// Address Records.
|
||||
type IGMPv3ReportGroupAddressRecordIterator struct {
|
||||
recordsLeft uint16
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextDisposition is the possible
|
||||
// return values from IGMPv3ReportGroupAddressRecordIterator.Next.
|
||||
type IGMPv3ReportGroupAddressRecordIteratorNextDisposition int
|
||||
|
||||
const (
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextOk indicates that a multicast
|
||||
// address record was yielded.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextOk IGMPv3ReportGroupAddressRecordIteratorNextDisposition = iota
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextDone indicates that the iterator
|
||||
// has been exhausted.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextDone
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort indicates
|
||||
// that the iterator expected another record, but the buffer ended
|
||||
// prematurely.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
)
|
||||
|
||||
// Next returns the next IGMPv3 Multicast Address Record.
|
||||
func (it *IGMPv3ReportGroupAddressRecordIterator) Next() (IGMPv3ReportGroupAddressRecord, IGMPv3ReportGroupAddressRecordIteratorNextDisposition) {
|
||||
if it.recordsLeft == 0 {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextDone
|
||||
}
|
||||
if it.buf.Len() < igmpv3ReportGroupAddressRecordMinimumSize {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
|
||||
hdr := IGMPv3ReportGroupAddressRecord(it.buf.Bytes())
|
||||
expectedLen := igmpv3ReportGroupAddressRecordMinimumSize +
|
||||
int(hdr.AuxDataLen()) + int(hdr.numberOfSources())*IPv4AddressSize
|
||||
|
||||
bytes := it.buf.Next(expectedLen)
|
||||
if len(bytes) < expectedLen {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
it.recordsLeft--
|
||||
return IGMPv3ReportGroupAddressRecord(bytes), IGMPv3ReportGroupAddressRecordIteratorNextOk
|
||||
}
|
||||
|
||||
// GroupAddressRecords returns an iterator of IGMPv3 Multicast Address
|
||||
// Records.
|
||||
func (i IGMPv3Report) GroupAddressRecords() IGMPv3ReportGroupAddressRecordIterator {
|
||||
return IGMPv3ReportGroupAddressRecordIterator{
|
||||
recordsLeft: binary.BigEndian.Uint16(i[igmpv3ReportNumberOfGroupAddressRecordsOffset:]),
|
||||
buf: bytes.NewBuffer(i[igmpv3ReportGroupAddressRecordsOffset:]),
|
||||
}
|
||||
}
|
||||
130
pkg/tcpip/header/interfaces.go
Normal file
130
pkg/tcpip/header/interfaces.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxIPPacketSize is the maximum supported IP packet size, excluding
|
||||
// jumbograms. The maximum IPv4 packet size is 64k-1 (total size must fit
|
||||
// in 16 bits). For IPv6, the payload max size (excluding jumbograms) is
|
||||
// 64k-1 (also needs to fit in 16 bits). So we use 64k - 1 + 2 * m, where
|
||||
// m is the minimum IPv6 header size; we leave room for some potential
|
||||
// IP options.
|
||||
MaxIPPacketSize = 0xffff + 2*IPv6MinimumSize
|
||||
)
|
||||
|
||||
// Transport offers generic methods to query and/or update the fields of the
|
||||
// header of a transport protocol buffer.
|
||||
type Transport interface {
|
||||
// SourcePort returns the value of the "source port" field.
|
||||
SourcePort() uint16
|
||||
|
||||
// Destination returns the value of the "destination port" field.
|
||||
DestinationPort() uint16
|
||||
|
||||
// Checksum returns the value of the "checksum" field.
|
||||
Checksum() uint16
|
||||
|
||||
// SetSourcePort sets the value of the "source port" field.
|
||||
SetSourcePort(uint16)
|
||||
|
||||
// SetDestinationPort sets the value of the "destination port" field.
|
||||
SetDestinationPort(uint16)
|
||||
|
||||
// SetChecksum sets the value of the "checksum" field.
|
||||
SetChecksum(uint16)
|
||||
|
||||
// Payload returns the data carried in the transport buffer.
|
||||
Payload() []byte
|
||||
}
|
||||
|
||||
// ChecksummableTransport is a Transport that supports checksumming.
|
||||
type ChecksummableTransport interface {
|
||||
Transport
|
||||
|
||||
// SetSourcePortWithChecksumUpdate sets the source port and updates
|
||||
// the checksum.
|
||||
//
|
||||
// The receiver's checksum must be a fully calculated checksum.
|
||||
SetSourcePortWithChecksumUpdate(port uint16)
|
||||
|
||||
// SetDestinationPortWithChecksumUpdate sets the destination port and updates
|
||||
// the checksum.
|
||||
//
|
||||
// The receiver's checksum must be a fully calculated checksum.
|
||||
SetDestinationPortWithChecksumUpdate(port uint16)
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress updates the checksum to reflect an
|
||||
// updated address in the pseudo header.
|
||||
//
|
||||
// If fullChecksum is true, the receiver's checksum field is assumed to hold a
|
||||
// fully calculated checksum. Otherwise, it is assumed to hold a partially
|
||||
// calculated checksum which only reflects the pseudo header.
|
||||
UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool)
|
||||
}
|
||||
|
||||
// Network offers generic methods to query and/or update the fields of the
|
||||
// header of a network protocol buffer.
|
||||
type Network interface {
|
||||
// SourceAddress returns the value of the "source address" field.
|
||||
SourceAddress() tcpip.Address
|
||||
|
||||
// DestinationAddress returns the value of the "destination address"
|
||||
// field.
|
||||
DestinationAddress() tcpip.Address
|
||||
|
||||
// Checksum returns the value of the "checksum" field.
|
||||
Checksum() uint16
|
||||
|
||||
// SetSourceAddress sets the value of the "source address" field.
|
||||
SetSourceAddress(tcpip.Address)
|
||||
|
||||
// SetDestinationAddress sets the value of the "destination address"
|
||||
// field.
|
||||
SetDestinationAddress(tcpip.Address)
|
||||
|
||||
// SetChecksum sets the value of the "checksum" field.
|
||||
SetChecksum(uint16)
|
||||
|
||||
// TransportProtocol returns the number of the transport protocol
|
||||
// stored in the payload.
|
||||
TransportProtocol() tcpip.TransportProtocolNumber
|
||||
|
||||
// Payload returns a byte slice containing the payload of the network
|
||||
// packet.
|
||||
Payload() []byte
|
||||
|
||||
// TOS returns the values of the "type of service" and "flow label" fields.
|
||||
TOS() (uint8, uint32)
|
||||
|
||||
// SetTOS sets the values of the "type of service" and "flow label" fields.
|
||||
SetTOS(t uint8, l uint32)
|
||||
}
|
||||
|
||||
// ChecksummableNetwork is a Network that supports checksumming.
|
||||
type ChecksummableNetwork interface {
|
||||
Network
|
||||
|
||||
// SetSourceAddressAndChecksum sets the source address and updates the
|
||||
// checksum to reflect the new address.
|
||||
SetSourceAddressWithChecksumUpdate(tcpip.Address)
|
||||
|
||||
// SetDestinationAddressAndChecksum sets the destination address and
|
||||
// updates the checksum to reflect the new address.
|
||||
SetDestinationAddressWithChecksumUpdate(tcpip.Address)
|
||||
}
|
||||
1274
pkg/tcpip/header/ipv4.go
Normal file
1274
pkg/tcpip/header/ipv4.go
Normal file
File diff suppressed because it is too large
Load diff
597
pkg/tcpip/header/ipv6.go
Normal file
597
pkg/tcpip/header/ipv6.go
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
versTCFL = 0
|
||||
// IPv6PayloadLenOffset is the offset of the PayloadLength field in
|
||||
// IPv6 header.
|
||||
IPv6PayloadLenOffset = 4
|
||||
// IPv6NextHeaderOffset is the offset of the NextHeader field in
|
||||
// IPv6 header.
|
||||
IPv6NextHeaderOffset = 6
|
||||
hopLimit = 7
|
||||
v6SrcAddr = 8
|
||||
v6DstAddr = v6SrcAddr + IPv6AddressSize
|
||||
|
||||
// IPv6FixedHeaderSize is the size of the fixed header.
|
||||
IPv6FixedHeaderSize = v6DstAddr + IPv6AddressSize
|
||||
)
|
||||
|
||||
// IPv6Fields contains the fields of an IPv6 packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type IPv6Fields struct {
|
||||
// TrafficClass is the "traffic class" field of an IPv6 packet.
|
||||
TrafficClass uint8
|
||||
|
||||
// FlowLabel is the "flow label" field of an IPv6 packet.
|
||||
FlowLabel uint32
|
||||
|
||||
// PayloadLength is the "payload length" field of an IPv6 packet, including
|
||||
// the length of all extension headers.
|
||||
PayloadLength uint16
|
||||
|
||||
// TransportProtocol is the transport layer protocol number. Serialized in the
|
||||
// last "next header" field of the IPv6 header + extension headers.
|
||||
TransportProtocol tcpip.TransportProtocolNumber
|
||||
|
||||
// HopLimit is the "Hop Limit" field of an IPv6 packet.
|
||||
HopLimit uint8
|
||||
|
||||
// SrcAddr is the "source ip address" of an IPv6 packet.
|
||||
SrcAddr tcpip.Address
|
||||
|
||||
// DstAddr is the "destination ip address" of an IPv6 packet.
|
||||
DstAddr tcpip.Address
|
||||
|
||||
// ExtensionHeaders are the extension headers following the IPv6 header.
|
||||
ExtensionHeaders IPv6ExtHdrSerializer
|
||||
}
|
||||
|
||||
// IPv6 represents an ipv6 header stored in a byte array.
|
||||
// Most of the methods of IPv6 access to the underlying slice without
|
||||
// checking the boundaries and could panic because of 'index out of range'.
|
||||
// Always call IsValid() to validate an instance of IPv6 before using other methods.
|
||||
type IPv6 []byte
|
||||
|
||||
const (
|
||||
// IPv6MinimumSize is the minimum size of a valid IPv6 packet.
|
||||
IPv6MinimumSize = IPv6FixedHeaderSize
|
||||
|
||||
// IPv6AddressSize is the size, in bytes, of an IPv6 address.
|
||||
IPv6AddressSize = 16
|
||||
|
||||
// IPv6AddressSizeBits is the size, in bits, of an IPv6 address.
|
||||
IPv6AddressSizeBits = 128
|
||||
|
||||
// IPv6MaximumPayloadSize is the maximum size of a valid IPv6 payload per
|
||||
// RFC 8200 Section 4.5.
|
||||
IPv6MaximumPayloadSize = 65535
|
||||
|
||||
// IPv6ProtocolNumber is IPv6's network protocol number.
|
||||
IPv6ProtocolNumber tcpip.NetworkProtocolNumber = 0x86dd
|
||||
|
||||
// IPv6Version is the version of the ipv6 protocol.
|
||||
IPv6Version = 6
|
||||
|
||||
// IIDSize is the size of an interface identifier (IID), in bytes, as
|
||||
// defined by RFC 4291 section 2.5.1.
|
||||
IIDSize = 8
|
||||
|
||||
// IPv6MinimumMTU is the minimum MTU required by IPv6, per RFC 8200,
|
||||
// section 5:
|
||||
// IPv6 requires that every link in the Internet have an MTU of 1280 octets
|
||||
// or greater. This is known as the IPv6 minimum link MTU.
|
||||
IPv6MinimumMTU = 1280
|
||||
|
||||
// IIDOffsetInIPv6Address is the offset, in bytes, from the start
|
||||
// of an IPv6 address to the beginning of the interface identifier
|
||||
// (IID) for auto-generated addresses. That is, all bytes before
|
||||
// the IIDOffsetInIPv6Address-th byte are the prefix bytes, and all
|
||||
// bytes including and after the IIDOffsetInIPv6Address-th byte are
|
||||
// for the IID.
|
||||
IIDOffsetInIPv6Address = 8
|
||||
|
||||
// OpaqueIIDSecretKeyMinBytes is the recommended minimum number of bytes
|
||||
// for the secret key used to generate an opaque interface identifier as
|
||||
// outlined by RFC 7217.
|
||||
OpaqueIIDSecretKeyMinBytes = 16
|
||||
|
||||
// ipv6MulticastAddressScopeByteIdx is the byte where the scope (scop) field
|
||||
// is located within a multicast IPv6 address, as per RFC 4291 section 2.7.
|
||||
ipv6MulticastAddressScopeByteIdx = 1
|
||||
|
||||
// ipv6MulticastAddressScopeMask is the mask for the scope (scop) field,
|
||||
// within the byte holding the field, as per RFC 4291 section 2.7.
|
||||
ipv6MulticastAddressScopeMask = 0xF
|
||||
)
|
||||
|
||||
var (
|
||||
// IPv6AllNodesMulticastAddress is a link-local multicast group that
|
||||
// all IPv6 nodes MUST join, as per RFC 4291, section 2.8. Packets
|
||||
// destined to this address will reach all nodes on a link.
|
||||
//
|
||||
// The address is ff02::1.
|
||||
IPv6AllNodesMulticastAddress = tcpip.AddrFrom16([16]byte{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
|
||||
|
||||
// IPv6AllRoutersInterfaceLocalMulticastAddress is an interface-local
|
||||
// multicast group that all IPv6 routers MUST join, as per RFC 4291, section
|
||||
// 2.8. Packets destined to this address will reach the router on an
|
||||
// interface.
|
||||
//
|
||||
// The address is ff01::2.
|
||||
IPv6AllRoutersInterfaceLocalMulticastAddress = tcpip.AddrFrom16([16]byte{0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
|
||||
|
||||
// IPv6AllRoutersLinkLocalMulticastAddress is a link-local multicast group
|
||||
// that all IPv6 routers MUST join, as per RFC 4291, section 2.8. Packets
|
||||
// destined to this address will reach all routers on a link.
|
||||
//
|
||||
// The address is ff02::2.
|
||||
IPv6AllRoutersLinkLocalMulticastAddress = tcpip.AddrFrom16([16]byte{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
|
||||
|
||||
// IPv6AllRoutersSiteLocalMulticastAddress is a site-local multicast group
|
||||
// that all IPv6 routers MUST join, as per RFC 4291, section 2.8. Packets
|
||||
// destined to this address will reach all routers in a site.
|
||||
//
|
||||
// The address is ff05::2.
|
||||
IPv6AllRoutersSiteLocalMulticastAddress = tcpip.AddrFrom16([16]byte{0xff, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
|
||||
|
||||
// IPv6Loopback is the IPv6 Loopback address.
|
||||
IPv6Loopback = tcpip.AddrFrom16([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
|
||||
|
||||
// IPv6Any is the non-routable IPv6 "any" meta address. It is also
|
||||
// known as the unspecified address.
|
||||
IPv6Any = tcpip.AddrFrom16([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
)
|
||||
|
||||
// IPv6EmptySubnet is the empty IPv6 subnet. It may also be known as the
|
||||
// catch-all or wildcard subnet. That is, all IPv6 addresses are considered to
|
||||
// be contained within this subnet.
|
||||
var IPv6EmptySubnet = tcpip.AddressWithPrefix{
|
||||
Address: IPv6Any,
|
||||
PrefixLen: 0,
|
||||
}.Subnet()
|
||||
|
||||
// IPv4MappedIPv6Subnet is the prefix for an IPv4 mapped IPv6 address as defined
|
||||
// by RFC 4291 section 2.5.5.
|
||||
var IPv4MappedIPv6Subnet = tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom16([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}),
|
||||
PrefixLen: 96,
|
||||
}.Subnet()
|
||||
|
||||
// IPv6LinkLocalPrefix is the prefix for IPv6 link-local addresses, as defined
|
||||
// by RFC 4291 section 2.5.6.
|
||||
//
|
||||
// The prefix is fe80::/64
|
||||
var IPv6LinkLocalPrefix = tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom16([16]byte{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
|
||||
PrefixLen: 64,
|
||||
}
|
||||
|
||||
// PayloadLength returns the value of the "payload length" field of the ipv6
|
||||
// header.
|
||||
func (b IPv6) PayloadLength() uint16 {
|
||||
return binary.BigEndian.Uint16(b[IPv6PayloadLenOffset:])
|
||||
}
|
||||
|
||||
// HopLimit returns the value of the "Hop Limit" field of the ipv6 header.
|
||||
func (b IPv6) HopLimit() uint8 {
|
||||
return b[hopLimit]
|
||||
}
|
||||
|
||||
// NextHeader returns the value of the "next header" field of the ipv6 header.
|
||||
func (b IPv6) NextHeader() uint8 {
|
||||
return b[IPv6NextHeaderOffset]
|
||||
}
|
||||
|
||||
// TransportProtocol implements Network.TransportProtocol.
|
||||
func (b IPv6) TransportProtocol() tcpip.TransportProtocolNumber {
|
||||
return tcpip.TransportProtocolNumber(b.NextHeader())
|
||||
}
|
||||
|
||||
// Payload implements Network.Payload.
|
||||
func (b IPv6) Payload() []byte {
|
||||
return b[IPv6MinimumSize:][:b.PayloadLength()]
|
||||
}
|
||||
|
||||
// SourceAddress returns the "source address" field of the ipv6 header.
|
||||
func (b IPv6) SourceAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom16([16]byte(b[v6SrcAddr:][:IPv6AddressSize]))
|
||||
}
|
||||
|
||||
// DestinationAddress returns the "destination address" field of the ipv6
|
||||
// header.
|
||||
func (b IPv6) DestinationAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom16([16]byte(b[v6DstAddr:][:IPv6AddressSize]))
|
||||
}
|
||||
|
||||
// SourceAddressSlice returns the "source address" field of the ipv6 header as a
|
||||
// byte slice.
|
||||
func (b IPv6) SourceAddressSlice() []byte {
|
||||
return []byte(b[v6SrcAddr:][:IPv6AddressSize])
|
||||
}
|
||||
|
||||
// DestinationAddressSlice returns the "destination address" field of the ipv6
|
||||
// header as a byte slice.
|
||||
func (b IPv6) DestinationAddressSlice() []byte {
|
||||
return []byte(b[v6DstAddr:][:IPv6AddressSize])
|
||||
}
|
||||
|
||||
// Checksum implements Network.Checksum. Given that IPv6 doesn't have a
|
||||
// checksum, it just returns 0.
|
||||
func (IPv6) Checksum() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TOS returns the "traffic class" and "flow label" fields of the ipv6 header.
|
||||
func (b IPv6) TOS() (uint8, uint32) {
|
||||
v := binary.BigEndian.Uint32(b[versTCFL:])
|
||||
return uint8(v >> 20), v & 0xfffff
|
||||
}
|
||||
|
||||
// SetTOS sets the "traffic class" and "flow label" fields of the ipv6 header.
|
||||
func (b IPv6) SetTOS(t uint8, l uint32) {
|
||||
vtf := (6 << 28) | (uint32(t) << 20) | (l & 0xfffff)
|
||||
binary.BigEndian.PutUint32(b[versTCFL:], vtf)
|
||||
}
|
||||
|
||||
// SetPayloadLength sets the "payload length" field of the ipv6 header.
|
||||
func (b IPv6) SetPayloadLength(payloadLength uint16) {
|
||||
binary.BigEndian.PutUint16(b[IPv6PayloadLenOffset:], payloadLength)
|
||||
}
|
||||
|
||||
// SetSourceAddress sets the "source address" field of the ipv6 header.
|
||||
func (b IPv6) SetSourceAddress(addr tcpip.Address) {
|
||||
copy(b[v6SrcAddr:][:IPv6AddressSize], addr.AsSlice())
|
||||
}
|
||||
|
||||
// SetDestinationAddress sets the "destination address" field of the ipv6
|
||||
// header.
|
||||
func (b IPv6) SetDestinationAddress(addr tcpip.Address) {
|
||||
copy(b[v6DstAddr:][:IPv6AddressSize], addr.AsSlice())
|
||||
}
|
||||
|
||||
// SetHopLimit sets the value of the "Hop Limit" field.
|
||||
func (b IPv6) SetHopLimit(v uint8) {
|
||||
b[hopLimit] = v
|
||||
}
|
||||
|
||||
// SetNextHeader sets the value of the "next header" field of the ipv6 header.
|
||||
func (b IPv6) SetNextHeader(v uint8) {
|
||||
b[IPv6NextHeaderOffset] = v
|
||||
}
|
||||
|
||||
// SetChecksum implements Network.SetChecksum. Given that IPv6 doesn't have a
|
||||
// checksum, it is empty.
|
||||
func (IPv6) SetChecksum(uint16) {
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the ipv6 header.
|
||||
func (b IPv6) Encode(i *IPv6Fields) {
|
||||
extHdr := b[IPv6MinimumSize:]
|
||||
b.SetTOS(i.TrafficClass, i.FlowLabel)
|
||||
b.SetPayloadLength(i.PayloadLength)
|
||||
b[hopLimit] = i.HopLimit
|
||||
b.SetSourceAddress(i.SrcAddr)
|
||||
b.SetDestinationAddress(i.DstAddr)
|
||||
nextHeader, _ := i.ExtensionHeaders.Serialize(i.TransportProtocol, extHdr)
|
||||
b[IPv6NextHeaderOffset] = nextHeader
|
||||
}
|
||||
|
||||
// IsValid performs basic validation on the packet.
|
||||
func (b IPv6) IsValid(pktSize int) bool {
|
||||
if len(b) < IPv6MinimumSize {
|
||||
return false
|
||||
}
|
||||
|
||||
dlen := int(b.PayloadLength())
|
||||
if dlen > pktSize-IPv6MinimumSize {
|
||||
return false
|
||||
}
|
||||
|
||||
if IPVersion(b) != IPv6Version {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsV4MappedAddress determines if the provided address is an IPv4 mapped
|
||||
// address by checking if its prefix is 0:0:0:0:0:ffff::/96.
|
||||
func IsV4MappedAddress(addr tcpip.Address) bool {
|
||||
if addr.BitLen() != IPv6AddressSizeBits {
|
||||
return false
|
||||
}
|
||||
|
||||
return IPv4MappedIPv6Subnet.Contains(addr)
|
||||
}
|
||||
|
||||
// IsV6MulticastAddress determines if the provided address is an IPv6
|
||||
// multicast address (anything starting with FF).
|
||||
func IsV6MulticastAddress(addr tcpip.Address) bool {
|
||||
if addr.BitLen() != IPv6AddressSizeBits {
|
||||
return false
|
||||
}
|
||||
return addr.As16()[0] == 0xff
|
||||
}
|
||||
|
||||
// IsV6UnicastAddress determines if the provided address is a valid IPv6
|
||||
// unicast (and specified) address. That is, IsV6UnicastAddress returns
|
||||
// true if addr contains IPv6AddressSize bytes, is not the unspecified
|
||||
// address and is not a multicast address.
|
||||
func IsV6UnicastAddress(addr tcpip.Address) bool {
|
||||
if addr.BitLen() != IPv6AddressSizeBits {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must not be unspecified
|
||||
if addr == IPv6Any {
|
||||
return false
|
||||
}
|
||||
|
||||
// Return if not a multicast.
|
||||
return addr.As16()[0] != 0xff
|
||||
}
|
||||
|
||||
var solicitedNodeMulticastPrefix = [13]byte{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff}
|
||||
|
||||
// SolicitedNodeAddr computes the solicited-node multicast address. This is
|
||||
// used for NDP. Described in RFC 4291. The argument must be a full-length IPv6
|
||||
// address.
|
||||
func SolicitedNodeAddr(addr tcpip.Address) tcpip.Address {
|
||||
addrBytes := addr.As16()
|
||||
return tcpip.AddrFrom16([16]byte(append(solicitedNodeMulticastPrefix[:], addrBytes[len(addrBytes)-3:]...)))
|
||||
}
|
||||
|
||||
// IsSolicitedNodeAddr determines whether the address is a solicited-node
|
||||
// multicast address.
|
||||
func IsSolicitedNodeAddr(addr tcpip.Address) bool {
|
||||
addrBytes := addr.As16()
|
||||
return solicitedNodeMulticastPrefix == [13]byte(addrBytes[:len(addrBytes)-3])
|
||||
}
|
||||
|
||||
// EthernetAdddressToModifiedEUI64IntoBuf populates buf with a modified EUI-64
|
||||
// from a 48-bit Ethernet/MAC address, as per RFC 4291 section 2.5.1.
|
||||
//
|
||||
// buf MUST be at least 8 bytes.
|
||||
func EthernetAdddressToModifiedEUI64IntoBuf(linkAddr tcpip.LinkAddress, buf []byte) {
|
||||
buf[0] = linkAddr[0] ^ 2
|
||||
buf[1] = linkAddr[1]
|
||||
buf[2] = linkAddr[2]
|
||||
buf[3] = 0xFF
|
||||
buf[4] = 0xFE
|
||||
buf[5] = linkAddr[3]
|
||||
buf[6] = linkAddr[4]
|
||||
buf[7] = linkAddr[5]
|
||||
}
|
||||
|
||||
// EthernetAddressToModifiedEUI64 computes a modified EUI-64 from a 48-bit
|
||||
// Ethernet/MAC address, as per RFC 4291 section 2.5.1.
|
||||
func EthernetAddressToModifiedEUI64(linkAddr tcpip.LinkAddress) [IIDSize]byte {
|
||||
var buf [IIDSize]byte
|
||||
EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, buf[:])
|
||||
return buf
|
||||
}
|
||||
|
||||
// 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 a modified 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 := [IPv6AddressSize]byte{
|
||||
0: 0xFE,
|
||||
1: 0x80,
|
||||
}
|
||||
EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, lladdrb[IIDOffsetInIPv6Address:])
|
||||
return tcpip.AddrFrom16(lladdrb)
|
||||
}
|
||||
|
||||
// IsV6LinkLocalUnicastAddress returns true iff the provided address is an IPv6
|
||||
// link-local unicast address, as defined by RFC 4291 section 2.5.6.
|
||||
func IsV6LinkLocalUnicastAddress(addr tcpip.Address) bool {
|
||||
if addr.BitLen() != IPv6AddressSizeBits {
|
||||
return false
|
||||
}
|
||||
addrBytes := addr.As16()
|
||||
return addrBytes[0] == 0xfe && (addrBytes[1]&0xc0) == 0x80
|
||||
}
|
||||
|
||||
// IsV6LoopbackAddress returns true iff the provided address is an IPv6 loopback
|
||||
// address, as defined by RFC 4291 section 2.5.3.
|
||||
func IsV6LoopbackAddress(addr tcpip.Address) bool {
|
||||
return addr == IPv6Loopback
|
||||
}
|
||||
|
||||
// IsV6LinkLocalMulticastAddress returns true iff the provided address is an
|
||||
// IPv6 link-local multicast address, as defined by RFC 4291 section 2.7.
|
||||
func IsV6LinkLocalMulticastAddress(addr tcpip.Address) bool {
|
||||
return IsV6MulticastAddress(addr) && V6MulticastScope(addr) == IPv6LinkLocalMulticastScope
|
||||
}
|
||||
|
||||
// AppendOpaqueInterfaceIdentifier appends a 64 bit opaque interface identifier
|
||||
// (IID) to buf as outlined by RFC 7217 and returns the extended buffer.
|
||||
//
|
||||
// The opaque IID is generated from the cryptographic hash of the concatenation
|
||||
// of the prefix, NIC's name, DAD counter (DAD retry counter) and the secret
|
||||
// key. The secret key SHOULD be at least OpaqueIIDSecretKeyMinBytes bytes and
|
||||
// MUST be generated to a pseudo-random number. See RFC 4086 for randomness
|
||||
// requirements for security.
|
||||
//
|
||||
// If buf has enough capacity for the IID (IIDSize bytes), a new underlying
|
||||
// array for the buffer will not be allocated.
|
||||
func AppendOpaqueInterfaceIdentifier(buf []byte, prefix tcpip.Subnet, nicName string, dadCounter uint8, secretKey []byte) []byte {
|
||||
// As per RFC 7217 section 5, the opaque identifier can be generated as a
|
||||
// cryptographic hash of the concatenation of each of the function parameters.
|
||||
// Note, we omit the optional Network_ID field.
|
||||
h := sha256.New()
|
||||
// h.Write never returns an error.
|
||||
prefixID := prefix.ID()
|
||||
h.Write([]byte(prefixID.AsSlice()[:IIDOffsetInIPv6Address]))
|
||||
h.Write([]byte(nicName))
|
||||
h.Write([]byte{dadCounter})
|
||||
h.Write(secretKey)
|
||||
|
||||
var sumBuf [sha256.Size]byte
|
||||
sum := h.Sum(sumBuf[:0])
|
||||
|
||||
return append(buf, sum[:IIDSize]...)
|
||||
}
|
||||
|
||||
// LinkLocalAddrWithOpaqueIID computes the default IPv6 link-local address with
|
||||
// an opaque IID.
|
||||
func LinkLocalAddrWithOpaqueIID(nicName string, dadCounter uint8, secretKey []byte) tcpip.Address {
|
||||
lladdrb := [IPv6AddressSize]byte{
|
||||
0: 0xFE,
|
||||
1: 0x80,
|
||||
}
|
||||
|
||||
return tcpip.AddrFrom16([16]byte(AppendOpaqueInterfaceIdentifier(lladdrb[:IIDOffsetInIPv6Address], IPv6LinkLocalPrefix.Subnet(), nicName, dadCounter, secretKey)))
|
||||
}
|
||||
|
||||
// IPv6AddressScope is the scope of an IPv6 address.
|
||||
type IPv6AddressScope int
|
||||
|
||||
const (
|
||||
// LinkLocalScope indicates a link-local address.
|
||||
LinkLocalScope IPv6AddressScope = iota
|
||||
|
||||
// GlobalScope indicates a global address.
|
||||
GlobalScope
|
||||
)
|
||||
|
||||
// ScopeForIPv6Address returns the scope for an IPv6 address.
|
||||
func ScopeForIPv6Address(addr tcpip.Address) (IPv6AddressScope, tcpip.Error) {
|
||||
if addr.BitLen() != IPv6AddressSizeBits {
|
||||
return GlobalScope, &tcpip.ErrBadAddress{}
|
||||
}
|
||||
|
||||
switch {
|
||||
case IsV6LinkLocalMulticastAddress(addr):
|
||||
return LinkLocalScope, nil
|
||||
|
||||
case IsV6LinkLocalUnicastAddress(addr):
|
||||
return LinkLocalScope, nil
|
||||
|
||||
default:
|
||||
return GlobalScope, nil
|
||||
}
|
||||
}
|
||||
|
||||
// InitialTempIID generates the initial temporary IID history value to generate
|
||||
// temporary SLAAC addresses with.
|
||||
//
|
||||
// Panics if initialTempIIDHistory is not at least IIDSize bytes.
|
||||
func InitialTempIID(initialTempIIDHistory []byte, seed []byte, nicID tcpip.NICID) {
|
||||
h := sha256.New()
|
||||
// h.Write never returns an error.
|
||||
h.Write(seed)
|
||||
var nicIDBuf [4]byte
|
||||
binary.BigEndian.PutUint32(nicIDBuf[:], uint32(nicID))
|
||||
h.Write(nicIDBuf[:])
|
||||
|
||||
var sumBuf [sha256.Size]byte
|
||||
sum := h.Sum(sumBuf[:0])
|
||||
|
||||
if n := copy(initialTempIIDHistory, sum[sha256.Size-IIDSize:]); n != IIDSize {
|
||||
panic(fmt.Sprintf("copied %d bytes, expected %d bytes", n, IIDSize))
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateTempIPv6SLAACAddr generates a temporary SLAAC IPv6 address for an
|
||||
// associated stable/permanent SLAAC address.
|
||||
//
|
||||
// GenerateTempIPv6SLAACAddr will update the temporary IID history value to be
|
||||
// used when generating a new temporary IID.
|
||||
//
|
||||
// Panics if tempIIDHistory is not at least IIDSize bytes.
|
||||
func GenerateTempIPv6SLAACAddr(tempIIDHistory []byte, stableAddr tcpip.Address) tcpip.AddressWithPrefix {
|
||||
addrBytes := stableAddr.As16()
|
||||
h := sha256.New()
|
||||
h.Write(tempIIDHistory)
|
||||
h.Write(addrBytes[IIDOffsetInIPv6Address:])
|
||||
var sumBuf [sha256.Size]byte
|
||||
sum := h.Sum(sumBuf[:0])
|
||||
|
||||
// The rightmost 64 bits of sum are saved for the next iteration.
|
||||
if n := copy(tempIIDHistory, sum[sha256.Size-IIDSize:]); n != IIDSize {
|
||||
panic(fmt.Sprintf("copied %d bytes, expected %d bytes", n, IIDSize))
|
||||
}
|
||||
|
||||
// The leftmost 64 bits of sum is used as the IID.
|
||||
if n := copy(addrBytes[IIDOffsetInIPv6Address:], sum); n != IIDSize {
|
||||
panic(fmt.Sprintf("copied %d IID bytes, expected %d bytes", n, IIDSize))
|
||||
}
|
||||
|
||||
return tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom16(addrBytes),
|
||||
PrefixLen: IIDOffsetInIPv6Address * 8,
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6MulticastScope is the scope of a multicast IPv6 address, as defined by
|
||||
// RFC 7346 section 2.
|
||||
type IPv6MulticastScope uint8
|
||||
|
||||
// The various values for IPv6 multicast scopes, as per RFC 7346 section 2:
|
||||
//
|
||||
// +------+--------------------------+-------------------------+
|
||||
// | scop | NAME | REFERENCE |
|
||||
// +------+--------------------------+-------------------------+
|
||||
// | 0 | Reserved | [RFC4291], RFC 7346 |
|
||||
// | 1 | Interface-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 2 | Link-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 3 | Realm-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 4 | Admin-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 5 | Site-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 6 | Unassigned | |
|
||||
// | 7 | Unassigned | |
|
||||
// | 8 | Organization-Local scope | [RFC4291], RFC 7346 |
|
||||
// | 9 | Unassigned | |
|
||||
// | A | Unassigned | |
|
||||
// | B | Unassigned | |
|
||||
// | C | Unassigned | |
|
||||
// | D | Unassigned | |
|
||||
// | E | Global scope | [RFC4291], RFC 7346 |
|
||||
// | F | Reserved | [RFC4291], RFC 7346 |
|
||||
// +------+--------------------------+-------------------------+
|
||||
const (
|
||||
IPv6Reserved0MulticastScope = IPv6MulticastScope(0x0)
|
||||
IPv6InterfaceLocalMulticastScope = IPv6MulticastScope(0x1)
|
||||
IPv6LinkLocalMulticastScope = IPv6MulticastScope(0x2)
|
||||
IPv6RealmLocalMulticastScope = IPv6MulticastScope(0x3)
|
||||
IPv6AdminLocalMulticastScope = IPv6MulticastScope(0x4)
|
||||
IPv6SiteLocalMulticastScope = IPv6MulticastScope(0x5)
|
||||
IPv6OrganizationLocalMulticastScope = IPv6MulticastScope(0x8)
|
||||
IPv6GlobalMulticastScope = IPv6MulticastScope(0xE)
|
||||
IPv6ReservedFMulticastScope = IPv6MulticastScope(0xF)
|
||||
)
|
||||
|
||||
// V6MulticastScope returns the scope of a multicast address.
|
||||
func V6MulticastScope(addr tcpip.Address) IPv6MulticastScope {
|
||||
addrBytes := addr.As16()
|
||||
return IPv6MulticastScope(addrBytes[ipv6MulticastAddressScopeByteIdx] & ipv6MulticastAddressScopeMask)
|
||||
}
|
||||
1010
pkg/tcpip/header/ipv6_extension_headers.go
Normal file
1010
pkg/tcpip/header/ipv6_extension_headers.go
Normal file
File diff suppressed because it is too large
Load diff
158
pkg/tcpip/header/ipv6_fragment.go
Normal file
158
pkg/tcpip/header/ipv6_fragment.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
nextHdrFrag = 0
|
||||
fragOff = 2
|
||||
more = 3
|
||||
idV6 = 4
|
||||
)
|
||||
|
||||
var _ IPv6SerializableExtHdr = (*IPv6SerializableFragmentExtHdr)(nil)
|
||||
|
||||
// IPv6SerializableFragmentExtHdr is used to serialize an IPv6 fragment
|
||||
// extension header as defined in RFC 8200 section 4.5.
|
||||
type IPv6SerializableFragmentExtHdr struct {
|
||||
// FragmentOffset is the "fragment offset" field of an IPv6 fragment.
|
||||
FragmentOffset uint16
|
||||
|
||||
// M is the "more" field of an IPv6 fragment.
|
||||
M bool
|
||||
|
||||
// Identification is the "identification" field of an IPv6 fragment.
|
||||
Identification uint32
|
||||
}
|
||||
|
||||
// identifier implements IPv6SerializableFragmentExtHdr.
|
||||
func (h *IPv6SerializableFragmentExtHdr) identifier() IPv6ExtensionHeaderIdentifier {
|
||||
return IPv6FragmentHeader
|
||||
}
|
||||
|
||||
// length implements IPv6SerializableFragmentExtHdr.
|
||||
func (h *IPv6SerializableFragmentExtHdr) length() int {
|
||||
return IPv6FragmentHeaderSize
|
||||
}
|
||||
|
||||
// serializeInto implements IPv6SerializableFragmentExtHdr.
|
||||
func (h *IPv6SerializableFragmentExtHdr) serializeInto(nextHeader uint8, b []byte) int {
|
||||
// Prevent too many bounds checks.
|
||||
_ = b[IPv6FragmentHeaderSize:]
|
||||
binary.BigEndian.PutUint32(b[idV6:], h.Identification)
|
||||
binary.BigEndian.PutUint16(b[fragOff:], h.FragmentOffset<<ipv6FragmentExtHdrFragmentOffsetShift)
|
||||
b[nextHdrFrag] = nextHeader
|
||||
if h.M {
|
||||
b[more] |= ipv6FragmentExtHdrMFlagMask
|
||||
}
|
||||
return IPv6FragmentHeaderSize
|
||||
}
|
||||
|
||||
// IPv6Fragment represents an ipv6 fragment header stored in a byte array.
|
||||
// Most of the methods of IPv6Fragment access to the underlying slice without
|
||||
// checking the boundaries and could panic because of 'index out of range'.
|
||||
// Always call IsValid() to validate an instance of IPv6Fragment before using other methods.
|
||||
type IPv6Fragment []byte
|
||||
|
||||
const (
|
||||
// IPv6FragmentHeader header is the number used to specify that the next
|
||||
// header is a fragment header, per RFC 2460.
|
||||
IPv6FragmentHeader = 44
|
||||
|
||||
// IPv6FragmentHeaderSize is the size of the fragment header.
|
||||
IPv6FragmentHeaderSize = 8
|
||||
)
|
||||
|
||||
// IsValid performs basic validation on the fragment header.
|
||||
func (b IPv6Fragment) IsValid() bool {
|
||||
return len(b) >= IPv6FragmentHeaderSize
|
||||
}
|
||||
|
||||
// NextHeader returns the value of the "next header" field of the ipv6 fragment.
|
||||
func (b IPv6Fragment) NextHeader() uint8 {
|
||||
return b[nextHdrFrag]
|
||||
}
|
||||
|
||||
// FragmentOffset returns the "fragment offset" field of the ipv6 fragment.
|
||||
func (b IPv6Fragment) FragmentOffset() uint16 {
|
||||
return binary.BigEndian.Uint16(b[fragOff:]) >> 3
|
||||
}
|
||||
|
||||
// More returns the "more" field of the ipv6 fragment.
|
||||
func (b IPv6Fragment) More() bool {
|
||||
return b[more]&1 > 0
|
||||
}
|
||||
|
||||
// Payload implements Network.Payload.
|
||||
func (b IPv6Fragment) Payload() []byte {
|
||||
return b[IPv6FragmentHeaderSize:]
|
||||
}
|
||||
|
||||
// ID returns the value of the identifier field of the ipv6 fragment.
|
||||
func (b IPv6Fragment) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(b[idV6:])
|
||||
}
|
||||
|
||||
// TransportProtocol implements Network.TransportProtocol.
|
||||
func (b IPv6Fragment) TransportProtocol() tcpip.TransportProtocolNumber {
|
||||
return tcpip.TransportProtocolNumber(b.NextHeader())
|
||||
}
|
||||
|
||||
// The functions below have been added only to satisfy the Network interface.
|
||||
|
||||
// Checksum is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) Checksum() uint16 {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// SourceAddress is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) SourceAddress() tcpip.Address {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// DestinationAddress is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) DestinationAddress() tcpip.Address {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// SetSourceAddress is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) SetSourceAddress(tcpip.Address) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// SetDestinationAddress is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) SetDestinationAddress(tcpip.Address) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// SetChecksum is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) SetChecksum(uint16) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// TOS is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) TOS() (uint8, uint32) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// SetTOS is not supported by IPv6Fragment.
|
||||
func (b IPv6Fragment) SetTOS(t uint8, l uint32) {
|
||||
panic("not supported")
|
||||
}
|
||||
103
pkg/tcpip/header/mld.go
Normal file
103
pkg/tcpip/header/mld.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2020 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
// MLDMinimumSize is the minimum size for an MLD message.
|
||||
MLDMinimumSize = 20
|
||||
|
||||
// MLDHopLimit is the Hop Limit for all IPv6 packets with an MLD message, as
|
||||
// per RFC 2710 section 3.
|
||||
MLDHopLimit = 1
|
||||
|
||||
// mldMaximumResponseDelayOffset is the offset to the Maximum Response Delay
|
||||
// field within MLD.
|
||||
mldMaximumResponseDelayOffset = 0
|
||||
|
||||
// mldMulticastAddressOffset is the offset to the Multicast Address field
|
||||
// within MLD.
|
||||
mldMulticastAddressOffset = 4
|
||||
)
|
||||
|
||||
// MLD is a Multicast Listener Discovery message in an ICMPv6 packet.
|
||||
//
|
||||
// MLD will only contain the body of an ICMPv6 packet.
|
||||
//
|
||||
// As per RFC 2710 section 3, MLD messages have the following format (MLD only
|
||||
// holds the bytes after the first four bytes in the diagram below):
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Maximum Response Delay | Reserved |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// + +
|
||||
// | |
|
||||
// + Multicast Address +
|
||||
// | |
|
||||
// + +
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLD []byte
|
||||
|
||||
// MaximumResponseDelay returns the Maximum Response Delay.
|
||||
func (m MLD) MaximumResponseDelay() time.Duration {
|
||||
// As per RFC 2710 section 3.4:
|
||||
//
|
||||
// The Maximum Response Delay field is meaningful only in Query
|
||||
// messages, and specifies the maximum allowed delay before sending a
|
||||
// responding Report, in units of milliseconds. In all other messages,
|
||||
// it is set to zero by the sender and ignored by receivers.
|
||||
return time.Duration(binary.BigEndian.Uint16(m[mldMaximumResponseDelayOffset:])) * time.Millisecond
|
||||
}
|
||||
|
||||
// SetMaximumResponseDelay sets the Maximum Response Delay field.
|
||||
//
|
||||
// maxRespDelayMS is the value in milliseconds.
|
||||
func (m MLD) SetMaximumResponseDelay(maxRespDelayMS uint16) {
|
||||
binary.BigEndian.PutUint16(m[mldMaximumResponseDelayOffset:], maxRespDelayMS)
|
||||
}
|
||||
|
||||
// MulticastAddress returns the Multicast Address.
|
||||
func (m MLD) MulticastAddress() tcpip.Address {
|
||||
// As per RFC 2710 section 3.5:
|
||||
//
|
||||
// In a Query message, the Multicast Address field is set to zero when
|
||||
// sending a General Query, and set to a specific IPv6 multicast address
|
||||
// when sending a Multicast-Address-Specific Query.
|
||||
//
|
||||
// In a Report or Done message, the Multicast Address field holds a
|
||||
// specific IPv6 multicast address to which the message sender is
|
||||
// listening or is ceasing to listen, respectively.
|
||||
return tcpip.AddrFrom16([16]byte(m[mldMulticastAddressOffset:][:IPv6AddressSize]))
|
||||
}
|
||||
|
||||
// SetMulticastAddress sets the Multicast Address field.
|
||||
func (m MLD) SetMulticastAddress(multicastAddress tcpip.Address) {
|
||||
if n := copy(m[mldMulticastAddressOffset:], multicastAddress.AsSlice()); n != IPv6AddressSize {
|
||||
panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, IPv6AddressSize))
|
||||
}
|
||||
}
|
||||
539
pkg/tcpip/header/mldv2.go
Normal file
539
pkg/tcpip/header/mldv2.go
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
// Copyright 2022 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 header
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
// MLDv2QueryMinimumSize is the minimum size for an MLDv2 message.
|
||||
MLDv2QueryMinimumSize = 24
|
||||
|
||||
mldv2QueryMaximumResponseCodeOffset = 0
|
||||
mldv2QueryResvSQRVOffset = 20
|
||||
mldv2QueryQRVMask = 0b111
|
||||
mldv2QueryQQICOffset = 21
|
||||
// mldv2QueryNumberOfSourcesOffset is the offset to the Number of Sources
|
||||
// field within MLDv2Query.
|
||||
mldv2QueryNumberOfSourcesOffset = 22
|
||||
|
||||
// MLDv2ReportMinimumSize is the minimum size of an MLDv2 report.
|
||||
MLDv2ReportMinimumSize = 24
|
||||
|
||||
// mldv2QuerySourcesOffset is the offset to the Sources field within
|
||||
// MLDv2Query.
|
||||
mldv2QuerySourcesOffset = 24
|
||||
)
|
||||
|
||||
// MLDv2RoutersAddress is the address to send MLDv2 reports to.
|
||||
//
|
||||
// As per RFC 3810 section 5.2.14,
|
||||
//
|
||||
// Version 2 Multicast Listener Reports are sent with an IP destination
|
||||
// address of FF02:0:0:0:0:0:0:16, to which all MLDv2-capable multicast
|
||||
// routers listen (see section 11 for IANA considerations related to
|
||||
// this special destination address).
|
||||
var MLDv2RoutersAddress = tcpip.AddrFrom16([16]byte{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16})
|
||||
|
||||
// MLDv2Query is a Multicast Listener Discovery Version 2 Query message in an
|
||||
// ICMPv6 packet.
|
||||
//
|
||||
// MLDv2Query will only contain the body of an ICMPv6 packet.
|
||||
//
|
||||
// As per RFC 3810 section 5.1, MLDv2 Query messages have the following format
|
||||
// (MLDv2Query only holds the bytes after the first four bytes in the diagram
|
||||
// below):
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 130 | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Maximum Response Code | Reserved |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Resv |S| QRV | QQIC | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- . -+
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLDv2Query MLD
|
||||
|
||||
// MaximumResponseCode returns the Maximum Response Code
|
||||
func (m MLDv2Query) MaximumResponseCode() uint16 {
|
||||
return binary.BigEndian.Uint16(m[mldv2QueryMaximumResponseCodeOffset:])
|
||||
}
|
||||
|
||||
// MLDv2MaximumResponseDelay returns the Maximum Response Delay in an MLDv2
|
||||
// Maximum Response Code.
|
||||
//
|
||||
// As per RFC 3810 section 5.1.3,
|
||||
//
|
||||
// The Maximum Response Code field specifies the maximum time allowed
|
||||
// before sending a responding Report. The actual time allowed, called
|
||||
// the Maximum Response Delay, is represented in units of milliseconds,
|
||||
// and is derived from the Maximum Response Code as follows:
|
||||
//
|
||||
// If Maximum Response Code < 32768,
|
||||
// Maximum Response Delay = Maximum Response Code
|
||||
//
|
||||
// If Maximum Response Code >=32768, Maximum Response Code represents a
|
||||
// floating-point value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Maximum Response Delay = (mant | 0x1000) << (exp+3)
|
||||
//
|
||||
// Small values of Maximum Response Delay allow MLDv2 routers to tune
|
||||
// the "leave latency" (the time between the moment the last node on a
|
||||
// link ceases to listen to a specific multicast address and the moment
|
||||
// the routing protocol is notified that there are no more listeners for
|
||||
// that address). Larger values, especially in the exponential range,
|
||||
// allow the tuning of the burstiness of MLD traffic on a link.
|
||||
func MLDv2MaximumResponseDelay(codeRaw uint16) time.Duration {
|
||||
code := time.Duration(codeRaw)
|
||||
if code < 32768 {
|
||||
return code * time.Millisecond
|
||||
}
|
||||
|
||||
const mantBits = 12
|
||||
const expMask = 0b111
|
||||
exp := (code >> mantBits) & expMask
|
||||
mant := code & ((1 << mantBits) - 1)
|
||||
return (mant | 0x1000) << (exp + 3) * time.Millisecond
|
||||
}
|
||||
|
||||
// MulticastAddress returns the Multicast Address.
|
||||
func (m MLDv2Query) MulticastAddress() tcpip.Address {
|
||||
// As per RFC 2710 section 3.5:
|
||||
//
|
||||
// In a Query message, the Multicast Address field is set to zero when
|
||||
// sending a General Query, and set to a specific IPv6 multicast address
|
||||
// when sending a Multicast-Address-Specific Query.
|
||||
//
|
||||
// In a Report or Done message, the Multicast Address field holds a
|
||||
// specific IPv6 multicast address to which the message sender is
|
||||
// listening or is ceasing to listen, respectively.
|
||||
return tcpip.AddrFrom16([16]byte(m[mldMulticastAddressOffset:][:IPv6AddressSize]))
|
||||
}
|
||||
|
||||
// QuerierRobustnessVariable returns the querier's robustness variable.
|
||||
func (m MLDv2Query) QuerierRobustnessVariable() uint8 {
|
||||
return m[mldv2QueryResvSQRVOffset] & mldv2QueryQRVMask
|
||||
}
|
||||
|
||||
// QuerierQueryInterval returns the querier's query interval.
|
||||
func (m MLDv2Query) QuerierQueryInterval() time.Duration {
|
||||
return mldv2AndIGMPv3QuerierQueryCodeToInterval(m[mldv2QueryQQICOffset])
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (m MLDv2Query) Sources() (AddressIterator, bool) {
|
||||
return makeAddressIterator(
|
||||
m[mldv2QuerySourcesOffset:],
|
||||
binary.BigEndian.Uint16(m[mldv2QueryNumberOfSourcesOffset:]),
|
||||
IPv6AddressSize,
|
||||
)
|
||||
}
|
||||
|
||||
// MLDv2ReportRecordType is the type of an MLDv2 multicast address record
|
||||
// found in an MLDv2 report, as per RFC 3810 section 5.2.12.
|
||||
type MLDv2ReportRecordType int
|
||||
|
||||
// MLDv2 multicast address record types, as per RFC 3810 section 5.2.12.
|
||||
const (
|
||||
MLDv2ReportRecordModeIsInclude MLDv2ReportRecordType = 1
|
||||
MLDv2ReportRecordModeIsExclude MLDv2ReportRecordType = 2
|
||||
MLDv2ReportRecordChangeToIncludeMode MLDv2ReportRecordType = 3
|
||||
MLDv2ReportRecordChangeToExcludeMode MLDv2ReportRecordType = 4
|
||||
MLDv2ReportRecordAllowNewSources MLDv2ReportRecordType = 5
|
||||
MLDv2ReportRecordBlockOldSources MLDv2ReportRecordType = 6
|
||||
)
|
||||
|
||||
const (
|
||||
mldv2ReportMulticastAddressRecordMinimumSize = 20
|
||||
mldv2ReportMulticastAddressRecordTypeOffset = 0
|
||||
mldv2ReportMulticastAddressRecordAuxDataLenOffset = 1
|
||||
mldv2ReportMulticastAddressRecordAuxDataLenUnits = 4
|
||||
mldv2ReportMulticastAddressRecordNumberOfSourcesOffset = 2
|
||||
mldv2ReportMulticastAddressRecordMulticastAddressOffset = 4
|
||||
mldv2ReportMulticastAddressRecordSourcesOffset = 20
|
||||
)
|
||||
|
||||
// MLDv2ReportMulticastAddressRecordSerializer is an MLDv2 Multicast Address
|
||||
// Record serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLDv2ReportMulticastAddressRecordSerializer struct {
|
||||
RecordType MLDv2ReportRecordType
|
||||
MulticastAddress tcpip.Address
|
||||
Sources []tcpip.Address
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *MLDv2ReportMulticastAddressRecordSerializer) Length() int {
|
||||
return mldv2ReportMulticastAddressRecordSourcesOffset + len(s.Sources)*IPv6AddressSize
|
||||
}
|
||||
|
||||
func copyIPv6Address(dst []byte, src tcpip.Address) {
|
||||
if n := copy(dst, src.AsSlice()); n != IPv6AddressSize {
|
||||
panic(fmt.Sprintf("got copy(...) = %d, want = %d", n, IPv6AddressSize))
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeInto serializes the record into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the record.
|
||||
func (s *MLDv2ReportMulticastAddressRecordSerializer) SerializeInto(b []byte) {
|
||||
b[mldv2ReportMulticastAddressRecordTypeOffset] = byte(s.RecordType)
|
||||
b[mldv2ReportMulticastAddressRecordAuxDataLenOffset] = 0
|
||||
binary.BigEndian.PutUint16(b[mldv2ReportMulticastAddressRecordNumberOfSourcesOffset:], uint16(len(s.Sources)))
|
||||
copyIPv6Address(b[mldv2ReportMulticastAddressRecordMulticastAddressOffset:], s.MulticastAddress)
|
||||
b = b[mldv2ReportMulticastAddressRecordSourcesOffset:]
|
||||
for _, source := range s.Sources {
|
||||
copyIPv6Address(b, source)
|
||||
b = b[IPv6AddressSize:]
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
mldv2ReportReservedOffset = 0
|
||||
mldv2ReportNumberOfMulticastAddressRecordsOffset = 2
|
||||
mldv2ReportMulticastAddressRecordsOffset = 4
|
||||
)
|
||||
|
||||
// MLDv2ReportSerializer is an MLD Version 2 Report serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLDv2ReportSerializer struct {
|
||||
Records []MLDv2ReportMulticastAddressRecordSerializer
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *MLDv2ReportSerializer) Length() int {
|
||||
ret := mldv2ReportMulticastAddressRecordsOffset
|
||||
for _, record := range s.Records {
|
||||
ret += record.Length()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SerializeInto serializes the report into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the report.
|
||||
func (s *MLDv2ReportSerializer) SerializeInto(b []byte) {
|
||||
binary.BigEndian.PutUint16(b[mldv2ReportReservedOffset:], 0)
|
||||
binary.BigEndian.PutUint16(b[mldv2ReportNumberOfMulticastAddressRecordsOffset:], uint16(len(s.Records)))
|
||||
b = b[mldv2ReportMulticastAddressRecordsOffset:]
|
||||
for _, record := range s.Records {
|
||||
len := record.Length()
|
||||
record.SerializeInto(b[:len])
|
||||
b = b[len:]
|
||||
}
|
||||
}
|
||||
|
||||
// MLDv2ReportMulticastAddressRecord is an MLDv2 record.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLDv2ReportMulticastAddressRecord []byte
|
||||
|
||||
// RecordType returns the type of this record.
|
||||
func (r MLDv2ReportMulticastAddressRecord) RecordType() MLDv2ReportRecordType {
|
||||
return MLDv2ReportRecordType(r[mldv2ReportMulticastAddressRecordTypeOffset])
|
||||
}
|
||||
|
||||
// AuxDataLen returns the length of the auxiliary data in this record.
|
||||
func (r MLDv2ReportMulticastAddressRecord) AuxDataLen() int {
|
||||
return int(r[mldv2ReportMulticastAddressRecordAuxDataLenOffset]) * mldv2ReportMulticastAddressRecordAuxDataLenUnits
|
||||
}
|
||||
|
||||
// numberOfSources returns the number of sources in this record.
|
||||
func (r MLDv2ReportMulticastAddressRecord) numberOfSources() uint16 {
|
||||
return binary.BigEndian.Uint16(r[mldv2ReportMulticastAddressRecordNumberOfSourcesOffset:])
|
||||
}
|
||||
|
||||
// MulticastAddress returns the multicast address this record targets.
|
||||
func (r MLDv2ReportMulticastAddressRecord) MulticastAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom16([16]byte(r[mldv2ReportMulticastAddressRecordMulticastAddressOffset:][:IPv6AddressSize]))
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (r MLDv2ReportMulticastAddressRecord) Sources() (AddressIterator, bool) {
|
||||
expectedLen := int(r.numberOfSources()) * IPv6AddressSize
|
||||
b := r[mldv2ReportMulticastAddressRecordSourcesOffset:]
|
||||
if len(b) < expectedLen {
|
||||
return AddressIterator{}, false
|
||||
}
|
||||
return AddressIterator{addressSize: IPv6AddressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true
|
||||
}
|
||||
|
||||
// MLDv2Report is an MLDv2 Report.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type MLDv2Report []byte
|
||||
|
||||
// MLDv2ReportMulticastAddressRecordIterator is an iterator over MLDv2 Multicast
|
||||
// Address Records.
|
||||
type MLDv2ReportMulticastAddressRecordIterator struct {
|
||||
recordsLeft uint16
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// MLDv2ReportMulticastAddressRecordIteratorNextDisposition is the possible
|
||||
// return values from MLDv2ReportMulticastAddressRecordIterator.Next.
|
||||
type MLDv2ReportMulticastAddressRecordIteratorNextDisposition int
|
||||
|
||||
const (
|
||||
// MLDv2ReportMulticastAddressRecordIteratorNextOk indicates that a multicast
|
||||
// address record was yielded.
|
||||
MLDv2ReportMulticastAddressRecordIteratorNextOk MLDv2ReportMulticastAddressRecordIteratorNextDisposition = iota
|
||||
|
||||
// MLDv2ReportMulticastAddressRecordIteratorNextDone indicates that the iterator
|
||||
// has been exhausted.
|
||||
MLDv2ReportMulticastAddressRecordIteratorNextDone
|
||||
|
||||
// MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort indicates
|
||||
// that the iterator expected another record, but the buffer ended
|
||||
// prematurely.
|
||||
MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort
|
||||
)
|
||||
|
||||
// Next returns the next MLDv2 Multicast Address Record.
|
||||
func (it *MLDv2ReportMulticastAddressRecordIterator) Next() (MLDv2ReportMulticastAddressRecord, MLDv2ReportMulticastAddressRecordIteratorNextDisposition) {
|
||||
if it.recordsLeft == 0 {
|
||||
return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextDone
|
||||
}
|
||||
if it.buf.Len() < mldv2ReportMulticastAddressRecordMinimumSize {
|
||||
return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
|
||||
hdr := MLDv2ReportMulticastAddressRecord(it.buf.Bytes())
|
||||
expectedLen := mldv2ReportMulticastAddressRecordMinimumSize +
|
||||
int(hdr.AuxDataLen()) + int(hdr.numberOfSources())*IPv6AddressSize
|
||||
|
||||
bytes := it.buf.Next(expectedLen)
|
||||
if len(bytes) < expectedLen {
|
||||
return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
it.recordsLeft--
|
||||
return MLDv2ReportMulticastAddressRecord(bytes), MLDv2ReportMulticastAddressRecordIteratorNextOk
|
||||
}
|
||||
|
||||
// MulticastAddressRecords returns an iterator of MLDv2 Multicast Address
|
||||
// Records.
|
||||
func (m MLDv2Report) MulticastAddressRecords() MLDv2ReportMulticastAddressRecordIterator {
|
||||
return MLDv2ReportMulticastAddressRecordIterator{
|
||||
recordsLeft: binary.BigEndian.Uint16(m[mldv2ReportNumberOfMulticastAddressRecordsOffset:]),
|
||||
buf: bytes.NewBuffer(m[mldv2ReportMulticastAddressRecordsOffset:]),
|
||||
}
|
||||
}
|
||||
124
pkg/tcpip/header/mldv2_igmpv3_common.go
Normal file
124
pkg/tcpip/header/mldv2_igmpv3_common.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2022 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 header
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
func mldv2AndIGMPv3QuerierQueryCodeToInterval(code uint8) time.Duration {
|
||||
// MLDv2: As per RFC 3810 section 5.1.19,
|
||||
//
|
||||
// The Querier's Query Interval Code field specifies the [Query
|
||||
// Interval] used by the Querier. The actual interval, called the
|
||||
// Querier's Query Interval (QQI), is represented in units of seconds,
|
||||
// and is derived from the Querier's Query Interval Code as follows:
|
||||
//
|
||||
// If QQIC < 128, QQI = QQIC
|
||||
//
|
||||
// If QQIC >= 128, QQIC represents a floating-point value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// QQI = (mant | 0x10) << (exp + 3)
|
||||
//
|
||||
// Multicast routers that are not the current Querier adopt the QQI
|
||||
// value from the most recently received Query as their own [Query
|
||||
// Interval] value, unless that most recently received QQI was zero, in
|
||||
// which case the receiving routers use the default [Query Interval]
|
||||
// value specified in section 9.2.
|
||||
//
|
||||
// IGMPv3: As per RFC 3376 section 4.1.7,
|
||||
//
|
||||
// The Querier's Query Interval Code field specifies the [Query
|
||||
// Interval] used by the querier. The actual interval, called the
|
||||
// Querier's Query Interval (QQI), is represented in units of seconds
|
||||
// and is derived from the Querier's Query Interval Code as follows:
|
||||
//
|
||||
// If QQIC < 128, QQI = QQIC
|
||||
//
|
||||
// If QQIC >= 128, QQIC represents a floating-point value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// QQI = (mant | 0x10) << (exp + 3)
|
||||
//
|
||||
// Multicast routers that are not the current querier adopt the QQI
|
||||
// value from the most recently received Query as their own [Query
|
||||
// Interval] value, unless that most recently received QQI was zero, in
|
||||
// which case the receiving routers use the default [Query Interval]
|
||||
// value specified in section 8.2.
|
||||
interval := time.Duration(code)
|
||||
if interval < 128 {
|
||||
return interval * time.Second
|
||||
}
|
||||
|
||||
const expMask = 0b111
|
||||
const mantBits = 4
|
||||
mant := interval & ((1 << mantBits) - 1)
|
||||
exp := (interval >> mantBits) & expMask
|
||||
return (mant | 0x10) << (exp + 3) * time.Second
|
||||
}
|
||||
|
||||
// MakeAddressIterator returns an AddressIterator.
|
||||
func MakeAddressIterator(addressSize int, buf *bytes.Buffer) AddressIterator {
|
||||
return AddressIterator{addressSize: addressSize, buf: buf}
|
||||
}
|
||||
|
||||
// AddressIterator is an iterator over IPv6 addresses.
|
||||
type AddressIterator struct {
|
||||
addressSize int
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// Done indicates that the iterator has been exhausted/has no more elements.
|
||||
func (it *AddressIterator) Done() bool {
|
||||
return it.buf.Len() == 0
|
||||
}
|
||||
|
||||
// Next returns the next address in the iterator.
|
||||
//
|
||||
// Returns false if the iterator has been exhausted.
|
||||
func (it *AddressIterator) Next() (tcpip.Address, bool) {
|
||||
if it.Done() {
|
||||
var emptyAddress tcpip.Address
|
||||
return emptyAddress, false
|
||||
}
|
||||
|
||||
b := it.buf.Next(it.addressSize)
|
||||
if len(b) != it.addressSize {
|
||||
panic(fmt.Sprintf("got len(buf.Next(%d)) = %d, want = %d", it.addressSize, len(b), it.addressSize))
|
||||
}
|
||||
|
||||
return tcpip.AddrFromSlice(b), true
|
||||
}
|
||||
|
||||
func makeAddressIterator(b []byte, expectedAddresses uint16, addressSize int) (AddressIterator, bool) {
|
||||
expectedLen := int(expectedAddresses) * addressSize
|
||||
if len(b) < expectedLen {
|
||||
return AddressIterator{}, false
|
||||
}
|
||||
return MakeAddressIterator(addressSize, bytes.NewBuffer(b[:expectedLen])), true
|
||||
}
|
||||
110
pkg/tcpip/header/ndp_neighbor_advert.go
Normal file
110
pkg/tcpip/header/ndp_neighbor_advert.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2019 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 header
|
||||
|
||||
import "github.com/sagernet/gvisor/pkg/tcpip"
|
||||
|
||||
// NDPNeighborAdvert is an NDP Neighbor Advertisement message. It will
|
||||
// only contain the body of an ICMPv6 packet.
|
||||
//
|
||||
// See RFC 4861 section 4.4 for more details.
|
||||
type NDPNeighborAdvert []byte
|
||||
|
||||
const (
|
||||
// NDPNAMinimumSize is the minimum size of a valid NDP Neighbor
|
||||
// Advertisement message (body of an ICMPv6 packet).
|
||||
NDPNAMinimumSize = 20
|
||||
|
||||
// ndpNATargetAddressOffset is the start of the Target Address
|
||||
// field within an NDPNeighborAdvert.
|
||||
ndpNATargetAddressOffset = 4
|
||||
|
||||
// ndpNAOptionsOffset is the start of the NDP options in an
|
||||
// NDPNeighborAdvert.
|
||||
ndpNAOptionsOffset = ndpNATargetAddressOffset + IPv6AddressSize
|
||||
|
||||
// ndpNAFlagsOffset is the offset of the flags within an
|
||||
// NDPNeighborAdvert
|
||||
ndpNAFlagsOffset = 0
|
||||
|
||||
// ndpNARouterFlagMask is the mask of the Router Flag field in
|
||||
// the flags byte within in an NDPNeighborAdvert.
|
||||
ndpNARouterFlagMask = (1 << 7)
|
||||
|
||||
// ndpNASolicitedFlagMask is the mask of the Solicited Flag field in
|
||||
// the flags byte within in an NDPNeighborAdvert.
|
||||
ndpNASolicitedFlagMask = (1 << 6)
|
||||
|
||||
// ndpNAOverrideFlagMask is the mask of the Override Flag field in
|
||||
// the flags byte within in an NDPNeighborAdvert.
|
||||
ndpNAOverrideFlagMask = (1 << 5)
|
||||
)
|
||||
|
||||
// TargetAddress returns the value within the Target Address field.
|
||||
func (b NDPNeighborAdvert) TargetAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom16Slice(b[ndpNATargetAddressOffset:][:IPv6AddressSize])
|
||||
}
|
||||
|
||||
// SetTargetAddress sets the value within the Target Address field.
|
||||
func (b NDPNeighborAdvert) SetTargetAddress(addr tcpip.Address) {
|
||||
copy(b[ndpNATargetAddressOffset:][:IPv6AddressSize], addr.AsSlice())
|
||||
}
|
||||
|
||||
// RouterFlag returns the value of the Router Flag field.
|
||||
func (b NDPNeighborAdvert) RouterFlag() bool {
|
||||
return b[ndpNAFlagsOffset]&ndpNARouterFlagMask != 0
|
||||
}
|
||||
|
||||
// SetRouterFlag sets the value in the Router Flag field.
|
||||
func (b NDPNeighborAdvert) SetRouterFlag(f bool) {
|
||||
if f {
|
||||
b[ndpNAFlagsOffset] |= ndpNARouterFlagMask
|
||||
} else {
|
||||
b[ndpNAFlagsOffset] &^= ndpNARouterFlagMask
|
||||
}
|
||||
}
|
||||
|
||||
// SolicitedFlag returns the value of the Solicited Flag field.
|
||||
func (b NDPNeighborAdvert) SolicitedFlag() bool {
|
||||
return b[ndpNAFlagsOffset]&ndpNASolicitedFlagMask != 0
|
||||
}
|
||||
|
||||
// SetSolicitedFlag sets the value in the Solicited Flag field.
|
||||
func (b NDPNeighborAdvert) SetSolicitedFlag(f bool) {
|
||||
if f {
|
||||
b[ndpNAFlagsOffset] |= ndpNASolicitedFlagMask
|
||||
} else {
|
||||
b[ndpNAFlagsOffset] &^= ndpNASolicitedFlagMask
|
||||
}
|
||||
}
|
||||
|
||||
// OverrideFlag returns the value of the Override Flag field.
|
||||
func (b NDPNeighborAdvert) OverrideFlag() bool {
|
||||
return b[ndpNAFlagsOffset]&ndpNAOverrideFlagMask != 0
|
||||
}
|
||||
|
||||
// SetOverrideFlag sets the value in the Override Flag field.
|
||||
func (b NDPNeighborAdvert) SetOverrideFlag(f bool) {
|
||||
if f {
|
||||
b[ndpNAFlagsOffset] |= ndpNAOverrideFlagMask
|
||||
} else {
|
||||
b[ndpNAFlagsOffset] &^= ndpNAOverrideFlagMask
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns an NDPOptions of the options body.
|
||||
func (b NDPNeighborAdvert) Options() NDPOptions {
|
||||
return NDPOptions(b[ndpNAOptionsOffset:])
|
||||
}
|
||||
52
pkg/tcpip/header/ndp_neighbor_solicit.go
Normal file
52
pkg/tcpip/header/ndp_neighbor_solicit.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2019 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 header
|
||||
|
||||
import "github.com/sagernet/gvisor/pkg/tcpip"
|
||||
|
||||
// NDPNeighborSolicit is an NDP Neighbor Solicitation message. It will only
|
||||
// contain the body of an ICMPv6 packet.
|
||||
//
|
||||
// See RFC 4861 section 4.3 for more details.
|
||||
type NDPNeighborSolicit []byte
|
||||
|
||||
const (
|
||||
// NDPNSMinimumSize is the minimum size of a valid NDP Neighbor
|
||||
// Solicitation message (body of an ICMPv6 packet).
|
||||
NDPNSMinimumSize = 20
|
||||
|
||||
// ndpNSTargetAddessOffset is the start of the Target Address
|
||||
// field within an NDPNeighborSolicit.
|
||||
ndpNSTargetAddessOffset = 4
|
||||
|
||||
// ndpNSOptionsOffset is the start of the NDP options in an
|
||||
// NDPNeighborSolicit.
|
||||
ndpNSOptionsOffset = ndpNSTargetAddessOffset + IPv6AddressSize
|
||||
)
|
||||
|
||||
// TargetAddress returns the value within the Target Address field.
|
||||
func (b NDPNeighborSolicit) TargetAddress() tcpip.Address {
|
||||
return tcpip.AddrFrom16Slice(b[ndpNSTargetAddessOffset:][:IPv6AddressSize])
|
||||
}
|
||||
|
||||
// SetTargetAddress sets the value within the Target Address field.
|
||||
func (b NDPNeighborSolicit) SetTargetAddress(addr tcpip.Address) {
|
||||
copy(b[ndpNSTargetAddessOffset:][:IPv6AddressSize], addr.AsSlice())
|
||||
}
|
||||
|
||||
// Options returns an NDPOptions of the options body.
|
||||
func (b NDPNeighborSolicit) Options() NDPOptions {
|
||||
return NDPOptions(b[ndpNSOptionsOffset:])
|
||||
}
|
||||
1072
pkg/tcpip/header/ndp_options.go
Normal file
1072
pkg/tcpip/header/ndp_options.go
Normal file
File diff suppressed because it is too large
Load diff
204
pkg/tcpip/header/ndp_router_advert.go
Normal file
204
pkg/tcpip/header/ndp_router_advert.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright 2019 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ fmt.Stringer = NDPRoutePreference(0)
|
||||
|
||||
// NDPRoutePreference is the preference values for default routers or
|
||||
// more-specific routes.
|
||||
//
|
||||
// As per RFC 4191 section 2.1,
|
||||
//
|
||||
// Default router preferences and preferences for more-specific routes
|
||||
// are encoded the same way.
|
||||
//
|
||||
// Preference values are encoded as a two-bit signed integer, as
|
||||
// follows:
|
||||
//
|
||||
// 01 High
|
||||
// 00 Medium (default)
|
||||
// 11 Low
|
||||
// 10 Reserved - MUST NOT be sent
|
||||
//
|
||||
// Note that implementations can treat the value as a two-bit signed
|
||||
// integer.
|
||||
//
|
||||
// Having just three values reinforces that they are not metrics and
|
||||
// more values do not appear to be necessary for reasonable scenarios.
|
||||
type NDPRoutePreference uint8
|
||||
|
||||
const (
|
||||
// HighRoutePreference indicates a high preference, as per
|
||||
// RFC 4191 section 2.1.
|
||||
HighRoutePreference NDPRoutePreference = 0b01
|
||||
|
||||
// MediumRoutePreference indicates a medium preference, as per
|
||||
// RFC 4191 section 2.1.
|
||||
//
|
||||
// This is the default preference value.
|
||||
MediumRoutePreference = 0b00
|
||||
|
||||
// LowRoutePreference indicates a low preference, as per
|
||||
// RFC 4191 section 2.1.
|
||||
LowRoutePreference = 0b11
|
||||
|
||||
// ReservedRoutePreference is a reserved preference value, as per
|
||||
// RFC 4191 section 2.1.
|
||||
//
|
||||
// It MUST NOT be sent.
|
||||
ReservedRoutePreference = 0b10
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (p NDPRoutePreference) String() string {
|
||||
switch p {
|
||||
case HighRoutePreference:
|
||||
return "HighRoutePreference"
|
||||
case MediumRoutePreference:
|
||||
return "MediumRoutePreference"
|
||||
case LowRoutePreference:
|
||||
return "LowRoutePreference"
|
||||
case ReservedRoutePreference:
|
||||
return "ReservedRoutePreference"
|
||||
default:
|
||||
return fmt.Sprintf("NDPRoutePreference(%d)", p)
|
||||
}
|
||||
}
|
||||
|
||||
// NDPRouterAdvert is an NDP Router Advertisement message. It will only contain
|
||||
// the body of an ICMPv6 packet.
|
||||
//
|
||||
// See RFC 4861 section 4.2 and RFC 4191 section 2.2 for more details.
|
||||
type NDPRouterAdvert []byte
|
||||
|
||||
// As per RFC 4191 section 2.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Cur Hop Limit |M|O|H|Prf|Resvd| Router Lifetime |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reachable Time |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Retrans Timer |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Options ...
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-
|
||||
const (
|
||||
// NDPRAMinimumSize is the minimum size of a valid NDP Router
|
||||
// Advertisement message (body of an ICMPv6 packet).
|
||||
NDPRAMinimumSize = 12
|
||||
|
||||
// ndpRACurrHopLimitOffset is the byte of the Curr Hop Limit field
|
||||
// within an NDPRouterAdvert.
|
||||
ndpRACurrHopLimitOffset = 0
|
||||
|
||||
// ndpRAFlagsOffset is the byte with the NDP RA bit-fields/flags
|
||||
// within an NDPRouterAdvert.
|
||||
ndpRAFlagsOffset = 1
|
||||
|
||||
// ndpRAManagedAddrConfFlagMask is the mask of the Managed Address
|
||||
// Configuration flag within the bit-field/flags byte of an
|
||||
// NDPRouterAdvert.
|
||||
ndpRAManagedAddrConfFlagMask = (1 << 7)
|
||||
|
||||
// ndpRAOtherConfFlagMask is the mask of the Other Configuration flag
|
||||
// within the bit-field/flags byte of an NDPRouterAdvert.
|
||||
ndpRAOtherConfFlagMask = (1 << 6)
|
||||
|
||||
// ndpDefaultRouterPreferenceShift is the shift of the Prf (Default Router
|
||||
// Preference) field within the flags byte of an NDPRouterAdvert.
|
||||
ndpDefaultRouterPreferenceShift = 3
|
||||
|
||||
// ndpDefaultRouterPreferenceMask is the mask of the Prf (Default Router
|
||||
// Preference) field within the flags byte of an NDPRouterAdvert.
|
||||
ndpDefaultRouterPreferenceMask = (0b11 << ndpDefaultRouterPreferenceShift)
|
||||
|
||||
// ndpRARouterLifetimeOffset is the start of the 2-byte Router Lifetime
|
||||
// field within an NDPRouterAdvert.
|
||||
ndpRARouterLifetimeOffset = 2
|
||||
|
||||
// ndpRAReachableTimeOffset is the start of the 4-byte Reachable Time
|
||||
// field within an NDPRouterAdvert.
|
||||
ndpRAReachableTimeOffset = 4
|
||||
|
||||
// ndpRARetransTimerOffset is the start of the 4-byte Retrans Timer
|
||||
// field within an NDPRouterAdvert.
|
||||
ndpRARetransTimerOffset = 8
|
||||
|
||||
// ndpRAOptionsOffset is the start of the NDP options in an
|
||||
// NDPRouterAdvert.
|
||||
ndpRAOptionsOffset = 12
|
||||
)
|
||||
|
||||
// CurrHopLimit returns the value of the Curr Hop Limit field.
|
||||
func (b NDPRouterAdvert) CurrHopLimit() uint8 {
|
||||
return b[ndpRACurrHopLimitOffset]
|
||||
}
|
||||
|
||||
// ManagedAddrConfFlag returns the value of the Managed Address Configuration
|
||||
// flag.
|
||||
func (b NDPRouterAdvert) ManagedAddrConfFlag() bool {
|
||||
return b[ndpRAFlagsOffset]&ndpRAManagedAddrConfFlagMask != 0
|
||||
}
|
||||
|
||||
// OtherConfFlag returns the value of the Other Configuration flag.
|
||||
func (b NDPRouterAdvert) OtherConfFlag() bool {
|
||||
return b[ndpRAFlagsOffset]&ndpRAOtherConfFlagMask != 0
|
||||
}
|
||||
|
||||
// DefaultRouterPreference returns the Default Router Preference field.
|
||||
func (b NDPRouterAdvert) DefaultRouterPreference() NDPRoutePreference {
|
||||
return NDPRoutePreference((b[ndpRAFlagsOffset] & ndpDefaultRouterPreferenceMask) >> ndpDefaultRouterPreferenceShift)
|
||||
}
|
||||
|
||||
// RouterLifetime returns the lifetime associated with the default router. A
|
||||
// value of 0 means the source of the Router Advertisement is not a default
|
||||
// router and SHOULD NOT appear on the default router list. Note, a value of 0
|
||||
// only means that the router should not be used as a default router, it does
|
||||
// not apply to other information contained in the Router Advertisement.
|
||||
func (b NDPRouterAdvert) RouterLifetime() time.Duration {
|
||||
// The field is the time in seconds, as per RFC 4861 section 4.2.
|
||||
return time.Second * time.Duration(binary.BigEndian.Uint16(b[ndpRARouterLifetimeOffset:]))
|
||||
}
|
||||
|
||||
// ReachableTime returns the time that a node assumes a neighbor is reachable
|
||||
// after having received a reachability confirmation. A value of 0 means
|
||||
// that it is unspecified by the source of the Router Advertisement message.
|
||||
func (b NDPRouterAdvert) ReachableTime() time.Duration {
|
||||
// The field is the time in milliseconds, as per RFC 4861 section 4.2.
|
||||
return time.Millisecond * time.Duration(binary.BigEndian.Uint32(b[ndpRAReachableTimeOffset:]))
|
||||
}
|
||||
|
||||
// RetransTimer returns the time between retransmitted Neighbor Solicitation
|
||||
// messages. A value of 0 means that it is unspecified by the source of the
|
||||
// Router Advertisement message.
|
||||
func (b NDPRouterAdvert) RetransTimer() time.Duration {
|
||||
// The field is the time in milliseconds, as per RFC 4861 section 4.2.
|
||||
return time.Millisecond * time.Duration(binary.BigEndian.Uint32(b[ndpRARetransTimerOffset:]))
|
||||
}
|
||||
|
||||
// Options returns an NDPOptions of the options body.
|
||||
func (b NDPRouterAdvert) Options() NDPOptions {
|
||||
return NDPOptions(b[ndpRAOptionsOffset:])
|
||||
}
|
||||
36
pkg/tcpip/header/ndp_router_solicit.go
Normal file
36
pkg/tcpip/header/ndp_router_solicit.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2019 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 header
|
||||
|
||||
// NDPRouterSolicit is an NDP Router Solicitation message. It will only contain
|
||||
// the body of an ICMPv6 packet.
|
||||
//
|
||||
// See RFC 4861 section 4.1 for more details.
|
||||
type NDPRouterSolicit []byte
|
||||
|
||||
const (
|
||||
// NDPRSMinimumSize is the minimum size of a valid NDP Router
|
||||
// Solicitation message (body of an ICMPv6 packet).
|
||||
NDPRSMinimumSize = 4
|
||||
|
||||
// ndpRSOptionsOffset is the start of the NDP options in an
|
||||
// NDPRouterSolicit.
|
||||
ndpRSOptionsOffset = 4
|
||||
)
|
||||
|
||||
// Options returns an NDPOptions of the options body.
|
||||
func (b NDPRouterSolicit) Options() NDPOptions {
|
||||
return NDPOptions(b[ndpRSOptionsOffset:])
|
||||
}
|
||||
56
pkg/tcpip/header/ndpoptionidentifier_string.go
Normal file
56
pkg/tcpip/header/ndpoptionidentifier_string.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2020 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.
|
||||
|
||||
// Code generated by "stringer -type ndpOptionIdentifier"; DO NOT EDIT.
|
||||
|
||||
package header
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ndpSourceLinkLayerAddressOptionType-1]
|
||||
_ = x[ndpTargetLinkLayerAddressOptionType-2]
|
||||
_ = x[ndpPrefixInformationType-3]
|
||||
_ = x[ndpNonceOptionType-14]
|
||||
_ = x[ndpRecursiveDNSServerOptionType-25]
|
||||
_ = x[ndpDNSSearchListOptionType-31]
|
||||
}
|
||||
|
||||
const (
|
||||
_ndpOptionIdentifier_name_0 = "ndpSourceLinkLayerAddressOptionTypendpTargetLinkLayerAddressOptionTypendpPrefixInformationType"
|
||||
_ndpOptionIdentifier_name_1 = "ndpNonceOptionType"
|
||||
_ndpOptionIdentifier_name_2 = "ndpRecursiveDNSServerOptionType"
|
||||
_ndpOptionIdentifier_name_3 = "ndpDNSSearchListOptionType"
|
||||
)
|
||||
|
||||
var _ndpOptionIdentifier_index_0 = [...]uint8{0, 35, 70, 94}
|
||||
|
||||
func (i ndpOptionIdentifier) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 3:
|
||||
i -= 1
|
||||
return _ndpOptionIdentifier_name_0[_ndpOptionIdentifier_index_0[i]:_ndpOptionIdentifier_index_0[i+1]]
|
||||
case i == 14:
|
||||
return _ndpOptionIdentifier_name_1
|
||||
case i == 25:
|
||||
return _ndpOptionIdentifier_name_2
|
||||
case i == 31:
|
||||
return _ndpOptionIdentifier_name_3
|
||||
default:
|
||||
return "ndpOptionIdentifier(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
243
pkg/tcpip/header/parse/parse.go
Normal file
243
pkg/tcpip/header/parse/parse.go
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
// Copyright 2020 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 parse provides utilities to parse packets.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// ARP populates pkt's network header with an ARP header found in
|
||||
// pkt.Data.
|
||||
//
|
||||
// Returns true if the header was successfully parsed.
|
||||
func ARP(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.NetworkHeader().Consume(header.ARPSize)
|
||||
if ok {
|
||||
pkt.NetworkProtocolNumber = header.ARPProtocolNumber
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// IPv4 parses an IPv4 packet found in pkt.Data and populates pkt's network
|
||||
// header with the IPv4 header.
|
||||
//
|
||||
// Returns true if the header was successfully parsed.
|
||||
func IPv4(pkt *stack.PacketBuffer) bool {
|
||||
hdr, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ipHdr := header.IPv4(hdr)
|
||||
|
||||
// Header may have options, determine the true header length.
|
||||
headerLen := int(ipHdr.HeaderLength())
|
||||
if headerLen < header.IPv4MinimumSize {
|
||||
// TODO(gvisor.dev/issue/2404): Per RFC 791, IHL needs to be at least 5 in
|
||||
// order for the packet to be valid. Figure out if we want to reject this
|
||||
// case.
|
||||
headerLen = header.IPv4MinimumSize
|
||||
}
|
||||
hdr, ok = pkt.NetworkHeader().Consume(headerLen)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ipHdr = header.IPv4(hdr)
|
||||
length := int(ipHdr.TotalLength()) - len(hdr)
|
||||
if length < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
|
||||
pkt.Data().CapLength(length)
|
||||
return true
|
||||
}
|
||||
|
||||
// IPv6 parses an IPv6 packet found in pkt.Data and populates pkt's network
|
||||
// header with the IPv6 header.
|
||||
func IPv6(pkt *stack.PacketBuffer) (proto tcpip.TransportProtocolNumber, fragID uint32, fragOffset uint16, fragMore bool, ok bool) {
|
||||
hdr, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
|
||||
if !ok {
|
||||
return 0, 0, 0, false, false
|
||||
}
|
||||
ipHdr := header.IPv6(hdr)
|
||||
|
||||
// Create a VV to parse the packet. We don't plan to modify anything here.
|
||||
// dataVV consists of:
|
||||
// - Any IPv6 header bytes after the first 40 (i.e. extensions).
|
||||
// - The transport header, if present.
|
||||
// - Any other payload data.
|
||||
dataBuf := pkt.Data().ToBuffer()
|
||||
dataBuf.TrimFront(header.IPv6MinimumSize)
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), dataBuf)
|
||||
defer it.Release()
|
||||
|
||||
// Iterate over the IPv6 extensions to find their length.
|
||||
var nextHdr tcpip.TransportProtocolNumber
|
||||
var extensionsSize int64
|
||||
|
||||
traverseExtensions:
|
||||
for {
|
||||
extHdr, done, err := it.Next()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// If we exhaust the extension list, the entire packet is the IPv6 header
|
||||
// and (possibly) extensions.
|
||||
if done {
|
||||
extensionsSize = dataBuf.Size()
|
||||
break
|
||||
}
|
||||
|
||||
switch extHdr := extHdr.(type) {
|
||||
case header.IPv6FragmentExtHdr:
|
||||
if extHdr.IsAtomic() {
|
||||
// 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
|
||||
}
|
||||
|
||||
if fragID == 0 && fragOffset == 0 && !fragMore {
|
||||
fragID = extHdr.ID()
|
||||
fragOffset = extHdr.FragmentOffset()
|
||||
fragMore = extHdr.More()
|
||||
}
|
||||
rawPayload := it.AsRawHeader(true /* consume */)
|
||||
extensionsSize = dataBuf.Size() - rawPayload.Buf.Size()
|
||||
rawPayload.Release()
|
||||
extHdr.Release()
|
||||
break traverseExtensions
|
||||
|
||||
case header.IPv6RawPayloadHeader:
|
||||
// We've found the payload after any extensions.
|
||||
extensionsSize = dataBuf.Size() - extHdr.Buf.Size()
|
||||
nextHdr = tcpip.TransportProtocolNumber(extHdr.Identifier)
|
||||
extHdr.Release()
|
||||
break traverseExtensions
|
||||
default:
|
||||
extHdr.Release()
|
||||
// Any other extension is a no-op, keep looping until we find the payload.
|
||||
}
|
||||
}
|
||||
|
||||
// Put the IPv6 header with extensions in pkt.NetworkHeader().
|
||||
hdr, ok = pkt.NetworkHeader().Consume(header.IPv6MinimumSize + int(extensionsSize))
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("pkt.Data should have at least %d bytes, but only has %d.", header.IPv6MinimumSize+extensionsSize, pkt.Data().Size()))
|
||||
}
|
||||
ipHdr = header.IPv6(hdr)
|
||||
pkt.Data().CapLength(int(ipHdr.PayloadLength()))
|
||||
pkt.NetworkProtocolNumber = header.IPv6ProtocolNumber
|
||||
|
||||
return nextHdr, fragID, fragOffset, fragMore, true
|
||||
}
|
||||
|
||||
// UDP parses a UDP packet found in pkt.Data and populates pkt's transport
|
||||
// header with the UDP header.
|
||||
//
|
||||
// Returns true if the header was successfully parsed.
|
||||
func UDP(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.TransportHeader().Consume(header.UDPMinimumSize)
|
||||
pkt.TransportProtocolNumber = header.UDPProtocolNumber
|
||||
return ok
|
||||
}
|
||||
|
||||
// TCP parses a TCP packet found in pkt.Data and populates pkt's transport
|
||||
// header with the TCP header.
|
||||
//
|
||||
// Returns true if the header was successfully parsed.
|
||||
func TCP(pkt *stack.PacketBuffer) bool {
|
||||
// TCP header is variable length, peek at it first.
|
||||
hdrLen := header.TCPMinimumSize
|
||||
hdr, ok := pkt.Data().PullUp(hdrLen)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the header has options, pull those up as well.
|
||||
if offset := int(header.TCP(hdr).DataOffset()); offset > header.TCPMinimumSize && offset <= pkt.Data().Size() {
|
||||
// TODO(gvisor.dev/issue/2404): Figure out whether to reject this kind of
|
||||
// packets.
|
||||
hdrLen = offset
|
||||
}
|
||||
|
||||
_, ok = pkt.TransportHeader().Consume(hdrLen)
|
||||
pkt.TransportProtocolNumber = header.TCPProtocolNumber
|
||||
return ok
|
||||
}
|
||||
|
||||
// ICMPv4 populates the packet buffer's transport header with an ICMPv4 header,
|
||||
// if present.
|
||||
//
|
||||
// Returns true if an ICMPv4 header was successfully parsed.
|
||||
func ICMPv4(pkt *stack.PacketBuffer) bool {
|
||||
if _, ok := pkt.TransportHeader().Consume(header.ICMPv4MinimumSize); ok {
|
||||
pkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ICMPv6 populates the packet buffer's transport header with an ICMPv4 header,
|
||||
// if present.
|
||||
//
|
||||
// Returns true if an ICMPv6 header was successfully parsed.
|
||||
func ICMPv6(pkt *stack.PacketBuffer) bool {
|
||||
hdr, ok := pkt.Data().PullUp(header.ICMPv6MinimumSize)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
h := header.ICMPv6(hdr)
|
||||
switch h.Type() {
|
||||
case header.ICMPv6RouterSolicit,
|
||||
header.ICMPv6RouterAdvert,
|
||||
header.ICMPv6NeighborSolicit,
|
||||
header.ICMPv6NeighborAdvert,
|
||||
header.ICMPv6RedirectMsg,
|
||||
header.ICMPv6MulticastListenerQuery,
|
||||
header.ICMPv6MulticastListenerReport,
|
||||
header.ICMPv6MulticastListenerV2Report,
|
||||
header.ICMPv6MulticastListenerDone:
|
||||
size := pkt.Data().Size()
|
||||
if _, ok := pkt.TransportHeader().Consume(size); !ok {
|
||||
panic(fmt.Sprintf("expected to consume the full data of size = %d bytes into transport header", size))
|
||||
}
|
||||
case header.ICMPv6DstUnreachable,
|
||||
header.ICMPv6PacketTooBig,
|
||||
header.ICMPv6TimeExceeded,
|
||||
header.ICMPv6ParamProblem,
|
||||
header.ICMPv6EchoRequest,
|
||||
header.ICMPv6EchoReply:
|
||||
fallthrough
|
||||
default:
|
||||
if _, ok := pkt.TransportHeader().Consume(header.ICMPv6MinimumSize); !ok {
|
||||
// Checked above if the packet buffer holds at least the minimum size for
|
||||
// an ICMPv6 packet.
|
||||
panic(fmt.Sprintf("expected to consume %d bytes", header.ICMPv6MinimumSize))
|
||||
}
|
||||
}
|
||||
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
|
||||
return true
|
||||
}
|
||||
3
pkg/tcpip/header/parse/parse_state_autogen.go
Normal file
3
pkg/tcpip/header/parse/parse_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package parse
|
||||
730
pkg/tcpip/header/tcp.go
Normal file
730
pkg/tcpip/header/tcp.go
Normal file
|
|
@ -0,0 +1,730 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/btree"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/seqnum"
|
||||
)
|
||||
|
||||
// These constants are the offsets of the respective fields in the TCP header.
|
||||
const (
|
||||
TCPSrcPortOffset = 0
|
||||
TCPDstPortOffset = 2
|
||||
TCPSeqNumOffset = 4
|
||||
TCPAckNumOffset = 8
|
||||
TCPDataOffset = 12
|
||||
TCPFlagsOffset = 13
|
||||
TCPWinSizeOffset = 14
|
||||
TCPChecksumOffset = 16
|
||||
TCPUrgentPtrOffset = 18
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxWndScale is maximum allowed window scaling, as described in
|
||||
// RFC 1323, section 2.3, page 11.
|
||||
MaxWndScale = 14
|
||||
|
||||
// TCPMaxSACKBlocks is the maximum number of SACK blocks that can
|
||||
// be encoded in a TCP option field.
|
||||
TCPMaxSACKBlocks = 4
|
||||
)
|
||||
|
||||
// TCPFlags is the dedicated type for TCP flags.
|
||||
type TCPFlags uint8
|
||||
|
||||
// Intersects returns true iff there are flags common to both f and o.
|
||||
func (f TCPFlags) Intersects(o TCPFlags) bool {
|
||||
return f&o != 0
|
||||
}
|
||||
|
||||
// Contains returns true iff all the flags in o are contained within f.
|
||||
func (f TCPFlags) Contains(o TCPFlags) bool {
|
||||
return f&o == o
|
||||
}
|
||||
|
||||
// String implements Stringer.String.
|
||||
func (f TCPFlags) String() string {
|
||||
flagsStr := []byte("FSRPAUEC")
|
||||
for i := range flagsStr {
|
||||
if f&(1<<uint(i)) == 0 {
|
||||
flagsStr[i] = ' '
|
||||
}
|
||||
}
|
||||
return string(flagsStr)
|
||||
}
|
||||
|
||||
// Flags that may be set in a TCP segment.
|
||||
const (
|
||||
TCPFlagFin TCPFlags = 1 << iota
|
||||
TCPFlagSyn
|
||||
TCPFlagRst
|
||||
TCPFlagPsh
|
||||
TCPFlagAck
|
||||
TCPFlagUrg
|
||||
TCPFlagEce
|
||||
TCPFlagCwr
|
||||
)
|
||||
|
||||
// Options that may be present in a TCP segment.
|
||||
const (
|
||||
TCPOptionEOL = 0
|
||||
TCPOptionNOP = 1
|
||||
TCPOptionMSS = 2
|
||||
TCPOptionWS = 3
|
||||
TCPOptionTS = 8
|
||||
TCPOptionSACKPermitted = 4
|
||||
TCPOptionSACK = 5
|
||||
)
|
||||
|
||||
// Option Lengths.
|
||||
const (
|
||||
TCPOptionMSSLength = 4
|
||||
TCPOptionTSLength = 10
|
||||
TCPOptionWSLength = 3
|
||||
TCPOptionSackPermittedLength = 2
|
||||
)
|
||||
|
||||
// TCPFields contains the fields of a TCP packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type TCPFields struct {
|
||||
// SrcPort is the "source port" field of a TCP packet.
|
||||
SrcPort uint16
|
||||
|
||||
// DstPort is the "destination port" field of a TCP packet.
|
||||
DstPort uint16
|
||||
|
||||
// SeqNum is the "sequence number" field of a TCP packet.
|
||||
SeqNum uint32
|
||||
|
||||
// AckNum is the "acknowledgement number" field of a TCP packet.
|
||||
AckNum uint32
|
||||
|
||||
// DataOffset is the "data offset" field of a TCP packet. It is the length of
|
||||
// the TCP header in bytes.
|
||||
DataOffset uint8
|
||||
|
||||
// Flags is the "flags" field of a TCP packet.
|
||||
Flags TCPFlags
|
||||
|
||||
// WindowSize is the "window size" field of a TCP packet.
|
||||
WindowSize uint16
|
||||
|
||||
// Checksum is the "checksum" field of a TCP packet.
|
||||
Checksum uint16
|
||||
|
||||
// UrgentPointer is the "urgent pointer" field of a TCP packet.
|
||||
UrgentPointer uint16
|
||||
}
|
||||
|
||||
// TCPSynOptions is used to return the parsed TCP Options in a syn
|
||||
// segment.
|
||||
//
|
||||
// +stateify savable
|
||||
type TCPSynOptions struct {
|
||||
// MSS is the maximum segment size provided by the peer in the SYN.
|
||||
MSS uint16
|
||||
|
||||
// WS is the window scale option provided by the peer in the SYN.
|
||||
//
|
||||
// Set to -1 if no window scale option was provided.
|
||||
WS int
|
||||
|
||||
// TS is true if the timestamp option was provided in the syn/syn-ack.
|
||||
TS bool
|
||||
|
||||
// TSVal is the value of the TSVal field in the timestamp option.
|
||||
TSVal uint32
|
||||
|
||||
// TSEcr is the value of the TSEcr field in the timestamp option.
|
||||
TSEcr uint32
|
||||
|
||||
// SACKPermitted is true if the SACK option was provided in the SYN/SYN-ACK.
|
||||
SACKPermitted bool
|
||||
|
||||
// Flags if specified are set on the outgoing SYN. The SYN flag is
|
||||
// always set.
|
||||
Flags TCPFlags
|
||||
}
|
||||
|
||||
// SACKBlock represents a single contiguous SACK block.
|
||||
//
|
||||
// +stateify savable
|
||||
type SACKBlock struct {
|
||||
// Start indicates the lowest sequence number in the block.
|
||||
Start seqnum.Value
|
||||
|
||||
// End indicates the sequence number immediately following the last
|
||||
// sequence number of this block.
|
||||
End seqnum.Value
|
||||
}
|
||||
|
||||
// Less returns true if r.Start < b.Start.
|
||||
func (r SACKBlock) Less(b btree.Item) bool {
|
||||
return r.Start.LessThan(b.(SACKBlock).Start)
|
||||
}
|
||||
|
||||
// Contains returns true if b is completely contained in r.
|
||||
func (r SACKBlock) Contains(b SACKBlock) bool {
|
||||
return r.Start.LessThanEq(b.Start) && b.End.LessThanEq(r.End)
|
||||
}
|
||||
|
||||
// TCPOptions are used to parse and cache the TCP segment options for a non
|
||||
// syn/syn-ack segment.
|
||||
//
|
||||
// +stateify savable
|
||||
type TCPOptions struct {
|
||||
// TS is true if the TimeStamp option is enabled.
|
||||
TS bool
|
||||
|
||||
// TSVal is the value in the TSVal field of the segment.
|
||||
TSVal uint32
|
||||
|
||||
// TSEcr is the value in the TSEcr field of the segment.
|
||||
TSEcr uint32
|
||||
|
||||
// SACKBlocks are the SACK blocks specified in the segment.
|
||||
SACKBlocks []SACKBlock
|
||||
}
|
||||
|
||||
// TCP represents a TCP header stored in a byte array.
|
||||
type TCP []byte
|
||||
|
||||
const (
|
||||
// TCPMinimumSize is the minimum size of a valid TCP packet.
|
||||
TCPMinimumSize = 20
|
||||
|
||||
// TCPOptionsMaximumSize is the maximum size of TCP options.
|
||||
TCPOptionsMaximumSize = 40
|
||||
|
||||
// TCPHeaderMaximumSize is the maximum header size of a TCP packet.
|
||||
TCPHeaderMaximumSize = TCPMinimumSize + TCPOptionsMaximumSize
|
||||
|
||||
// TCPTotalHeaderMaximumSize is the maximum size of headers from all layers in
|
||||
// a TCP packet. It analogous to MAX_TCP_HEADER in Linux.
|
||||
//
|
||||
// TODO(b/319936470): Investigate why this needs to be at least 140 bytes. In
|
||||
// Linux this value is at least 160, but in theory we should be able to use
|
||||
// 138. In practice anything less than 140 starts to break GSO on gVNIC
|
||||
// hardware.
|
||||
TCPTotalHeaderMaximumSize = 160
|
||||
|
||||
// TCPProtocolNumber is TCP's transport protocol number.
|
||||
TCPProtocolNumber tcpip.TransportProtocolNumber = 6
|
||||
|
||||
// TCPMinimumMSS is the minimum acceptable value for MSS. This is the
|
||||
// same as the value TCP_MIN_MSS defined net/tcp.h.
|
||||
TCPMinimumMSS = IPv4MaximumHeaderSize + TCPHeaderMaximumSize + MinIPFragmentPayloadSize - IPv4MinimumSize - TCPMinimumSize
|
||||
|
||||
// TCPMinimumSendMSS is the minimum value for MSS in a sender. This is the
|
||||
// same as the value TCP_MIN_SND_MSS in net/tcp.h.
|
||||
TCPMinimumSendMSS = TCPOptionsMaximumSize + MinIPFragmentPayloadSize
|
||||
|
||||
// TCPMaximumMSS is the maximum acceptable value for MSS.
|
||||
TCPMaximumMSS = 0xffff
|
||||
|
||||
// TCPDefaultMSS is the MSS value that should be used if an MSS option
|
||||
// is not received from the peer. It's also the value returned by
|
||||
// TCP_MAXSEG option for a socket in an unconnected state.
|
||||
//
|
||||
// Per RFC 1122, page 85: "If an MSS option is not received at
|
||||
// connection setup, TCP MUST assume a default send MSS of 536."
|
||||
TCPDefaultMSS = 536
|
||||
)
|
||||
|
||||
// SourcePort returns the "source port" field of the TCP header.
|
||||
func (b TCP) SourcePort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPSrcPortOffset:])
|
||||
}
|
||||
|
||||
// DestinationPort returns the "destination port" field of the TCP header.
|
||||
func (b TCP) DestinationPort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPDstPortOffset:])
|
||||
}
|
||||
|
||||
// SequenceNumber returns the "sequence number" field of the TCP header.
|
||||
func (b TCP) SequenceNumber() uint32 {
|
||||
return binary.BigEndian.Uint32(b[TCPSeqNumOffset:])
|
||||
}
|
||||
|
||||
// AckNumber returns the "ack number" field of the TCP header.
|
||||
func (b TCP) AckNumber() uint32 {
|
||||
return binary.BigEndian.Uint32(b[TCPAckNumOffset:])
|
||||
}
|
||||
|
||||
// DataOffset returns the "data offset" field of the TCP header. The return
|
||||
// value is the length of the TCP header in bytes.
|
||||
func (b TCP) DataOffset() uint8 {
|
||||
return (b[TCPDataOffset] >> 4) * 4
|
||||
}
|
||||
|
||||
// Payload returns the data in the TCP packet.
|
||||
func (b TCP) Payload() []byte {
|
||||
return b[b.DataOffset():]
|
||||
}
|
||||
|
||||
// Flags returns the flags field of the TCP header.
|
||||
func (b TCP) Flags() TCPFlags {
|
||||
return TCPFlags(b[TCPFlagsOffset])
|
||||
}
|
||||
|
||||
// WindowSize returns the "window size" field of the TCP header.
|
||||
func (b TCP) WindowSize() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPWinSizeOffset:])
|
||||
}
|
||||
|
||||
// Checksum returns the "checksum" field of the TCP header.
|
||||
func (b TCP) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPChecksumOffset:])
|
||||
}
|
||||
|
||||
// UrgentPointer returns the "urgent pointer" field of the TCP header.
|
||||
func (b TCP) UrgentPointer() uint16 {
|
||||
return binary.BigEndian.Uint16(b[TCPUrgentPtrOffset:])
|
||||
}
|
||||
|
||||
// SetSourcePort sets the "source port" field of the TCP header.
|
||||
func (b TCP) SetSourcePort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPSrcPortOffset:], port)
|
||||
}
|
||||
|
||||
// SetDestinationPort sets the "destination port" field of the TCP header.
|
||||
func (b TCP) SetDestinationPort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPDstPortOffset:], port)
|
||||
}
|
||||
|
||||
// SetChecksum sets the checksum field of the TCP header.
|
||||
func (b TCP) SetChecksum(xsum uint16) {
|
||||
checksum.Put(b[TCPChecksumOffset:], xsum)
|
||||
}
|
||||
|
||||
// SetDataOffset sets the data offset field of the TCP header. headerLen should
|
||||
// be the length of the TCP header in bytes.
|
||||
func (b TCP) SetDataOffset(headerLen uint8) {
|
||||
b[TCPDataOffset] = (headerLen / 4) << 4
|
||||
}
|
||||
|
||||
// SetSequenceNumber sets the sequence number field of the TCP header.
|
||||
func (b TCP) SetSequenceNumber(seqNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seqNum)
|
||||
}
|
||||
|
||||
// SetAckNumber sets the ack number field of the TCP header.
|
||||
func (b TCP) SetAckNumber(ackNum uint32) {
|
||||
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ackNum)
|
||||
}
|
||||
|
||||
// SetFlags sets the flags field of the TCP header.
|
||||
func (b TCP) SetFlags(flags uint8) {
|
||||
b[TCPFlagsOffset] = flags
|
||||
}
|
||||
|
||||
// SetWindowSize sets the window size field of the TCP header.
|
||||
func (b TCP) SetWindowSize(rcvwnd uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
|
||||
}
|
||||
|
||||
// SetUrgentPointer sets the window size field of the TCP header.
|
||||
func (b TCP) SetUrgentPointer(urgentPointer uint16) {
|
||||
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], urgentPointer)
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the TCP segment.
|
||||
// partialChecksum is the checksum of the network-layer pseudo-header
|
||||
// and the checksum of the segment data.
|
||||
func (b TCP) CalculateChecksum(partialChecksum uint16) uint16 {
|
||||
// Calculate the rest of the checksum.
|
||||
// return checksum.Checksum(b[:b.DataOffset()], partialChecksum)
|
||||
xsum := checksum.Checksum(b[:TCPChecksumOffset], partialChecksum)
|
||||
xsum = checksum.Checksum(b[TCPChecksumOffset+2:b.DataOffset()], xsum)
|
||||
return xsum
|
||||
}
|
||||
|
||||
// IsChecksumValid returns true iff the TCP header's checksum is valid.
|
||||
func (b TCP) IsChecksumValid(src, dst tcpip.Address, payloadChecksum, payloadLength uint16) bool {
|
||||
xsum := PseudoHeaderChecksum(TCPProtocolNumber, src, dst, uint16(b.DataOffset())+payloadLength)
|
||||
xsum = checksum.Combine(xsum, payloadChecksum)
|
||||
// return b.CalculateChecksum(xsum) == 0xffff
|
||||
return checksum.Checksum(b[:b.DataOffset()], xsum) == 0xffff
|
||||
}
|
||||
|
||||
// Options returns a slice that holds the unparsed TCP options in the segment.
|
||||
func (b TCP) Options() []byte {
|
||||
return b[TCPMinimumSize:b.DataOffset()]
|
||||
}
|
||||
|
||||
// ParsedOptions returns a TCPOptions structure which parses and caches the TCP
|
||||
// option values in the TCP segment. NOTE: Invoking this function repeatedly is
|
||||
// expensive as it reparses the options on each invocation.
|
||||
func (b TCP) ParsedOptions() TCPOptions {
|
||||
return ParseTCPOptions(b.Options())
|
||||
}
|
||||
|
||||
func (b TCP) encodeSubset(seq, ack uint32, flags TCPFlags, rcvwnd uint16) {
|
||||
binary.BigEndian.PutUint32(b[TCPSeqNumOffset:], seq)
|
||||
binary.BigEndian.PutUint32(b[TCPAckNumOffset:], ack)
|
||||
b[TCPFlagsOffset] = uint8(flags)
|
||||
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the TCP header.
|
||||
func (b TCP) Encode(t *TCPFields) {
|
||||
b.encodeSubset(t.SeqNum, t.AckNum, t.Flags, t.WindowSize)
|
||||
b.SetSourcePort(t.SrcPort)
|
||||
b.SetDestinationPort(t.DstPort)
|
||||
b.SetDataOffset(t.DataOffset)
|
||||
b.SetChecksum(t.Checksum)
|
||||
b.SetUrgentPointer(t.UrgentPointer)
|
||||
}
|
||||
|
||||
// EncodePartial updates a subset of the fields of the TCP header. It is useful
|
||||
// in cases when similar segments are produced.
|
||||
func (b TCP) EncodePartial(partialChecksum, length uint16, seqnum, acknum uint32, flags TCPFlags, rcvwnd uint16) {
|
||||
// Add the total length and "flags" field contributions to the checksum.
|
||||
// We don't use the flags field directly from the header because it's a
|
||||
// one-byte field with an odd offset, so it would be accounted for
|
||||
// incorrectly by the Checksum routine.
|
||||
tmp := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(tmp, length)
|
||||
binary.BigEndian.PutUint16(tmp[2:], uint16(flags))
|
||||
xsum := checksum.Checksum(tmp, partialChecksum)
|
||||
|
||||
// Encode the passed-in fields.
|
||||
b.encodeSubset(seqnum, acknum, flags, rcvwnd)
|
||||
|
||||
// Add the contributions of the passed-in fields to the checksum.
|
||||
xsum = checksum.Checksum(b[TCPSeqNumOffset:TCPSeqNumOffset+8], xsum)
|
||||
xsum = checksum.Checksum(b[TCPWinSizeOffset:TCPWinSizeOffset+2], xsum)
|
||||
|
||||
// Encode the checksum.
|
||||
b.SetChecksum(^xsum)
|
||||
}
|
||||
|
||||
// SetSourcePortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b TCP) SetSourcePortWithChecksumUpdate(new uint16) {
|
||||
old := b.SourcePort()
|
||||
b.SetSourcePort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// SetDestinationPortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b TCP) SetDestinationPortWithChecksumUpdate(new uint16) {
|
||||
old := b.DestinationPort()
|
||||
b.SetDestinationPort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress implements ChecksummableTransport.
|
||||
func (b TCP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool) {
|
||||
xsum := b.Checksum()
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
xsum = checksumUpdate2ByteAlignedAddress(xsum, old, new)
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
b.SetChecksum(xsum)
|
||||
}
|
||||
|
||||
// ParseSynOptions parses the options received in a SYN segment and returns the
|
||||
// relevant ones. opts should point to the option part of the TCP header.
|
||||
func ParseSynOptions(opts []byte, isAck bool) TCPSynOptions {
|
||||
limit := len(opts)
|
||||
|
||||
synOpts := TCPSynOptions{
|
||||
// Per RFC 1122, page 85: "If an MSS option is not received at
|
||||
// connection setup, TCP MUST assume a default send MSS of 536."
|
||||
MSS: TCPDefaultMSS,
|
||||
// If no window scale option is specified, WS in options is
|
||||
// returned as -1; this is because the absence of the option
|
||||
// indicates that the we cannot use window scaling on the
|
||||
// receive end either.
|
||||
WS: -1,
|
||||
}
|
||||
|
||||
for i := 0; i < limit; {
|
||||
switch opts[i] {
|
||||
case TCPOptionEOL:
|
||||
i = limit
|
||||
case TCPOptionNOP:
|
||||
i++
|
||||
case TCPOptionMSS:
|
||||
if i+4 > limit || opts[i+1] != 4 {
|
||||
return synOpts
|
||||
}
|
||||
mss := uint16(opts[i+2])<<8 | uint16(opts[i+3])
|
||||
if mss == 0 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.MSS = mss
|
||||
if mss < TCPMinimumSendMSS {
|
||||
synOpts.MSS = TCPMinimumSendMSS
|
||||
}
|
||||
i += 4
|
||||
|
||||
case TCPOptionWS:
|
||||
if i+3 > limit || opts[i+1] != 3 {
|
||||
return synOpts
|
||||
}
|
||||
ws := int(opts[i+2])
|
||||
if ws > MaxWndScale {
|
||||
ws = MaxWndScale
|
||||
}
|
||||
synOpts.WS = ws
|
||||
i += 3
|
||||
|
||||
case TCPOptionTS:
|
||||
if i+10 > limit || opts[i+1] != 10 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.TSVal = binary.BigEndian.Uint32(opts[i+2:])
|
||||
if isAck {
|
||||
// If the segment is a SYN-ACK then store the Timestamp Echo Reply
|
||||
// in the segment.
|
||||
synOpts.TSEcr = binary.BigEndian.Uint32(opts[i+6:])
|
||||
}
|
||||
synOpts.TS = true
|
||||
i += 10
|
||||
case TCPOptionSACKPermitted:
|
||||
if i+2 > limit || opts[i+1] != 2 {
|
||||
return synOpts
|
||||
}
|
||||
synOpts.SACKPermitted = true
|
||||
i += 2
|
||||
|
||||
default:
|
||||
// We don't recognize this option, just skip over it.
|
||||
if i+2 > limit {
|
||||
return synOpts
|
||||
}
|
||||
l := int(opts[i+1])
|
||||
// If the length is incorrect or if l+i overflows the
|
||||
// total options length then return false.
|
||||
if l < 2 || i+l > limit {
|
||||
return synOpts
|
||||
}
|
||||
i += l
|
||||
}
|
||||
}
|
||||
|
||||
return synOpts
|
||||
}
|
||||
|
||||
// ParseTCPOptions extracts and stores all known options in the provided byte
|
||||
// slice in a TCPOptions structure.
|
||||
func ParseTCPOptions(b []byte) TCPOptions {
|
||||
opts := TCPOptions{}
|
||||
limit := len(b)
|
||||
for i := 0; i < limit; {
|
||||
switch b[i] {
|
||||
case TCPOptionEOL:
|
||||
i = limit
|
||||
case TCPOptionNOP:
|
||||
i++
|
||||
case TCPOptionTS:
|
||||
if i+10 > limit || (b[i+1] != 10) {
|
||||
return opts
|
||||
}
|
||||
opts.TS = true
|
||||
opts.TSVal = binary.BigEndian.Uint32(b[i+2:])
|
||||
opts.TSEcr = binary.BigEndian.Uint32(b[i+6:])
|
||||
i += 10
|
||||
case TCPOptionSACK:
|
||||
if i+2 > limit {
|
||||
// Malformed SACK block, just return and stop parsing.
|
||||
return opts
|
||||
}
|
||||
sackOptionLen := int(b[i+1])
|
||||
if i+sackOptionLen > limit || (sackOptionLen-2)%8 != 0 {
|
||||
// Malformed SACK block, just return and stop parsing.
|
||||
return opts
|
||||
}
|
||||
numBlocks := (sackOptionLen - 2) / 8
|
||||
opts.SACKBlocks = []SACKBlock{}
|
||||
for j := 0; j < numBlocks; j++ {
|
||||
start := binary.BigEndian.Uint32(b[i+2+j*8:])
|
||||
end := binary.BigEndian.Uint32(b[i+2+j*8+4:])
|
||||
opts.SACKBlocks = append(opts.SACKBlocks, SACKBlock{
|
||||
Start: seqnum.Value(start),
|
||||
End: seqnum.Value(end),
|
||||
})
|
||||
}
|
||||
i += sackOptionLen
|
||||
default:
|
||||
// We don't recognize this option, just skip over it.
|
||||
if i+2 > limit {
|
||||
return opts
|
||||
}
|
||||
l := int(b[i+1])
|
||||
// If the length is incorrect or if l+i overflows the
|
||||
// total options length then return false.
|
||||
if l < 2 || i+l > limit {
|
||||
return opts
|
||||
}
|
||||
i += l
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// EncodeMSSOption encodes the MSS TCP option with the provided MSS values in
|
||||
// the supplied buffer. If the provided buffer is not large enough then it just
|
||||
// returns without encoding anything. It returns the number of bytes written to
|
||||
// the provided buffer.
|
||||
func EncodeMSSOption(mss uint32, b []byte) int {
|
||||
if len(b) < TCPOptionMSSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1], b[2], b[3] = TCPOptionMSS, TCPOptionMSSLength, byte(mss>>8), byte(mss)
|
||||
return TCPOptionMSSLength
|
||||
}
|
||||
|
||||
// EncodeWSOption encodes the WS TCP option with the WS value in the
|
||||
// provided buffer. If the provided buffer is not large enough then it just
|
||||
// returns without encoding anything. It returns the number of bytes written to
|
||||
// the provided buffer.
|
||||
func EncodeWSOption(ws int, b []byte) int {
|
||||
if len(b) < TCPOptionWSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1], b[2] = TCPOptionWS, TCPOptionWSLength, uint8(ws)
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeTSOption encodes the provided tsVal and tsEcr values as a TCP timestamp
|
||||
// option into the provided buffer. If the buffer is smaller than expected it
|
||||
// just returns without encoding anything. It returns the number of bytes
|
||||
// written to the provided buffer.
|
||||
func EncodeTSOption(tsVal, tsEcr uint32, b []byte) int {
|
||||
if len(b) < TCPOptionTSLength {
|
||||
return 0
|
||||
}
|
||||
b[0], b[1] = TCPOptionTS, TCPOptionTSLength
|
||||
binary.BigEndian.PutUint32(b[2:], tsVal)
|
||||
binary.BigEndian.PutUint32(b[6:], tsEcr)
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeSACKPermittedOption encodes a SACKPermitted option into the provided
|
||||
// buffer. If the buffer is smaller than required it just returns without
|
||||
// encoding anything. It returns the number of bytes written to the provided
|
||||
// buffer.
|
||||
func EncodeSACKPermittedOption(b []byte) int {
|
||||
if len(b) < TCPOptionSackPermittedLength {
|
||||
return 0
|
||||
}
|
||||
|
||||
b[0], b[1] = TCPOptionSACKPermitted, TCPOptionSackPermittedLength
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeSACKBlocks encodes the provided SACK blocks as a TCP SACK option block
|
||||
// in the provided slice. It tries to fit in as many blocks as possible based on
|
||||
// number of bytes available in the provided buffer. It returns the number of
|
||||
// bytes written to the provided buffer.
|
||||
func EncodeSACKBlocks(sackBlocks []SACKBlock, b []byte) int {
|
||||
if len(sackBlocks) == 0 {
|
||||
return 0
|
||||
}
|
||||
l := len(sackBlocks)
|
||||
if l > TCPMaxSACKBlocks {
|
||||
l = TCPMaxSACKBlocks
|
||||
}
|
||||
if ll := (len(b) - 2) / 8; ll < l {
|
||||
l = ll
|
||||
}
|
||||
if l == 0 {
|
||||
// There is not enough space in the provided buffer to add
|
||||
// any SACK blocks.
|
||||
return 0
|
||||
}
|
||||
b[0] = TCPOptionSACK
|
||||
b[1] = byte(l*8 + 2)
|
||||
for i := 0; i < l; i++ {
|
||||
binary.BigEndian.PutUint32(b[i*8+2:], uint32(sackBlocks[i].Start))
|
||||
binary.BigEndian.PutUint32(b[i*8+6:], uint32(sackBlocks[i].End))
|
||||
}
|
||||
return int(b[1])
|
||||
}
|
||||
|
||||
// EncodeNOP adds an explicit NOP to the option list.
|
||||
func EncodeNOP(b []byte) int {
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
b[0] = TCPOptionNOP
|
||||
return 1
|
||||
}
|
||||
|
||||
// AddTCPOptionPadding adds the required number of TCPOptionNOP to quad align
|
||||
// the option buffer. It adds padding bytes after the offset specified and
|
||||
// returns the number of padding bytes added. The passed in options slice
|
||||
// must have space for the padding bytes.
|
||||
func AddTCPOptionPadding(options []byte, offset int) int {
|
||||
paddingToAdd := -offset & 3
|
||||
// Now add any padding bytes that might be required to quad align the
|
||||
// options.
|
||||
for i := offset; i < offset+paddingToAdd; i++ {
|
||||
options[i] = TCPOptionNOP
|
||||
}
|
||||
return paddingToAdd
|
||||
}
|
||||
|
||||
// Acceptable checks if a segment that starts at segSeq and has length segLen is
|
||||
// "acceptable" for arriving in a receive window that starts at rcvNxt and ends
|
||||
// before rcvAcc, according to the table on page 26 and 69 of RFC 793.
|
||||
func Acceptable(segSeq seqnum.Value, segLen seqnum.Size, rcvNxt, rcvAcc seqnum.Value) bool {
|
||||
if rcvNxt == rcvAcc {
|
||||
return segLen == 0 && segSeq == rcvNxt
|
||||
}
|
||||
if segLen == 0 {
|
||||
// rcvWnd is incremented by 1 because that is Linux's behavior despite the
|
||||
// RFC.
|
||||
return segSeq.InRange(rcvNxt, rcvAcc.Add(1))
|
||||
}
|
||||
// Page 70 of RFC 793 allows packets that can be made "acceptable" by trimming
|
||||
// the payload, so we'll accept any payload that overlaps the receive window.
|
||||
// segSeq < rcvAcc is more correct according to RFC, however, Linux does it
|
||||
// differently, it uses segSeq <= rcvAcc, we'd want to keep the same behavior
|
||||
// as Linux.
|
||||
return rcvNxt.LessThan(segSeq.Add(segLen)) && segSeq.LessThanEq(rcvAcc)
|
||||
}
|
||||
|
||||
// TCPValid returns true if the pkt has a valid TCP header. It checks whether:
|
||||
// - The data offset is too small.
|
||||
// - The data offset is too large.
|
||||
// - The checksum is invalid.
|
||||
//
|
||||
// TCPValid corresponds to net/netfilter/nf_conntrack_proto_tcp.c:tcp_error.
|
||||
func TCPValid(hdr TCP, payloadChecksum func() uint16, payloadSize uint16, srcAddr, dstAddr tcpip.Address, skipChecksumValidation bool) (csum uint16, csumValid, ok bool) {
|
||||
if offset := int(hdr.DataOffset()); offset < TCPMinimumSize || offset > len(hdr) {
|
||||
return
|
||||
}
|
||||
|
||||
if skipChecksumValidation {
|
||||
csumValid = true
|
||||
} else {
|
||||
csum = hdr.Checksum()
|
||||
csumValid = hdr.IsChecksumValid(srcAddr, dstAddr, payloadChecksum(), payloadSize)
|
||||
}
|
||||
return csum, csumValid, true
|
||||
}
|
||||
199
pkg/tcpip/header/udp.go
Normal file
199
pkg/tcpip/header/udp.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// 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 header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
)
|
||||
|
||||
const (
|
||||
udpSrcPort = 0
|
||||
udpDstPort = 2
|
||||
udpLength = 4
|
||||
udpChecksum = 6
|
||||
)
|
||||
|
||||
const (
|
||||
// UDPMaximumPacketSize is the largest possible UDP packet.
|
||||
UDPMaximumPacketSize = 0xffff
|
||||
)
|
||||
|
||||
// UDPFields contains the fields of a UDP packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type UDPFields struct {
|
||||
// SrcPort is the "source port" field of a UDP packet.
|
||||
SrcPort uint16
|
||||
|
||||
// DstPort is the "destination port" field of a UDP packet.
|
||||
DstPort uint16
|
||||
|
||||
// Length is the "length" field of a UDP packet.
|
||||
Length uint16
|
||||
|
||||
// Checksum is the "checksum" field of a UDP packet.
|
||||
Checksum uint16
|
||||
}
|
||||
|
||||
// UDP represents a UDP header stored in a byte array.
|
||||
type UDP []byte
|
||||
|
||||
const (
|
||||
// UDPMinimumSize is the minimum size of a valid UDP packet.
|
||||
UDPMinimumSize = 8
|
||||
|
||||
// UDPMaximumSize is the maximum size of a valid UDP packet. The length field
|
||||
// in the UDP header is 16 bits as per RFC 768.
|
||||
UDPMaximumSize = math.MaxUint16
|
||||
|
||||
// UDPProtocolNumber is UDP's transport protocol number.
|
||||
UDPProtocolNumber tcpip.TransportProtocolNumber = 17
|
||||
)
|
||||
|
||||
// SourcePort returns the "source port" field of the UDP header.
|
||||
func (b UDP) SourcePort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpSrcPort:])
|
||||
}
|
||||
|
||||
// DestinationPort returns the "destination port" field of the UDP header.
|
||||
func (b UDP) DestinationPort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpDstPort:])
|
||||
}
|
||||
|
||||
// Length returns the "length" field of the UDP header.
|
||||
func (b UDP) Length() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpLength:])
|
||||
}
|
||||
|
||||
// Payload returns the data contained in the UDP datagram.
|
||||
func (b UDP) Payload() []byte {
|
||||
return b[UDPMinimumSize:]
|
||||
}
|
||||
|
||||
// Checksum returns the "checksum" field of the UDP header.
|
||||
func (b UDP) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpChecksum:])
|
||||
}
|
||||
|
||||
// SetSourcePort sets the "source port" field of the UDP header.
|
||||
func (b UDP) SetSourcePort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpSrcPort:], port)
|
||||
}
|
||||
|
||||
// SetDestinationPort sets the "destination port" field of the UDP header.
|
||||
func (b UDP) SetDestinationPort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpDstPort:], port)
|
||||
}
|
||||
|
||||
// SetChecksum sets the "checksum" field of the UDP header.
|
||||
func (b UDP) SetChecksum(xsum uint16) {
|
||||
checksum.Put(b[udpChecksum:], xsum)
|
||||
}
|
||||
|
||||
// SetLength sets the "length" field of the UDP header.
|
||||
func (b UDP) SetLength(length uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpLength:], length)
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the UDP packet, given the
|
||||
// checksum of the network-layer pseudo-header and the checksum of the payload.
|
||||
func (b UDP) CalculateChecksum(partialChecksum uint16) uint16 {
|
||||
// Calculate the rest of the checksum.
|
||||
// return checksum.Checksum(b[:UDPMinimumSize], partialChecksum)
|
||||
xsum := checksum.Checksum(b[:udpChecksum], partialChecksum)
|
||||
xsum = checksum.Checksum(b[udpChecksum+2:UDPMinimumSize], xsum)
|
||||
return xsum
|
||||
}
|
||||
|
||||
// IsChecksumValid returns true iff the UDP header's checksum is valid.
|
||||
func (b UDP) IsChecksumValid(src, dst tcpip.Address, payloadChecksum uint16) bool {
|
||||
xsum := PseudoHeaderChecksum(UDPProtocolNumber, dst, src, b.Length())
|
||||
xsum = checksum.Combine(xsum, payloadChecksum)
|
||||
// return b.CalculateChecksum(xsum) == 0xffff
|
||||
return checksum.Checksum(b[:UDPMinimumSize], xsum) == 0xffff
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the UDP header.
|
||||
func (b UDP) Encode(u *UDPFields) {
|
||||
b.SetSourcePort(u.SrcPort)
|
||||
b.SetDestinationPort(u.DstPort)
|
||||
b.SetLength(u.Length)
|
||||
b.SetChecksum(u.Checksum)
|
||||
}
|
||||
|
||||
// SetSourcePortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b UDP) SetSourcePortWithChecksumUpdate(new uint16) {
|
||||
old := b.SourcePort()
|
||||
b.SetSourcePort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// SetDestinationPortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b UDP) SetDestinationPortWithChecksumUpdate(new uint16) {
|
||||
old := b.DestinationPort()
|
||||
b.SetDestinationPort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
}
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress implements ChecksummableTransport.
|
||||
func (b UDP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool) {
|
||||
xsum := b.Checksum()
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
xsum = checksumUpdate2ByteAlignedAddress(xsum, old, new)
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
}
|
||||
|
||||
b.SetChecksum(xsum)
|
||||
}
|
||||
|
||||
// UDPValid returns true if the pkt has a valid UDP header. It checks whether:
|
||||
// - The length field is too small.
|
||||
// - The length field is too large.
|
||||
// - The checksum is invalid.
|
||||
//
|
||||
// UDPValid corresponds to net/netfilter/nf_conntrack_proto_udp.c:udp_error.
|
||||
func UDPValid(hdr UDP, payloadChecksum func() uint16, payloadSize uint16, netProto tcpip.NetworkProtocolNumber, srcAddr, dstAddr tcpip.Address, skipChecksumValidation bool) (lengthValid, csumValid bool) {
|
||||
if length := hdr.Length(); length > payloadSize+UDPMinimumSize || length < UDPMinimumSize {
|
||||
return false, false
|
||||
}
|
||||
|
||||
if skipChecksumValidation {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// On IPv4, UDP checksum is optional, and a zero value means the transmitter
|
||||
// omitted the checksum generation, as per RFC 768:
|
||||
//
|
||||
// An all zero transmitted checksum value means that the transmitter
|
||||
// generated no checksum (for debugging or for higher level protocols that
|
||||
// don't care).
|
||||
//
|
||||
// On IPv6, UDP checksum is not optional, as per RFC 2460 Section 8.1:
|
||||
//
|
||||
// Unlike IPv4, when UDP packets are originated by an IPv6 node, the UDP
|
||||
// checksum is not optional.
|
||||
if netProto == IPv4ProtocolNumber && hdr.Checksum() == 0 {
|
||||
return true, true
|
||||
}
|
||||
|
||||
return true, hdr.IsChecksumValid(srcAddr, dstAddr, payloadChecksum())
|
||||
}
|
||||
94
pkg/tcpip/header/virtionet.go
Normal file
94
pkg/tcpip/header/virtionet.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2021 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 header
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// These constants are declared in linux/virtio_net.h.
|
||||
const (
|
||||
_VIRTIO_NET_HDR_F_NEEDS_CSUM = 1
|
||||
_VIRTIO_NET_HDR_GSO_NONE = 0
|
||||
_VIRTIO_NET_HDR_GSO_TCPV4 = 1
|
||||
_VIRTIO_NET_HDR_GSO_TCPV6 = 4
|
||||
)
|
||||
|
||||
const (
|
||||
// VirtioNetHeaderSize is the size of VirtioNetHeader in bytes.
|
||||
VirtioNetHeaderSize = 10
|
||||
)
|
||||
|
||||
// Offsets for fields in the virtio net header.
|
||||
const (
|
||||
flags = 0
|
||||
gsoType = 1
|
||||
hdrLen = 2
|
||||
gsoSize = 4
|
||||
csumStart = 6
|
||||
csumOffset = 8
|
||||
)
|
||||
|
||||
// VirtioNetHeaderFields is the Go equivalent of the struct declared in
|
||||
// linux/virtio_net.h.
|
||||
type VirtioNetHeaderFields struct {
|
||||
Flags uint8
|
||||
GSOType uint8
|
||||
HdrLen uint16
|
||||
GSOSize uint16
|
||||
CSumStart uint16
|
||||
CSumOffset uint16
|
||||
}
|
||||
|
||||
// VirtioNetHeader represents a virtio net header stored in a byte array.
|
||||
type VirtioNetHeader []byte
|
||||
|
||||
// Flags returns the "flags" field of the virtio net header.
|
||||
func (v VirtioNetHeader) Flags() uint8 {
|
||||
return uint8(v[flags])
|
||||
}
|
||||
|
||||
// GSOType returns the "gsoType" field of the virtio net header.
|
||||
func (v VirtioNetHeader) GSOType() uint8 {
|
||||
return uint8(v[gsoType])
|
||||
}
|
||||
|
||||
// HdrLen returns the "hdrLen" field of the virtio net header.
|
||||
func (v VirtioNetHeader) HdrLen() uint16 {
|
||||
return binary.BigEndian.Uint16(v[hdrLen:])
|
||||
}
|
||||
|
||||
// GSOSize returns the "gsoSize" field of the virtio net header.
|
||||
func (v VirtioNetHeader) GSOSize() uint16 {
|
||||
return binary.BigEndian.Uint16(v[gsoSize:])
|
||||
}
|
||||
|
||||
// CSumStart returns the "csumStart" field of the virtio net header.
|
||||
func (v VirtioNetHeader) CSumStart() uint16 {
|
||||
return binary.BigEndian.Uint16(v[csumStart:])
|
||||
}
|
||||
|
||||
// CSumOffset returns the "csumOffset" field of the virtio net header.
|
||||
func (v VirtioNetHeader) CSumOffset() uint16 {
|
||||
return binary.BigEndian.Uint16(v[csumOffset:])
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the virtio net header.
|
||||
func (v VirtioNetHeader) Encode(f *VirtioNetHeaderFields) {
|
||||
v[flags] = uint8(f.Flags)
|
||||
v[gsoType] = uint8(f.GSOType)
|
||||
binary.LittleEndian.PutUint16(v[hdrLen:], f.HdrLen)
|
||||
binary.LittleEndian.PutUint16(v[gsoSize:], f.GSOSize)
|
||||
binary.LittleEndian.PutUint16(v[csumStart:], f.CSumStart)
|
||||
binary.LittleEndian.PutUint16(v[csumOffset:], f.CSumOffset)
|
||||
}
|
||||
48
pkg/tcpip/internal/tcp/tcp.go
Normal file
48
pkg/tcpip/internal/tcp/tcp.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2021 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 tcp contains internal type definitions that are not expected to be
|
||||
// used by anyone else outside pkg/tcpip.
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
// TSOffset is an offset applied to the value of the TSVal field in the TCP
|
||||
// Timestamp option.
|
||||
//
|
||||
// +stateify savable
|
||||
type TSOffset struct {
|
||||
milliseconds uint32
|
||||
}
|
||||
|
||||
// NewTSOffset creates a new TSOffset from milliseconds.
|
||||
func NewTSOffset(milliseconds uint32) TSOffset {
|
||||
return TSOffset{
|
||||
milliseconds: milliseconds,
|
||||
}
|
||||
}
|
||||
|
||||
// TSVal applies the offset to now and returns the timestamp in milliseconds.
|
||||
func (offset TSOffset) TSVal(now tcpip.MonotonicTime) uint32 {
|
||||
return uint32(now.Sub(tcpip.MonotonicTime{}).Milliseconds()) + offset.milliseconds
|
||||
}
|
||||
|
||||
// Elapsed calculates the elapsed time given now and the echoed back timestamp.
|
||||
func (offset TSOffset) Elapsed(now tcpip.MonotonicTime, tsEcr uint32) time.Duration {
|
||||
return time.Duration(offset.TSVal(now)-tsEcr) * time.Millisecond
|
||||
}
|
||||
38
pkg/tcpip/internal/tcp/tcp_state_autogen.go
Normal file
38
pkg/tcpip/internal/tcp/tcp_state_autogen.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (offset *TSOffset) StateTypeName() string {
|
||||
return "pkg/tcpip/internal/tcp.TSOffset"
|
||||
}
|
||||
|
||||
func (offset *TSOffset) StateFields() []string {
|
||||
return []string{
|
||||
"milliseconds",
|
||||
}
|
||||
}
|
||||
|
||||
func (offset *TSOffset) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (offset *TSOffset) StateSave(stateSinkObject state.Sink) {
|
||||
offset.beforeSave()
|
||||
stateSinkObject.Save(0, &offset.milliseconds)
|
||||
}
|
||||
|
||||
func (offset *TSOffset) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (offset *TSOffset) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &offset.milliseconds)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*TSOffset)(nil))
|
||||
}
|
||||
321
pkg/tcpip/link/channel/channel.go
Normal file
321
pkg/tcpip/link/channel/channel.go
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
// 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 channel provides the implementation of channel-based data-link layer
|
||||
// endpoints. Such endpoints allow injection of inbound packets and store
|
||||
// outbound packets in a channel.
|
||||
package channel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// Notification is the interface for receiving notification from the packet
|
||||
// queue.
|
||||
type Notification interface {
|
||||
// WriteNotify will be called when a write happens to the queue.
|
||||
WriteNotify()
|
||||
}
|
||||
|
||||
// NotificationHandle is an opaque handle to the registered notification target.
|
||||
// It can be used to unregister the notification when no longer interested.
|
||||
//
|
||||
// +stateify savable
|
||||
type NotificationHandle struct {
|
||||
n Notification
|
||||
}
|
||||
|
||||
type queue struct {
|
||||
// c is the outbound packet channel.
|
||||
c chan *stack.PacketBuffer
|
||||
mu queueRWMutex
|
||||
// +checklocks:mu
|
||||
notify []*NotificationHandle
|
||||
// +checklocks:mu
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (q *queue) Close() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if !q.closed {
|
||||
close(q.c)
|
||||
}
|
||||
q.closed = true
|
||||
}
|
||||
|
||||
func (q *queue) Read() *stack.PacketBuffer {
|
||||
select {
|
||||
case p := <-q.c:
|
||||
return p
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queue) ReadContext(ctx context.Context) *stack.PacketBuffer {
|
||||
select {
|
||||
case pkt := <-q.c:
|
||||
return pkt
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queue) Write(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
// q holds the PacketBuffer.
|
||||
q.mu.RLock()
|
||||
if q.closed {
|
||||
q.mu.RUnlock()
|
||||
return &tcpip.ErrClosedForSend{}
|
||||
}
|
||||
|
||||
wrote := false
|
||||
p := pkt.Clone()
|
||||
select {
|
||||
case q.c <- p:
|
||||
wrote = true
|
||||
default:
|
||||
p.DecRef()
|
||||
}
|
||||
notify := q.notify
|
||||
q.mu.RUnlock()
|
||||
|
||||
if wrote {
|
||||
// Send notification outside of lock.
|
||||
for _, h := range notify {
|
||||
h.n.WriteNotify()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &tcpip.ErrNoBufferSpace{}
|
||||
}
|
||||
|
||||
func (q *queue) Num() int {
|
||||
return len(q.c)
|
||||
}
|
||||
|
||||
func (q *queue) AddNotify(notify Notification) *NotificationHandle {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
h := &NotificationHandle{n: notify}
|
||||
q.notify = append(q.notify, h)
|
||||
return h
|
||||
}
|
||||
|
||||
func (q *queue) RemoveNotify(handle *NotificationHandle) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
// Make a copy, since we reads the array outside of lock when notifying.
|
||||
notify := make([]*NotificationHandle, 0, len(q.notify))
|
||||
for _, h := range q.notify {
|
||||
if h != handle {
|
||||
notify = append(notify, h)
|
||||
}
|
||||
}
|
||||
q.notify = notify
|
||||
}
|
||||
|
||||
var (
|
||||
_ stack.LinkEndpoint = (*Endpoint)(nil)
|
||||
_ stack.GSOEndpoint = (*Endpoint)(nil)
|
||||
)
|
||||
|
||||
// Endpoint is link layer endpoint that stores outbound packets in a channel
|
||||
// and allows injection of inbound packets.
|
||||
//
|
||||
// +stateify savable
|
||||
type Endpoint struct {
|
||||
LinkEPCapabilities stack.LinkEndpointCapabilities
|
||||
SupportedGSOKind stack.SupportedGSO
|
||||
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
dispatcher stack.NetworkDispatcher
|
||||
// +checklocks:mu
|
||||
linkAddr tcpip.LinkAddress
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
|
||||
// Outbound packet queue.
|
||||
q *queue
|
||||
}
|
||||
|
||||
// New creates a new channel endpoint.
|
||||
func New(size int, mtu uint32, linkAddr tcpip.LinkAddress) *Endpoint {
|
||||
return &Endpoint{
|
||||
q: &queue{
|
||||
c: make(chan *stack.PacketBuffer, size),
|
||||
},
|
||||
mtu: mtu,
|
||||
linkAddr: linkAddr,
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes e. Further packet injections will return an error, and all pending
|
||||
// packets are discarded. Close may be called concurrently with WritePackets.
|
||||
func (e *Endpoint) Close() {
|
||||
e.q.Close()
|
||||
e.Drain()
|
||||
}
|
||||
|
||||
// Read does non-blocking read one packet from the outbound packet queue.
|
||||
func (e *Endpoint) Read() *stack.PacketBuffer {
|
||||
return e.q.Read()
|
||||
}
|
||||
|
||||
// ReadContext does blocking read for one packet from the outbound packet queue.
|
||||
// It can be cancelled by ctx, and in this case, it returns nil.
|
||||
func (e *Endpoint) ReadContext(ctx context.Context) *stack.PacketBuffer {
|
||||
return e.q.ReadContext(ctx)
|
||||
}
|
||||
|
||||
// Drain removes all outbound packets from the channel and counts them.
|
||||
func (e *Endpoint) Drain() int {
|
||||
c := 0
|
||||
for pkt := e.Read(); pkt != nil; pkt = e.Read() {
|
||||
pkt.DecRef()
|
||||
c++
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NumQueued returns the number of packet queued for outbound.
|
||||
func (e *Endpoint) NumQueued() int {
|
||||
return e.q.Num()
|
||||
}
|
||||
|
||||
// InjectInbound injects an inbound packet. If the endpoint is not attached, the
|
||||
// packet is not delivered.
|
||||
func (e *Endpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
d := e.dispatcher
|
||||
e.mu.RUnlock()
|
||||
if d != nil {
|
||||
d.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach saves the stack network-layer dispatcher for use later when packets
|
||||
// are injected.
|
||||
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
||||
func (e *Endpoint) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.MTU.
|
||||
func (e *Endpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.SetMTU.
|
||||
func (e *Endpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
||||
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return e.LinkEPCapabilities
|
||||
}
|
||||
|
||||
// GSOMaxSize implements stack.GSOEndpoint.
|
||||
func (*Endpoint) GSOMaxSize() uint32 {
|
||||
return 1 << 15
|
||||
}
|
||||
|
||||
// SupportedGSO implements stack.GSOEndpoint.
|
||||
func (e *Endpoint) SupportedGSO() stack.SupportedGSO {
|
||||
return e.SupportedGSOKind
|
||||
}
|
||||
|
||||
// MaxHeaderLength returns the maximum size of the link layer header. Given 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 (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.linkAddr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *Endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.linkAddr = addr
|
||||
}
|
||||
|
||||
// WritePackets stores outbound packets into the channel.
|
||||
// Multiple concurrent calls are permitted.
|
||||
func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if err := e.q.Write(pkt); err != nil {
|
||||
if _, ok := err.(*tcpip.ErrNoBufferSpace); !ok && n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait.
|
||||
func (*Endpoint) Wait() {}
|
||||
|
||||
// AddNotify adds a notification target for receiving event about outgoing
|
||||
// packets.
|
||||
func (e *Endpoint) AddNotify(notify Notification) *NotificationHandle {
|
||||
return e.q.AddNotify(notify)
|
||||
}
|
||||
|
||||
// RemoveNotify removes handle from the list of notification targets.
|
||||
func (e *Endpoint) RemoveNotify(handle *NotificationHandle) {
|
||||
e.q.RemoveNotify(handle)
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
|
||||
func (*Endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (*Endpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (*Endpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.
|
||||
func (*Endpoint) SetOnCloseAction(func()) {}
|
||||
79
pkg/tcpip/link/channel/channel_state_autogen.go
Normal file
79
pkg/tcpip/link/channel/channel_state_autogen.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package channel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (n *NotificationHandle) StateTypeName() string {
|
||||
return "pkg/tcpip/link/channel.NotificationHandle"
|
||||
}
|
||||
|
||||
func (n *NotificationHandle) StateFields() []string {
|
||||
return []string{
|
||||
"n",
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NotificationHandle) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NotificationHandle) StateSave(stateSinkObject state.Sink) {
|
||||
n.beforeSave()
|
||||
stateSinkObject.Save(0, &n.n)
|
||||
}
|
||||
|
||||
func (n *NotificationHandle) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NotificationHandle) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &n.n)
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/channel.Endpoint"
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"LinkEPCapabilities",
|
||||
"SupportedGSOKind",
|
||||
"dispatcher",
|
||||
"linkAddr",
|
||||
"mtu",
|
||||
"q",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.LinkEPCapabilities)
|
||||
stateSinkObject.Save(1, &e.SupportedGSOKind)
|
||||
stateSinkObject.Save(2, &e.dispatcher)
|
||||
stateSinkObject.Save(3, &e.linkAddr)
|
||||
stateSinkObject.Save(4, &e.mtu)
|
||||
stateSinkObject.Save(5, &e.q)
|
||||
}
|
||||
|
||||
func (e *Endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.LinkEPCapabilities)
|
||||
stateSourceObject.Load(1, &e.SupportedGSOKind)
|
||||
stateSourceObject.Load(2, &e.dispatcher)
|
||||
stateSourceObject.Load(3, &e.linkAddr)
|
||||
stateSourceObject.Load(4, &e.mtu)
|
||||
stateSourceObject.Load(5, &e.q)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*NotificationHandle)(nil))
|
||||
state.Register((*Endpoint)(nil))
|
||||
}
|
||||
96
pkg/tcpip/link/channel/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/channel/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package channel
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
96
pkg/tcpip/link/channel/queue_mutex.go
Normal file
96
pkg/tcpip/link/channel/queue_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package channel
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type queueRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var queuelockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type queuelockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) Lock() {
|
||||
locking.AddGLock(queueprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) NestedLock(i queuelockNameIndex) {
|
||||
locking.AddGLock(queueprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(queueprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) NestedUnlock(i queuelockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(queueprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) RLock() {
|
||||
locking.AddGLock(queueprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(queueprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *queueRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var queueprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func queueinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
queueinitLockNames()
|
||||
queueprefixIndex = locking.NewMutexClass(reflect.TypeOf(queueRWMutex{}), queuelockNames)
|
||||
}
|
||||
121
pkg/tcpip/link/ethernet/ethernet.go
Normal file
121
pkg/tcpip/link/ethernet/ethernet.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2020 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 ethernet provides an implementation of an ethernet link endpoint that
|
||||
// wraps an inner link endpoint.
|
||||
package ethernet
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/nested"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var (
|
||||
_ stack.NetworkDispatcher = (*Endpoint)(nil)
|
||||
_ stack.LinkEndpoint = (*Endpoint)(nil)
|
||||
)
|
||||
|
||||
// New returns an ethernet link endpoint that wraps an inner link endpoint.
|
||||
func New(ep stack.LinkEndpoint) *Endpoint {
|
||||
var e Endpoint
|
||||
e.Endpoint.Init(ep, &e)
|
||||
return &e
|
||||
}
|
||||
|
||||
// Endpoint is an ethernet endpoint.
|
||||
//
|
||||
// It adds an ethernet header to packets before sending them out through its
|
||||
// inner link endpoint and consumes an ethernet header before sending the
|
||||
// packet to the stack.
|
||||
//
|
||||
// +stateify savable
|
||||
type Endpoint struct {
|
||||
nested.Endpoint
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
if l := e.Endpoint.LinkAddress(); len(l) != 0 {
|
||||
return l
|
||||
}
|
||||
return header.UnspecifiedEthernetAddress
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MTU() uint32 {
|
||||
return e.Endpoint.MTU()
|
||||
}
|
||||
|
||||
// DeliverNetworkPacket implements stack.NetworkDispatcher.
|
||||
func (e *Endpoint) DeliverNetworkPacket(_ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
if !e.ParseHeader(pkt) {
|
||||
return
|
||||
}
|
||||
eth := header.Ethernet(pkt.LinkHeader().Slice())
|
||||
dst := eth.DestinationAddress()
|
||||
if dst == header.EthernetBroadcastAddress {
|
||||
pkt.PktType = tcpip.PacketBroadcast
|
||||
} else if header.IsMulticastEthernetAddress(dst) {
|
||||
pkt.PktType = tcpip.PacketMulticast
|
||||
} else if dst == e.LinkAddress() {
|
||||
pkt.PktType = tcpip.PacketHost
|
||||
} else {
|
||||
pkt.PktType = tcpip.PacketOtherHost
|
||||
}
|
||||
|
||||
// Note, there is no need to check the destination link address here since
|
||||
// the ethernet hardware filters frames based on their destination addresses.
|
||||
e.Endpoint.DeliverNetworkPacket(eth.Type() /* protocol */, pkt)
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
c := e.Endpoint.Capabilities()
|
||||
if c&stack.CapabilityLoopback == 0 {
|
||||
c |= stack.CapabilityResolutionRequired
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MaxHeaderLength() uint16 {
|
||||
return header.EthernetMinimumSize + e.Endpoint.MaxHeaderLength()
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
if a := e.Endpoint.ARPHardwareType(); a != header.ARPHardwareNone {
|
||||
return a
|
||||
}
|
||||
return header.ARPHardwareEther
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.
|
||||
func (*Endpoint) AddHeader(pkt *stack.PacketBuffer) {
|
||||
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
|
||||
fields := header.EthernetFields{
|
||||
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
|
||||
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
|
||||
Type: pkt.NetworkProtocolNumber,
|
||||
}
|
||||
eth.Encode(&fields)
|
||||
}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.
|
||||
func (*Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize)
|
||||
return ok
|
||||
}
|
||||
38
pkg/tcpip/link/ethernet/ethernet_state_autogen.go
Normal file
38
pkg/tcpip/link/ethernet/ethernet_state_autogen.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package ethernet
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (e *Endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/ethernet.Endpoint"
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"Endpoint",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.Endpoint)
|
||||
}
|
||||
|
||||
func (e *Endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.Endpoint)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*Endpoint)(nil))
|
||||
}
|
||||
906
pkg/tcpip/link/fdbased/endpoint.go
Normal file
906
pkg/tcpip/link/fdbased/endpoint.go
Normal file
|
|
@ -0,0 +1,906 @@
|
|||
// 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// Package fdbased provides the implementation 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().
|
||||
//
|
||||
// FD based endpoints can use more than one file descriptor to read incoming
|
||||
// packets. If there are more than one FDs specified and the underlying FD is an
|
||||
// AF_PACKET then the endpoint will enable FANOUT mode on the socket so that the
|
||||
// host kernel will consistently hash the packets to the sockets. This ensures
|
||||
// that packets for the same TCP streams are not reordered.
|
||||
//
|
||||
// Similarly if more than one FD's are specified where the underlying FD is not
|
||||
// AF_PACKET then it's the caller's responsibility to ensure that all inbound
|
||||
// packets on the descriptors are consistently 5 tuple hashed to one of the
|
||||
// descriptors to prevent TCP reordering.
|
||||
//
|
||||
// Since netstack today does not compute 5 tuple hashes for outgoing packets we
|
||||
// only use the first FD to write outbound packets. Once 5 tuple hashes for
|
||||
// all outbound packets are available we will make use of all underlying FD's to
|
||||
// write outbound packets.
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// linkDispatcher reads packets from the link FD and dispatches them to the
|
||||
// NetworkDispatcher.
|
||||
type linkDispatcher interface {
|
||||
Stop()
|
||||
dispatch() (bool, tcpip.Error)
|
||||
release()
|
||||
}
|
||||
|
||||
// PacketDispatchMode are the various supported methods of receiving and
|
||||
// dispatching packets from the underlying FD.
|
||||
type PacketDispatchMode int
|
||||
|
||||
// BatchSize is the number of packets to write in each syscall. It is 47
|
||||
// because when GVisorGSO is in use then a single 65KB TCP segment can get
|
||||
// split into 46 segments of 1420 bytes and a single 216 byte segment.
|
||||
const BatchSize = 47
|
||||
|
||||
const (
|
||||
// Readv is the default dispatch mode and is the least performant of the
|
||||
// dispatch options but the one that is supported by all underlying FD
|
||||
// types.
|
||||
Readv PacketDispatchMode = iota
|
||||
// RecvMMsg enables use of recvmmsg() syscall instead of readv() to
|
||||
// read inbound packets. This reduces # of syscalls needed to process
|
||||
// packets.
|
||||
//
|
||||
// NOTE: recvmmsg() is only supported for sockets, so if the underlying
|
||||
// FD is not a socket then the code will still fall back to the readv()
|
||||
// path.
|
||||
RecvMMsg
|
||||
// PacketMMap enables use of PACKET_RX_RING to receive packets from the
|
||||
// NIC. PacketMMap requires that the underlying FD be an AF_PACKET. The
|
||||
// primary use-case for this is runsc which uses an AF_PACKET FD to
|
||||
// receive packets from the veth device.
|
||||
PacketMMap
|
||||
)
|
||||
|
||||
func (p PacketDispatchMode) String() string {
|
||||
switch p {
|
||||
case Readv:
|
||||
return "Readv"
|
||||
case RecvMMsg:
|
||||
return "RecvMMsg"
|
||||
case PacketMMap:
|
||||
return "PacketMMap"
|
||||
default:
|
||||
return fmt.Sprintf("unknown packet dispatch mode '%d'", p)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ stack.LinkEndpoint = (*endpoint)(nil)
|
||||
_ stack.GSOEndpoint = (*endpoint)(nil)
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type fdInfo struct {
|
||||
fd int
|
||||
isSocket bool
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type endpoint struct {
|
||||
// fds is the set of file descriptors each identifying one inbound/outbound
|
||||
// channel. The endpoint will dispatch from all inbound channels as well as
|
||||
// hash outbound packets to specific channels based on the packet hash.
|
||||
fds []fdInfo
|
||||
|
||||
// hdrSize specifies the link-layer header size. If set to 0, no header
|
||||
// is added/removed; otherwise an ethernet header is used.
|
||||
hdrSize int
|
||||
|
||||
// caps holds the endpoint capabilities.
|
||||
caps stack.LinkEndpointCapabilities
|
||||
|
||||
// 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) `state:"nosave"`
|
||||
|
||||
inboundDispatchers []linkDispatcher
|
||||
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
dispatcher stack.NetworkDispatcher
|
||||
|
||||
// packetDispatchMode controls the packet dispatcher used by this
|
||||
// endpoint.
|
||||
packetDispatchMode PacketDispatchMode
|
||||
|
||||
// gsoMaxSize is the maximum GSO packet size. It is zero if GSO is
|
||||
// disabled.
|
||||
gsoMaxSize uint32
|
||||
|
||||
// wg keeps track of running goroutines.
|
||||
wg sync.WaitGroup `state:"nosave"`
|
||||
|
||||
// gsoKind is the supported kind of GSO.
|
||||
gsoKind stack.SupportedGSO
|
||||
|
||||
// maxSyscallHeaderBytes has the same meaning as
|
||||
// Options.MaxSyscallHeaderBytes.
|
||||
maxSyscallHeaderBytes uintptr
|
||||
|
||||
// writevMaxIovs is the maximum number of iovecs that may be passed to
|
||||
// rawfile.NonBlockingWriteIovec, as possibly limited by
|
||||
// maxSyscallHeaderBytes. (No analogous limit is defined for
|
||||
// rawfile.NonBlockingSendMMsg, since in that case the maximum number of
|
||||
// iovecs also depends on the number of mmsghdrs. Instead, if sendBatch
|
||||
// encounters a packet whose iovec count is limited by
|
||||
// maxSyscallHeaderBytes, it falls back to writing the packet using writev
|
||||
// via WritePacket.)
|
||||
writevMaxIovs int
|
||||
|
||||
// addr is the address of the endpoint.
|
||||
//
|
||||
// +checklocks:mu
|
||||
addr tcpip.LinkAddress
|
||||
|
||||
// mtu (maximum transmission unit) is the maximum size of a packet.
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
// Options specify the details about the fd-based endpoint to be created.
|
||||
//
|
||||
// +stateify savable
|
||||
type Options struct {
|
||||
// FDs is a set of FDs used to read/write packets.
|
||||
FDs []int
|
||||
|
||||
// MTU is the mtu to use for this endpoint.
|
||||
MTU uint32
|
||||
|
||||
// EthernetHeader if true, indicates that the endpoint should read/write
|
||||
// ethernet frames instead of IP packets.
|
||||
EthernetHeader bool
|
||||
|
||||
// ClosedFunc is a function to be called when an endpoint's peer (if
|
||||
// any) closes its end of the communication pipe.
|
||||
ClosedFunc func(tcpip.Error)
|
||||
|
||||
// Address is the link address for this endpoint. Only used if
|
||||
// EthernetHeader is true.
|
||||
Address tcpip.LinkAddress
|
||||
|
||||
// SaveRestore if true, indicates that this NIC capability set should
|
||||
// include CapabilitySaveRestore
|
||||
SaveRestore bool
|
||||
|
||||
// DisconnectOk if true, indicates that this NIC capability set should
|
||||
// include CapabilityDisconnectOk.
|
||||
DisconnectOk bool
|
||||
|
||||
// GSOMaxSize is the maximum GSO packet size. It is zero if GSO is
|
||||
// disabled.
|
||||
GSOMaxSize uint32
|
||||
|
||||
// GVisorGSOEnabled indicates whether Gvisor GSO is enabled or not.
|
||||
GVisorGSOEnabled bool
|
||||
|
||||
// PacketDispatchMode specifies the type of inbound dispatcher to be
|
||||
// used for this endpoint.
|
||||
PacketDispatchMode PacketDispatchMode
|
||||
|
||||
// TXChecksumOffload if true, indicates that this endpoints capability
|
||||
// set should include CapabilityTXChecksumOffload.
|
||||
TXChecksumOffload bool
|
||||
|
||||
// RXChecksumOffload if true, indicates that this endpoints capability
|
||||
// set should include CapabilityRXChecksumOffload.
|
||||
RXChecksumOffload bool
|
||||
|
||||
// If MaxSyscallHeaderBytes is non-zero, it is the maximum number of bytes
|
||||
// of struct iovec, msghdr, and mmsghdr that may be passed by each host
|
||||
// system call.
|
||||
MaxSyscallHeaderBytes int
|
||||
|
||||
// InterfaceIndex is the interface index of the underlying device.
|
||||
InterfaceIndex int
|
||||
|
||||
// GRO enables generic receive offload.
|
||||
GRO bool
|
||||
|
||||
// ProcessorsPerChannel is the number of goroutines used to handle packets
|
||||
// from each FD.
|
||||
ProcessorsPerChannel int
|
||||
}
|
||||
|
||||
// fanoutID is used for AF_PACKET based endpoints to enable PACKET_FANOUT
|
||||
// support in the host kernel. This allows us to use multiple FD's to receive
|
||||
// from the same underlying NIC. The fanoutID needs to be the same for a given
|
||||
// set of FD's that point to the same NIC. Trying to set the PACKET_FANOUT
|
||||
// option for an FD with a fanoutID already in use by another FD for a different
|
||||
// NIC will return an EINVAL.
|
||||
//
|
||||
// Since fanoutID must be unique within the network namespace, we start with
|
||||
// the PID to avoid collisions. The only way to be sure of avoiding collisions
|
||||
// is to run in a new network namespace.
|
||||
var fanoutID atomicbitops.Int32 = atomicbitops.FromInt32(int32(unix.Getpid()))
|
||||
|
||||
// New creates a new fd-based endpoint.
|
||||
//
|
||||
// Makes fd non-blocking, but does not take ownership of fd, which must remain
|
||||
// open for the lifetime of the returned endpoint (until after the endpoint has
|
||||
// stopped being using and Wait returns).
|
||||
func New(opts *Options) (stack.LinkEndpoint, error) {
|
||||
caps := stack.LinkEndpointCapabilities(0)
|
||||
if opts.RXChecksumOffload {
|
||||
caps |= stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
if opts.TXChecksumOffload {
|
||||
caps |= stack.CapabilityTXChecksumOffload
|
||||
}
|
||||
|
||||
hdrSize := 0
|
||||
if opts.EthernetHeader {
|
||||
hdrSize = header.EthernetMinimumSize
|
||||
caps |= stack.CapabilityResolutionRequired
|
||||
}
|
||||
|
||||
if opts.SaveRestore {
|
||||
caps |= stack.CapabilitySaveRestore
|
||||
}
|
||||
|
||||
if opts.DisconnectOk {
|
||||
caps |= stack.CapabilityDisconnectOk
|
||||
}
|
||||
|
||||
if len(opts.FDs) == 0 {
|
||||
return nil, fmt.Errorf("opts.FD is empty, at least one FD must be specified")
|
||||
}
|
||||
|
||||
if opts.MaxSyscallHeaderBytes < 0 {
|
||||
return nil, fmt.Errorf("opts.MaxSyscallHeaderBytes is negative")
|
||||
}
|
||||
|
||||
e := &endpoint{
|
||||
mtu: opts.MTU,
|
||||
caps: caps,
|
||||
closed: opts.ClosedFunc,
|
||||
addr: opts.Address,
|
||||
hdrSize: hdrSize,
|
||||
packetDispatchMode: opts.PacketDispatchMode,
|
||||
maxSyscallHeaderBytes: uintptr(opts.MaxSyscallHeaderBytes),
|
||||
writevMaxIovs: rawfile.MaxIovs,
|
||||
}
|
||||
if e.maxSyscallHeaderBytes != 0 {
|
||||
if max := int(e.maxSyscallHeaderBytes / rawfile.SizeofIovec); max < e.writevMaxIovs {
|
||||
e.writevMaxIovs = max
|
||||
}
|
||||
}
|
||||
|
||||
// Increment fanoutID to ensure that we don't re-use the same fanoutID
|
||||
// for the next endpoint.
|
||||
fid := fanoutID.Add(1)
|
||||
|
||||
// Create per channel dispatchers.
|
||||
for _, fd := range opts.FDs {
|
||||
if err := unix.SetNonblock(fd, true); err != nil {
|
||||
return nil, fmt.Errorf("unix.SetNonblock(%v) failed: %v", fd, err)
|
||||
}
|
||||
|
||||
isSocket, err := isSocketFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.fds = append(e.fds, fdInfo{fd: fd, isSocket: isSocket})
|
||||
if opts.GSOMaxSize != 0 {
|
||||
if opts.GVisorGSOEnabled {
|
||||
e.gsoKind = stack.GVisorGSOSupported
|
||||
} else {
|
||||
e.gsoKind = stack.HostGSOSupported
|
||||
}
|
||||
e.gsoMaxSize = opts.GSOMaxSize
|
||||
}
|
||||
if opts.ProcessorsPerChannel == 0 {
|
||||
opts.ProcessorsPerChannel = max(1, runtime.GOMAXPROCS(0)/len(opts.FDs))
|
||||
}
|
||||
|
||||
inboundDispatcher, err := createInboundDispatcher(e, fd, isSocket, fid, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("createInboundDispatcher(...) = %v", err)
|
||||
}
|
||||
e.inboundDispatchers = append(e.inboundDispatchers, inboundDispatcher)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts *Options) (linkDispatcher, error) {
|
||||
// By default use the readv() dispatcher as it works with all kinds of
|
||||
// FDs (tap/tun/unix domain sockets and af_packet).
|
||||
inboundDispatcher, err := newReadVDispatcher(fd, e, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newReadVDispatcher(%d, %+v) = %v", fd, e, err)
|
||||
}
|
||||
|
||||
if isSocket {
|
||||
sa, err := unix.Getsockname(fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix.Getsockname(%d) = %v", fd, err)
|
||||
}
|
||||
switch sa.(type) {
|
||||
case *unix.SockaddrLinklayer:
|
||||
// Enable PACKET_FANOUT mode if the underlying socket is of type
|
||||
// AF_PACKET. We do not enable PACKET_FANOUT_FLAG_DEFRAG as that will
|
||||
// prevent gvisor from receiving fragmented packets and the host does the
|
||||
// reassembly on our behalf before delivering the fragments. This makes it
|
||||
// hard to test fragmentation reassembly code in Netstack.
|
||||
//
|
||||
// See: include/uapi/linux/if_packet.h (struct fanout_args).
|
||||
//
|
||||
// NOTE: We are using SetSockOptInt here even though the underlying
|
||||
// option is actually a struct. The code follows the example in the
|
||||
// kernel documentation as described at the link below:
|
||||
//
|
||||
// See: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
//
|
||||
// This works out because the actual implementation for the option zero
|
||||
// initializes the structure and will initialize the max_members field
|
||||
// to a proper value if zero.
|
||||
//
|
||||
// See: https://github.com/torvalds/linux/blob/7acac4b3196caee5e21fb5ea53f8bc124e6a16fc/net/packet/af_packet.c#L3881
|
||||
const fanoutType = unix.PACKET_FANOUT_HASH
|
||||
fanoutArg := (int(fID) & 0xffff) | fanoutType<<16
|
||||
if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT, fanoutArg); err != nil {
|
||||
return nil, fmt.Errorf("failed to enable PACKET_FANOUT option: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch e.packetDispatchMode {
|
||||
case PacketMMap:
|
||||
inboundDispatcher, err = newPacketMMapDispatcher(fd, e, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newPacketMMapDispatcher(%d, %+v) = %v", fd, e, err)
|
||||
}
|
||||
case RecvMMsg:
|
||||
// If the provided FD is a socket then we optimize
|
||||
// packet reads by using recvmmsg() instead of read() to
|
||||
// read packets in a batch.
|
||||
inboundDispatcher, err = newRecvMMsgDispatcher(fd, e, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newRecvMMsgDispatcher(%d, %+v) = %v", fd, e, err)
|
||||
}
|
||||
case Readv:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown dispatch mode %d", e.packetDispatchMode)
|
||||
}
|
||||
}
|
||||
return inboundDispatcher, nil
|
||||
}
|
||||
|
||||
func isSocketFD(fd int) (bool, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
return false, fmt.Errorf("unix.Fstat(%v,...) failed: %v", fd, err)
|
||||
}
|
||||
return (stat.Mode & unix.S_IFSOCK) == unix.S_IFSOCK, nil
|
||||
}
|
||||
|
||||
// Attach launches the goroutine that reads packets from the file descriptor and
|
||||
// dispatches them via the provided dispatcher. If one is already attached,
|
||||
// then nothing happens.
|
||||
//
|
||||
// Attach implements stack.LinkEndpoint.Attach.
|
||||
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
|
||||
// nil means the NIC is being removed.
|
||||
if dispatcher == nil && e.dispatcher != nil {
|
||||
for _, dispatcher := range e.inboundDispatchers {
|
||||
dispatcher.Stop()
|
||||
}
|
||||
e.dispatcher = nil
|
||||
// NOTE(gvisor.dev/issue/11456): Unlock e.mu before e.Wait().
|
||||
e.mu.Unlock()
|
||||
e.Wait()
|
||||
return
|
||||
}
|
||||
defer e.mu.Unlock()
|
||||
if dispatcher != nil && e.dispatcher == nil {
|
||||
e.dispatcher = dispatcher
|
||||
// Link endpoints are not savable. When transportation endpoints are
|
||||
// saved, they stop sending outgoing packets and all incoming packets
|
||||
// are rejected.
|
||||
for i := range e.inboundDispatchers {
|
||||
e.wg.Add(1)
|
||||
go func(i int) { // S/R-SAFE: See above.
|
||||
e.dispatchLoop(e.inboundDispatchers[i])
|
||||
e.wg.Done()
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
||||
func (e *endpoint) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.MTU.
|
||||
func (e *endpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.SetMTU.
|
||||
func (e *endpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
||||
func (e *endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return e.caps
|
||||
}
|
||||
|
||||
// MaxHeaderLength returns the maximum size of the link-layer header.
|
||||
func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
return uint16(e.hdrSize)
|
||||
}
|
||||
|
||||
// LinkAddress returns the link address of this endpoint.
|
||||
func (e *endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.addr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.addr = addr
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait. It waits for the endpoint to stop
|
||||
// reading from its FD.
|
||||
func (e *endpoint) Wait() {
|
||||
e.wg.Wait()
|
||||
}
|
||||
|
||||
// virtioNetHdr is declared in linux/virtio_net.h.
|
||||
type virtioNetHdr struct {
|
||||
flags uint8
|
||||
gsoType uint8
|
||||
hdrLen uint16
|
||||
gsoSize uint16
|
||||
csumStart uint16
|
||||
csumOffset uint16
|
||||
}
|
||||
|
||||
// marshal serializes h to a newly-allocated byte slice, in little-endian byte
|
||||
// order.
|
||||
//
|
||||
// Note: Virtio v1.0 onwards specifies little-endian as the byte ordering used
|
||||
// for general serialization. This makes it difficult to use go-marshal for
|
||||
// virtio types, as go-marshal implicitly uses the native byte ordering.
|
||||
func (h *virtioNetHdr) marshal() []byte {
|
||||
buf := [virtioNetHdrSize]byte{
|
||||
0: byte(h.flags),
|
||||
1: byte(h.gsoType),
|
||||
|
||||
// Manually lay out the fields in little-endian byte order. Little endian =>
|
||||
// least significant bit goes to the lower address.
|
||||
|
||||
2: byte(h.hdrLen),
|
||||
3: byte(h.hdrLen >> 8),
|
||||
|
||||
4: byte(h.gsoSize),
|
||||
5: byte(h.gsoSize >> 8),
|
||||
|
||||
6: byte(h.csumStart),
|
||||
7: byte(h.csumStart >> 8),
|
||||
|
||||
8: byte(h.csumOffset),
|
||||
9: byte(h.csumOffset >> 8),
|
||||
}
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
// These constants are declared in linux/virtio_net.h.
|
||||
const (
|
||||
_VIRTIO_NET_HDR_F_NEEDS_CSUM = 1
|
||||
|
||||
_VIRTIO_NET_HDR_GSO_TCPV4 = 1
|
||||
_VIRTIO_NET_HDR_GSO_TCPV6 = 4
|
||||
)
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (e *endpoint) AddHeader(pkt *stack.PacketBuffer) {
|
||||
if e.hdrSize > 0 {
|
||||
// Add ethernet header if needed.
|
||||
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
|
||||
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
|
||||
Type: pkt.NetworkProtocolNumber,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) parseHeader(pkt *stack.PacketBuffer) (header.Ethernet, bool) {
|
||||
if e.hdrSize <= 0 {
|
||||
return nil, true
|
||||
}
|
||||
hdrBytes, ok := pkt.LinkHeader().Consume(e.hdrSize)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
hdr := header.Ethernet(hdrBytes)
|
||||
pkt.NetworkProtocolNumber = hdr.Type()
|
||||
return hdr, true
|
||||
}
|
||||
|
||||
// parseInboundHeader parses the link header of pkt and returns true if the
|
||||
// header is well-formed and sent to this endpoint's MAC or the broadcast
|
||||
// address.
|
||||
func (e *endpoint) parseInboundHeader(pkt *stack.PacketBuffer, wantAddr tcpip.LinkAddress) bool {
|
||||
hdr, ok := e.parseHeader(pkt)
|
||||
if !ok || e.hdrSize <= 0 {
|
||||
return ok
|
||||
}
|
||||
dstAddr := hdr.DestinationAddress()
|
||||
// Per RFC 9542 2.1 on the least significant bit of the first octet of
|
||||
// a MAC address: "If it is zero, the MAC address is unicast. If it is
|
||||
// a one, the address is groupcast (multicast or broadcast)." Multicast
|
||||
// and broadcast are the same thing to ethernet; they are both sent to
|
||||
// everyone.
|
||||
return dstAddr == wantAddr || byte(dstAddr[0])&0x01 == 1
|
||||
}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (e *endpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := e.parseHeader(pkt)
|
||||
return ok
|
||||
}
|
||||
|
||||
// writePacket writes outbound packets to the file descriptor. If it is not
|
||||
// currently writable, the packet is dropped.
|
||||
func (e *endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
fdInfo := e.fds[pkt.Hash%uint32(len(e.fds))]
|
||||
fd := fdInfo.fd
|
||||
var vnetHdrBuf []byte
|
||||
if e.gsoKind == stack.HostGSOSupported {
|
||||
vnetHdr := virtioNetHdr{}
|
||||
if pkt.GSOOptions.Type != stack.GSONone {
|
||||
vnetHdr.hdrLen = uint16(pkt.HeaderSize())
|
||||
if pkt.GSOOptions.NeedsCsum {
|
||||
vnetHdr.flags = _VIRTIO_NET_HDR_F_NEEDS_CSUM
|
||||
vnetHdr.csumStart = pkt.GSOOptions.L3HdrLen
|
||||
vnetHdr.csumOffset = pkt.GSOOptions.CsumOffset
|
||||
}
|
||||
if uint16(pkt.Data().Size()) > pkt.GSOOptions.MSS {
|
||||
switch pkt.GSOOptions.Type {
|
||||
case stack.GSOTCPv4:
|
||||
vnetHdr.gsoType = _VIRTIO_NET_HDR_GSO_TCPV4
|
||||
case stack.GSOTCPv6:
|
||||
vnetHdr.gsoType = _VIRTIO_NET_HDR_GSO_TCPV6
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown gso type: %v", pkt.GSOOptions.Type))
|
||||
}
|
||||
vnetHdr.gsoSize = pkt.GSOOptions.MSS
|
||||
}
|
||||
}
|
||||
vnetHdrBuf = vnetHdr.marshal()
|
||||
}
|
||||
|
||||
views := pkt.AsSlices()
|
||||
numIovecs := len(views)
|
||||
if len(vnetHdrBuf) != 0 {
|
||||
numIovecs++
|
||||
}
|
||||
if numIovecs > e.writevMaxIovs {
|
||||
numIovecs = e.writevMaxIovs
|
||||
}
|
||||
|
||||
// Allocate small iovec arrays on the stack.
|
||||
var iovecsArr [8]unix.Iovec
|
||||
iovecs := iovecsArr[:0]
|
||||
if numIovecs > len(iovecsArr) {
|
||||
iovecs = make([]unix.Iovec, 0, numIovecs)
|
||||
}
|
||||
iovecs = rawfile.AppendIovecFromBytes(iovecs, vnetHdrBuf, numIovecs)
|
||||
for _, v := range views {
|
||||
iovecs = rawfile.AppendIovecFromBytes(iovecs, v, numIovecs)
|
||||
}
|
||||
if errno := rawfile.NonBlockingWriteIovec(fd, iovecs); errno != 0 {
|
||||
return tcpip.TranslateErrno(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *endpoint) sendBatch(batchFDInfo fdInfo, pkts []*stack.PacketBuffer) (int, tcpip.Error) {
|
||||
// Degrade to writePacket if underlying fd is not a socket.
|
||||
if !batchFDInfo.isSocket {
|
||||
var written int
|
||||
var err tcpip.Error
|
||||
for written < len(pkts) {
|
||||
if err = e.writePacket(pkts[written]); err != nil {
|
||||
break
|
||||
}
|
||||
written++
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
// Send a batch of packets through batchFD.
|
||||
batchFD := batchFDInfo.fd
|
||||
mmsgHdrsStorage := make([]rawfile.MMsgHdr, 0, len(pkts))
|
||||
packets := 0
|
||||
for packets < len(pkts) {
|
||||
mmsgHdrs := mmsgHdrsStorage
|
||||
batch := pkts[packets:]
|
||||
syscallHeaderBytes := uintptr(0)
|
||||
for _, pkt := range batch {
|
||||
var vnetHdrBuf []byte
|
||||
if e.gsoKind == stack.HostGSOSupported {
|
||||
vnetHdr := virtioNetHdr{}
|
||||
if pkt.GSOOptions.Type != stack.GSONone {
|
||||
vnetHdr.hdrLen = uint16(pkt.HeaderSize())
|
||||
if pkt.GSOOptions.NeedsCsum {
|
||||
vnetHdr.flags = _VIRTIO_NET_HDR_F_NEEDS_CSUM
|
||||
vnetHdr.csumStart = pkt.GSOOptions.L3HdrLen
|
||||
vnetHdr.csumOffset = pkt.GSOOptions.CsumOffset
|
||||
}
|
||||
if pkt.GSOOptions.Type != stack.GSONone && uint16(pkt.Data().Size()) > pkt.GSOOptions.MSS {
|
||||
switch pkt.GSOOptions.Type {
|
||||
case stack.GSOTCPv4:
|
||||
vnetHdr.gsoType = _VIRTIO_NET_HDR_GSO_TCPV4
|
||||
case stack.GSOTCPv6:
|
||||
vnetHdr.gsoType = _VIRTIO_NET_HDR_GSO_TCPV6
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown gso type: %v", pkt.GSOOptions.Type))
|
||||
}
|
||||
vnetHdr.gsoSize = pkt.GSOOptions.MSS
|
||||
}
|
||||
}
|
||||
vnetHdrBuf = vnetHdr.marshal()
|
||||
}
|
||||
|
||||
views, offset := pkt.AsViewList()
|
||||
var skipped int
|
||||
var view *buffer.View
|
||||
for view = views.Front(); view != nil && offset >= view.Size(); view = view.Next() {
|
||||
offset -= view.Size()
|
||||
skipped++
|
||||
}
|
||||
|
||||
// We've made it to the usable views.
|
||||
numIovecs := views.Len() - skipped
|
||||
if len(vnetHdrBuf) != 0 {
|
||||
numIovecs++
|
||||
}
|
||||
if numIovecs > rawfile.MaxIovs {
|
||||
numIovecs = rawfile.MaxIovs
|
||||
}
|
||||
if e.maxSyscallHeaderBytes != 0 {
|
||||
syscallHeaderBytes += rawfile.SizeofMMsgHdr + uintptr(numIovecs)*rawfile.SizeofIovec
|
||||
if syscallHeaderBytes > e.maxSyscallHeaderBytes {
|
||||
// We can't fit this packet into this call to sendmmsg().
|
||||
// We could potentially do so if we reduced numIovecs
|
||||
// further, but this might incur considerable extra
|
||||
// copying. Leave it to the next batch instead.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We can't easily allocate iovec arrays on the stack here since
|
||||
// they will escape this loop iteration via mmsgHdrs.
|
||||
iovecs := make([]unix.Iovec, 0, numIovecs)
|
||||
iovecs = rawfile.AppendIovecFromBytes(iovecs, vnetHdrBuf, numIovecs)
|
||||
// At most one slice has a non-zero offset.
|
||||
iovecs = rawfile.AppendIovecFromBytes(iovecs, view.AsSlice()[offset:], numIovecs)
|
||||
for view = view.Next(); view != nil; view = view.Next() {
|
||||
iovecs = rawfile.AppendIovecFromBytes(iovecs, view.AsSlice(), numIovecs)
|
||||
}
|
||||
|
||||
var mmsgHdr rawfile.MMsgHdr
|
||||
mmsgHdr.Msg.Iov = &iovecs[0]
|
||||
mmsgHdr.Msg.SetIovlen(len(iovecs))
|
||||
mmsgHdrs = append(mmsgHdrs, mmsgHdr)
|
||||
}
|
||||
|
||||
if len(mmsgHdrs) == 0 {
|
||||
// We can't fit batch[0] into a mmsghdr while staying under
|
||||
// e.maxSyscallHeaderBytes. Use WritePacket, which will avoid the
|
||||
// mmsghdr (by using writev) and re-buffer iovecs more aggressively
|
||||
// if necessary (by using e.writevMaxIovs instead of
|
||||
// rawfile.MaxIovs).
|
||||
pkt := batch[0]
|
||||
if err := e.writePacket(pkt); err != nil {
|
||||
return packets, err
|
||||
}
|
||||
packets++
|
||||
} else {
|
||||
for len(mmsgHdrs) > 0 {
|
||||
sent, errno := rawfile.NonBlockingSendMMsg(batchFD, mmsgHdrs)
|
||||
if errno != 0 {
|
||||
return packets, tcpip.TranslateErrno(errno)
|
||||
}
|
||||
packets += sent
|
||||
mmsgHdrs = mmsgHdrs[sent:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packets, nil
|
||||
}
|
||||
|
||||
// WritePackets writes outbound packets to the underlying file descriptors. If
|
||||
// one is not currently writable, the packet is dropped.
|
||||
//
|
||||
// Being a batch API, each packet in pkts should have the following
|
||||
// fields populated:
|
||||
// - pkt.EgressRoute
|
||||
// - pkt.GSOOptions
|
||||
// - pkt.NetworkProtocolNumber
|
||||
func (e *endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
// Preallocate to avoid repeated reallocation as we append to batch.
|
||||
batch := make([]*stack.PacketBuffer, 0, BatchSize)
|
||||
batchFDInfo := fdInfo{fd: -1, isSocket: false}
|
||||
sentPackets := 0
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if len(batch) == 0 {
|
||||
batchFDInfo = e.fds[pkt.Hash%uint32(len(e.fds))]
|
||||
}
|
||||
pktFDInfo := e.fds[pkt.Hash%uint32(len(e.fds))]
|
||||
if sendNow := pktFDInfo != batchFDInfo; !sendNow {
|
||||
batch = append(batch, pkt)
|
||||
continue
|
||||
}
|
||||
n, err := e.sendBatch(batchFDInfo, batch)
|
||||
sentPackets += n
|
||||
if err != nil {
|
||||
return sentPackets, err
|
||||
}
|
||||
batch = batch[:0]
|
||||
batch = append(batch, pkt)
|
||||
batchFDInfo = pktFDInfo
|
||||
}
|
||||
|
||||
if len(batch) != 0 {
|
||||
n, err := e.sendBatch(batchFDInfo, batch)
|
||||
sentPackets += n
|
||||
if err != nil {
|
||||
return sentPackets, err
|
||||
}
|
||||
}
|
||||
return sentPackets, nil
|
||||
}
|
||||
|
||||
// InjectOutbound implements stack.InjectableEndpoint.InjectOutbound.
|
||||
func (e *endpoint) InjectOutbound(dest tcpip.Address, packet *buffer.View) tcpip.Error {
|
||||
if errno := rawfile.NonBlockingWrite(e.fds[0].fd, packet.AsSlice()); errno != 0 {
|
||||
return tcpip.TranslateErrno(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatchLoop reads packets from the file descriptor in a loop and dispatches
|
||||
// them to the network stack.
|
||||
func (e *endpoint) dispatchLoop(inboundDispatcher linkDispatcher) tcpip.Error {
|
||||
for {
|
||||
cont, err := inboundDispatcher.dispatch()
|
||||
if err != nil || !cont {
|
||||
if e.closed != nil {
|
||||
e.closed(err)
|
||||
}
|
||||
inboundDispatcher.release()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GSOMaxSize implements stack.GSOEndpoint.
|
||||
func (e *endpoint) GSOMaxSize() uint32 {
|
||||
return e.gsoMaxSize
|
||||
}
|
||||
|
||||
// SupportedGSO implements stack.GSOEndpoint.
|
||||
func (e *endpoint) SupportedGSO() stack.SupportedGSO {
|
||||
return e.gsoKind
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
|
||||
func (e *endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
if e.hdrSize > 0 {
|
||||
return header.ARPHardwareEther
|
||||
}
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
// Close implements stack.LinkEndpoint.
|
||||
func (e *endpoint) Close() {}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.
|
||||
func (*endpoint) SetOnCloseAction(func()) {}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// +stateify savable
|
||||
type InjectableEndpoint struct {
|
||||
endpoint
|
||||
|
||||
mu injectableEndpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
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.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
|
||||
// InjectInbound injects an inbound packet. If the endpoint is not attached, the
|
||||
// packet is not delivered.
|
||||
func (e *InjectableEndpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
d := e.dispatcher
|
||||
e.mu.RUnlock()
|
||||
if d != nil {
|
||||
d.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// NewInjectable creates a new fd-based InjectableEndpoint.
|
||||
func NewInjectable(fd int, mtu uint32, capabilities stack.LinkEndpointCapabilities) (*InjectableEndpoint, error) {
|
||||
unix.SetNonblock(fd, true)
|
||||
isSocket, err := isSocketFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &InjectableEndpoint{endpoint: endpoint{
|
||||
fds: []fdInfo{{fd: fd, isSocket: isSocket}},
|
||||
mtu: mtu,
|
||||
caps: capabilities,
|
||||
writevMaxIovs: rawfile.MaxIovs,
|
||||
}}, nil
|
||||
}
|
||||
96
pkg/tcpip/link/fdbased/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/fdbased/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package fdbased
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
24
pkg/tcpip/link/fdbased/endpoint_unsafe.go
Normal file
24
pkg/tcpip/link/fdbased/endpoint_unsafe.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const virtioNetHdrSize = int(unsafe.Sizeof(virtioNetHdr{}))
|
||||
6
pkg/tcpip/link/fdbased/fdbased_nonlinux_state_autogen.go
Normal file
6
pkg/tcpip/link/fdbased/fdbased_nonlinux_state_autogen.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
//go:build !linux || (!amd64 && !arm64)
|
||||
// +build !linux !amd64,!arm64
|
||||
|
||||
package fdbased
|
||||
438
pkg/tcpip/link/fdbased/fdbased_state_autogen.go
Normal file
438
pkg/tcpip/link/fdbased/fdbased_state_autogen.go
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
//go:build linux && ((linux && amd64) || (linux && arm64)) && linux && linux
|
||||
// +build linux
|
||||
// +build linux,amd64 linux,arm64
|
||||
// +build linux
|
||||
// +build linux
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (f *fdInfo) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.fdInfo"
|
||||
}
|
||||
|
||||
func (f *fdInfo) StateFields() []string {
|
||||
return []string{
|
||||
"fd",
|
||||
"isSocket",
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fdInfo) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *fdInfo) StateSave(stateSinkObject state.Sink) {
|
||||
f.beforeSave()
|
||||
stateSinkObject.Save(0, &f.fd)
|
||||
stateSinkObject.Save(1, &f.isSocket)
|
||||
}
|
||||
|
||||
func (f *fdInfo) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *fdInfo) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &f.fd)
|
||||
stateSourceObject.Load(1, &f.isSocket)
|
||||
}
|
||||
|
||||
func (e *endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.endpoint"
|
||||
}
|
||||
|
||||
func (e *endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"fds",
|
||||
"hdrSize",
|
||||
"caps",
|
||||
"inboundDispatchers",
|
||||
"dispatcher",
|
||||
"packetDispatchMode",
|
||||
"gsoMaxSize",
|
||||
"gsoKind",
|
||||
"maxSyscallHeaderBytes",
|
||||
"writevMaxIovs",
|
||||
"addr",
|
||||
"mtu",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.fds)
|
||||
stateSinkObject.Save(1, &e.hdrSize)
|
||||
stateSinkObject.Save(2, &e.caps)
|
||||
stateSinkObject.Save(3, &e.inboundDispatchers)
|
||||
stateSinkObject.Save(4, &e.dispatcher)
|
||||
stateSinkObject.Save(5, &e.packetDispatchMode)
|
||||
stateSinkObject.Save(6, &e.gsoMaxSize)
|
||||
stateSinkObject.Save(7, &e.gsoKind)
|
||||
stateSinkObject.Save(8, &e.maxSyscallHeaderBytes)
|
||||
stateSinkObject.Save(9, &e.writevMaxIovs)
|
||||
stateSinkObject.Save(10, &e.addr)
|
||||
stateSinkObject.Save(11, &e.mtu)
|
||||
}
|
||||
|
||||
func (e *endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.fds)
|
||||
stateSourceObject.Load(1, &e.hdrSize)
|
||||
stateSourceObject.Load(2, &e.caps)
|
||||
stateSourceObject.Load(3, &e.inboundDispatchers)
|
||||
stateSourceObject.Load(4, &e.dispatcher)
|
||||
stateSourceObject.Load(5, &e.packetDispatchMode)
|
||||
stateSourceObject.Load(6, &e.gsoMaxSize)
|
||||
stateSourceObject.Load(7, &e.gsoKind)
|
||||
stateSourceObject.Load(8, &e.maxSyscallHeaderBytes)
|
||||
stateSourceObject.Load(9, &e.writevMaxIovs)
|
||||
stateSourceObject.Load(10, &e.addr)
|
||||
stateSourceObject.Load(11, &e.mtu)
|
||||
}
|
||||
|
||||
func (o *Options) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.Options"
|
||||
}
|
||||
|
||||
func (o *Options) StateFields() []string {
|
||||
return []string{
|
||||
"FDs",
|
||||
"MTU",
|
||||
"EthernetHeader",
|
||||
"ClosedFunc",
|
||||
"Address",
|
||||
"SaveRestore",
|
||||
"DisconnectOk",
|
||||
"GSOMaxSize",
|
||||
"GVisorGSOEnabled",
|
||||
"PacketDispatchMode",
|
||||
"TXChecksumOffload",
|
||||
"RXChecksumOffload",
|
||||
"MaxSyscallHeaderBytes",
|
||||
"InterfaceIndex",
|
||||
"GRO",
|
||||
"ProcessorsPerChannel",
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Options) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (o *Options) StateSave(stateSinkObject state.Sink) {
|
||||
o.beforeSave()
|
||||
stateSinkObject.Save(0, &o.FDs)
|
||||
stateSinkObject.Save(1, &o.MTU)
|
||||
stateSinkObject.Save(2, &o.EthernetHeader)
|
||||
stateSinkObject.Save(3, &o.ClosedFunc)
|
||||
stateSinkObject.Save(4, &o.Address)
|
||||
stateSinkObject.Save(5, &o.SaveRestore)
|
||||
stateSinkObject.Save(6, &o.DisconnectOk)
|
||||
stateSinkObject.Save(7, &o.GSOMaxSize)
|
||||
stateSinkObject.Save(8, &o.GVisorGSOEnabled)
|
||||
stateSinkObject.Save(9, &o.PacketDispatchMode)
|
||||
stateSinkObject.Save(10, &o.TXChecksumOffload)
|
||||
stateSinkObject.Save(11, &o.RXChecksumOffload)
|
||||
stateSinkObject.Save(12, &o.MaxSyscallHeaderBytes)
|
||||
stateSinkObject.Save(13, &o.InterfaceIndex)
|
||||
stateSinkObject.Save(14, &o.GRO)
|
||||
stateSinkObject.Save(15, &o.ProcessorsPerChannel)
|
||||
}
|
||||
|
||||
func (o *Options) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (o *Options) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &o.FDs)
|
||||
stateSourceObject.Load(1, &o.MTU)
|
||||
stateSourceObject.Load(2, &o.EthernetHeader)
|
||||
stateSourceObject.Load(3, &o.ClosedFunc)
|
||||
stateSourceObject.Load(4, &o.Address)
|
||||
stateSourceObject.Load(5, &o.SaveRestore)
|
||||
stateSourceObject.Load(6, &o.DisconnectOk)
|
||||
stateSourceObject.Load(7, &o.GSOMaxSize)
|
||||
stateSourceObject.Load(8, &o.GVisorGSOEnabled)
|
||||
stateSourceObject.Load(9, &o.PacketDispatchMode)
|
||||
stateSourceObject.Load(10, &o.TXChecksumOffload)
|
||||
stateSourceObject.Load(11, &o.RXChecksumOffload)
|
||||
stateSourceObject.Load(12, &o.MaxSyscallHeaderBytes)
|
||||
stateSourceObject.Load(13, &o.InterfaceIndex)
|
||||
stateSourceObject.Load(14, &o.GRO)
|
||||
stateSourceObject.Load(15, &o.ProcessorsPerChannel)
|
||||
}
|
||||
|
||||
func (e *InjectableEndpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.InjectableEndpoint"
|
||||
}
|
||||
|
||||
func (e *InjectableEndpoint) StateFields() []string {
|
||||
return []string{
|
||||
"endpoint",
|
||||
"dispatcher",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *InjectableEndpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *InjectableEndpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.endpoint)
|
||||
stateSinkObject.Save(1, &e.dispatcher)
|
||||
}
|
||||
|
||||
func (e *InjectableEndpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *InjectableEndpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.endpoint)
|
||||
stateSourceObject.Load(1, &e.dispatcher)
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.packetMMapDispatcher"
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) StateFields() []string {
|
||||
return []string{
|
||||
"StopFD",
|
||||
"fd",
|
||||
"e",
|
||||
"ringBuffer",
|
||||
"ringOffset",
|
||||
"mgr",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *packetMMapDispatcher) StateSave(stateSinkObject state.Sink) {
|
||||
d.beforeSave()
|
||||
stateSinkObject.Save(0, &d.StopFD)
|
||||
stateSinkObject.Save(1, &d.fd)
|
||||
stateSinkObject.Save(2, &d.e)
|
||||
stateSinkObject.Save(3, &d.ringBuffer)
|
||||
stateSinkObject.Save(4, &d.ringOffset)
|
||||
stateSinkObject.Save(5, &d.mgr)
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *packetMMapDispatcher) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &d.StopFD)
|
||||
stateSourceObject.Load(1, &d.fd)
|
||||
stateSourceObject.Load(2, &d.e)
|
||||
stateSourceObject.Load(3, &d.ringBuffer)
|
||||
stateSourceObject.Load(4, &d.ringOffset)
|
||||
stateSourceObject.Load(5, &d.mgr)
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.iovecBuffer"
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) StateFields() []string {
|
||||
return []string{
|
||||
"views",
|
||||
"sizes",
|
||||
"skipsVnetHdr",
|
||||
"pulledIndex",
|
||||
}
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (b *iovecBuffer) StateSave(stateSinkObject state.Sink) {
|
||||
b.beforeSave()
|
||||
stateSinkObject.Save(0, &b.views)
|
||||
stateSinkObject.Save(1, &b.sizes)
|
||||
stateSinkObject.Save(2, &b.skipsVnetHdr)
|
||||
stateSinkObject.Save(3, &b.pulledIndex)
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (b *iovecBuffer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &b.views)
|
||||
stateSourceObject.Load(1, &b.sizes)
|
||||
stateSourceObject.Load(2, &b.skipsVnetHdr)
|
||||
stateSourceObject.Load(3, &b.pulledIndex)
|
||||
}
|
||||
|
||||
func (d *readVDispatcher) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.readVDispatcher"
|
||||
}
|
||||
|
||||
func (d *readVDispatcher) StateFields() []string {
|
||||
return []string{
|
||||
"StopFD",
|
||||
"fd",
|
||||
"e",
|
||||
"buf",
|
||||
"mgr",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *readVDispatcher) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *readVDispatcher) StateSave(stateSinkObject state.Sink) {
|
||||
d.beforeSave()
|
||||
stateSinkObject.Save(0, &d.StopFD)
|
||||
stateSinkObject.Save(1, &d.fd)
|
||||
stateSinkObject.Save(2, &d.e)
|
||||
stateSinkObject.Save(3, &d.buf)
|
||||
stateSinkObject.Save(4, &d.mgr)
|
||||
}
|
||||
|
||||
func (d *readVDispatcher) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *readVDispatcher) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &d.StopFD)
|
||||
stateSourceObject.Load(1, &d.fd)
|
||||
stateSourceObject.Load(2, &d.e)
|
||||
stateSourceObject.Load(3, &d.buf)
|
||||
stateSourceObject.Load(4, &d.mgr)
|
||||
}
|
||||
|
||||
func (r *recvMMsgDispatcher) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.recvMMsgDispatcher"
|
||||
}
|
||||
|
||||
func (r *recvMMsgDispatcher) StateFields() []string {
|
||||
return []string{
|
||||
"StopFD",
|
||||
"fd",
|
||||
"e",
|
||||
"bufs",
|
||||
"pkts",
|
||||
"gro",
|
||||
"mgr",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *recvMMsgDispatcher) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *recvMMsgDispatcher) StateSave(stateSinkObject state.Sink) {
|
||||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.StopFD)
|
||||
stateSinkObject.Save(1, &r.fd)
|
||||
stateSinkObject.Save(2, &r.e)
|
||||
stateSinkObject.Save(3, &r.bufs)
|
||||
stateSinkObject.Save(4, &r.pkts)
|
||||
stateSinkObject.Save(5, &r.gro)
|
||||
stateSinkObject.Save(6, &r.mgr)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *recvMMsgDispatcher) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.StopFD)
|
||||
stateSourceObject.Load(1, &r.fd)
|
||||
stateSourceObject.Load(2, &r.e)
|
||||
stateSourceObject.Load(3, &r.bufs)
|
||||
stateSourceObject.Load(4, &r.pkts)
|
||||
stateSourceObject.Load(5, &r.gro)
|
||||
stateSourceObject.Load(6, &r.mgr)
|
||||
stateSourceObject.AfterLoad(func() { r.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
func (p *processor) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.processor"
|
||||
}
|
||||
|
||||
func (p *processor) StateFields() []string {
|
||||
return []string{
|
||||
"pkts",
|
||||
"e",
|
||||
"gro",
|
||||
"sleeper",
|
||||
"packetWaker",
|
||||
"closeWaker",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *processor) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (p *processor) StateSave(stateSinkObject state.Sink) {
|
||||
p.beforeSave()
|
||||
stateSinkObject.Save(0, &p.pkts)
|
||||
stateSinkObject.Save(1, &p.e)
|
||||
stateSinkObject.Save(2, &p.gro)
|
||||
stateSinkObject.Save(3, &p.sleeper)
|
||||
stateSinkObject.Save(4, &p.packetWaker)
|
||||
stateSinkObject.Save(5, &p.closeWaker)
|
||||
}
|
||||
|
||||
func (p *processor) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (p *processor) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &p.pkts)
|
||||
stateSourceObject.Load(1, &p.e)
|
||||
stateSourceObject.Load(2, &p.gro)
|
||||
stateSourceObject.Load(3, &p.sleeper)
|
||||
stateSourceObject.Load(4, &p.packetWaker)
|
||||
stateSourceObject.Load(5, &p.closeWaker)
|
||||
}
|
||||
|
||||
func (m *processorManager) StateTypeName() string {
|
||||
return "pkg/tcpip/link/fdbased.processorManager"
|
||||
}
|
||||
|
||||
func (m *processorManager) StateFields() []string {
|
||||
return []string{
|
||||
"processors",
|
||||
"seed",
|
||||
"e",
|
||||
"ready",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *processorManager) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *processorManager) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.processors)
|
||||
stateSinkObject.Save(1, &m.seed)
|
||||
stateSinkObject.Save(2, &m.e)
|
||||
stateSinkObject.Save(3, &m.ready)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *processorManager) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.processors)
|
||||
stateSourceObject.Load(1, &m.seed)
|
||||
stateSourceObject.Load(2, &m.e)
|
||||
stateSourceObject.Load(3, &m.ready)
|
||||
stateSourceObject.AfterLoad(func() { m.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*fdInfo)(nil))
|
||||
state.Register((*endpoint)(nil))
|
||||
state.Register((*Options)(nil))
|
||||
state.Register((*InjectableEndpoint)(nil))
|
||||
state.Register((*packetMMapDispatcher)(nil))
|
||||
state.Register((*iovecBuffer)(nil))
|
||||
state.Register((*readVDispatcher)(nil))
|
||||
state.Register((*recvMMsgDispatcher)(nil))
|
||||
state.Register((*processor)(nil))
|
||||
state.Register((*processorManager)(nil))
|
||||
}
|
||||
7
pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go
Normal file
7
pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
//go:build linux && ((linux && amd64) || (linux && arm64))
|
||||
// +build linux
|
||||
// +build linux,amd64 linux,arm64
|
||||
|
||||
package fdbased
|
||||
96
pkg/tcpip/link/fdbased/injectable_endpoint_mutex.go
Normal file
96
pkg/tcpip/link/fdbased/injectable_endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package fdbased
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type injectableEndpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var injectableEndpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type injectableEndpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) Lock() {
|
||||
locking.AddGLock(injectableEndpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) NestedLock(i injectableEndpointlockNameIndex) {
|
||||
locking.AddGLock(injectableEndpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(injectableEndpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) NestedUnlock(i injectableEndpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(injectableEndpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) RLock() {
|
||||
locking.AddGLock(injectableEndpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(injectableEndpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *injectableEndpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var injectableEndpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func injectableEndpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
injectableEndpointinitLockNames()
|
||||
injectableEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(injectableEndpointRWMutex{}), injectableEndpointlockNames)
|
||||
}
|
||||
199
pkg/tcpip/link/fdbased/mmap.go
Normal file
199
pkg/tcpip/link/fdbased/mmap.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
//go:build (linux && amd64) || (linux && arm64)
|
||||
// +build linux,amd64 linux,arm64
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/stopfd"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
tPacketAlignment = uintptr(16)
|
||||
tpStatusKernel = 0
|
||||
tpStatusUser = 1
|
||||
tpStatusCopy = 2
|
||||
tpStatusLosing = 4
|
||||
)
|
||||
|
||||
// We overallocate the frame size to accommodate space for the
|
||||
// TPacketHdr+RawSockAddrLinkLayer+MAC header and any padding.
|
||||
//
|
||||
// Memory allocated for the ring buffer: tpBlockSize * tpBlockNR = 2 MiB
|
||||
//
|
||||
// NOTE:
|
||||
//
|
||||
// Frames need to be aligned at 16 byte boundaries.
|
||||
// BlockSize needs to be page aligned.
|
||||
//
|
||||
// For details see PACKET_MMAP setting constraints in
|
||||
// https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
const (
|
||||
tpFrameSize = 65536 + 128
|
||||
tpBlockSize = tpFrameSize * 32
|
||||
tpBlockNR = 1
|
||||
tpFrameNR = (tpBlockSize * tpBlockNR) / tpFrameSize
|
||||
)
|
||||
|
||||
// tPacketAlign aligns the pointer v at a tPacketAlignment boundary. Direct
|
||||
// translation of the TPACKET_ALIGN macro in <linux/if_packet.h>.
|
||||
func tPacketAlign(v uintptr) uintptr {
|
||||
return (v + tPacketAlignment - 1) & uintptr(^(tPacketAlignment - 1))
|
||||
}
|
||||
|
||||
// tPacketReq is the tpacket_req structure as described in
|
||||
// https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
type tPacketReq struct {
|
||||
tpBlockSize uint32
|
||||
tpBlockNR uint32
|
||||
tpFrameSize uint32
|
||||
tpFrameNR uint32
|
||||
}
|
||||
|
||||
// tPacketHdr is tpacket_hdr structure as described in <linux/if_packet.h>
|
||||
type tPacketHdr []byte
|
||||
|
||||
const (
|
||||
tpStatusOffset = 0
|
||||
tpLenOffset = 8
|
||||
tpSnapLenOffset = 12
|
||||
tpMacOffset = 16
|
||||
tpNetOffset = 18
|
||||
tpSecOffset = 20
|
||||
tpUSecOffset = 24
|
||||
)
|
||||
|
||||
func (t tPacketHdr) tpLen() uint32 {
|
||||
return binary.LittleEndian.Uint32(t[tpLenOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) tpSnapLen() uint32 {
|
||||
return binary.LittleEndian.Uint32(t[tpSnapLenOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) tpMac() uint16 {
|
||||
return binary.LittleEndian.Uint16(t[tpMacOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) tpNet() uint16 {
|
||||
return binary.LittleEndian.Uint16(t[tpNetOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) tpSec() uint32 {
|
||||
return binary.LittleEndian.Uint32(t[tpSecOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) tpUSec() uint32 {
|
||||
return binary.LittleEndian.Uint32(t[tpUSecOffset:])
|
||||
}
|
||||
|
||||
func (t tPacketHdr) Payload() []byte {
|
||||
return t[uint32(t.tpMac()) : uint32(t.tpMac())+t.tpSnapLen()]
|
||||
}
|
||||
|
||||
// packetMMapDispatcher uses PACKET_RX_RING's to read/dispatch inbound packets.
|
||||
// See: mmap_amd64_unsafe.go for implementation details.
|
||||
//
|
||||
// +stateify savable
|
||||
type packetMMapDispatcher struct {
|
||||
stopfd.StopFD
|
||||
// fd is the file descriptor used to send and receive packets.
|
||||
fd int
|
||||
|
||||
// e is the endpoint this dispatcher is attached to.
|
||||
e *endpoint
|
||||
|
||||
// ringBuffer is only used when PacketMMap dispatcher is used and points
|
||||
// to the start of the mmapped PACKET_RX_RING buffer.
|
||||
ringBuffer []byte
|
||||
|
||||
// ringOffset is the current offset into the ring buffer where the next
|
||||
// inbound packet will be placed by the kernel.
|
||||
ringOffset int
|
||||
|
||||
// mgr is the processor goroutine manager.
|
||||
mgr *processorManager
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) release() {
|
||||
d.mgr.close()
|
||||
}
|
||||
|
||||
func (d *packetMMapDispatcher) readMMappedPackets() (stack.PacketBufferList, bool, tcpip.Error) {
|
||||
var pkts stack.PacketBufferList
|
||||
hdr := tPacketHdr(d.ringBuffer[d.ringOffset*tpFrameSize:])
|
||||
for hdr.tpStatus()&tpStatusUser == 0 {
|
||||
stopped, errno := rawfile.BlockingPollUntilStopped(d.EFD, d.fd, unix.POLLIN|unix.POLLERR)
|
||||
if errno != 0 {
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return pkts, stopped, tcpip.TranslateErrno(errno)
|
||||
}
|
||||
if stopped {
|
||||
return pkts, true, nil
|
||||
}
|
||||
if hdr.tpStatus()&tpStatusCopy != 0 {
|
||||
// This frame is truncated so skip it after flipping the
|
||||
// buffer to the kernel.
|
||||
hdr.setTPStatus(tpStatusKernel)
|
||||
d.ringOffset = (d.ringOffset + 1) % tpFrameNR
|
||||
hdr = (tPacketHdr)(d.ringBuffer[d.ringOffset*tpFrameSize:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for hdr.tpStatus()&tpStatusUser == 1 {
|
||||
// Copy out the packet from the mmapped frame to a locally owned buffer.
|
||||
pkts.PushBack(stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithView(buffer.NewViewWithData(hdr.Payload())),
|
||||
}))
|
||||
// Release packet to kernel.
|
||||
hdr.setTPStatus(tpStatusKernel)
|
||||
d.ringOffset = (d.ringOffset + 1) % tpFrameNR
|
||||
hdr = tPacketHdr(d.ringBuffer[d.ringOffset*tpFrameSize:])
|
||||
}
|
||||
return pkts, false, nil
|
||||
}
|
||||
|
||||
// dispatch reads packets from an mmaped ring buffer and dispatches them to the
|
||||
// network stack.
|
||||
func (d *packetMMapDispatcher) dispatch() (bool, tcpip.Error) {
|
||||
pkts, stopped, err := d.readMMappedPackets()
|
||||
defer pkts.Reset()
|
||||
if err != nil || stopped {
|
||||
return false, err
|
||||
}
|
||||
d.e.mu.RLock()
|
||||
addr := d.e.addr
|
||||
d.e.mu.RUnlock()
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if d.e.parseInboundHeader(pkt, addr) {
|
||||
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
|
||||
}
|
||||
}
|
||||
if pkts.Len() > 0 {
|
||||
d.mgr.wakeReady()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
24
pkg/tcpip/link/fdbased/mmap_nonlinux.go
Normal file
24
pkg/tcpip/link/fdbased/mmap_nonlinux.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
//go:build !linux || (!amd64 && !arm64)
|
||||
// +build !linux !amd64,!arm64
|
||||
|
||||
package fdbased
|
||||
|
||||
// Stubbed out version for non-linux/non-amd64/non-arm64 platforms.
|
||||
|
||||
func newPacketMMapDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
|
||||
return nil, nil
|
||||
}
|
||||
92
pkg/tcpip/link/fdbased/mmap_unsafe.go
Normal file
92
pkg/tcpip/link/fdbased/mmap_unsafe.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
//go:build (linux && amd64) || (linux && arm64)
|
||||
// +build linux,amd64 linux,arm64
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/stopfd"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// tPacketHdrlen is the TPACKET_HDRLEN variable defined in <linux/if_packet.h>.
|
||||
var tPacketHdrlen = tPacketAlign(unsafe.Sizeof(tPacketHdr{}) + unsafe.Sizeof(unix.RawSockaddrLinklayer{}))
|
||||
|
||||
// tpStatus returns the frame status field.
|
||||
// The status is concurrently updated by the kernel as a result we must
|
||||
// use atomic operations to prevent races.
|
||||
func (t tPacketHdr) tpStatus() uint32 {
|
||||
hdr := unsafe.Pointer(&t[0])
|
||||
statusPtr := unsafe.Pointer(uintptr(hdr) + uintptr(tpStatusOffset))
|
||||
return (*atomicbitops.Uint32)(statusPtr).Load()
|
||||
}
|
||||
|
||||
// setTPStatus set's the frame status to the provided status.
|
||||
// The status is concurrently updated by the kernel as a result we must
|
||||
// use atomic operations to prevent races.
|
||||
func (t tPacketHdr) setTPStatus(status uint32) {
|
||||
hdr := unsafe.Pointer(&t[0])
|
||||
statusPtr := unsafe.Pointer(uintptr(hdr) + uintptr(tpStatusOffset))
|
||||
(*atomicbitops.Uint32)(statusPtr).Store(status)
|
||||
}
|
||||
|
||||
func newPacketMMapDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
|
||||
stopFD, err := stopfd.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &packetMMapDispatcher{
|
||||
StopFD: stopFD,
|
||||
fd: fd,
|
||||
e: e,
|
||||
}
|
||||
pageSize := unix.Getpagesize()
|
||||
if tpBlockSize%pageSize != 0 {
|
||||
return nil, fmt.Errorf("tpBlockSize: %d is not page aligned, pagesize: %d", tpBlockSize, pageSize)
|
||||
}
|
||||
tReq := tPacketReq{
|
||||
tpBlockSize: uint32(tpBlockSize),
|
||||
tpBlockNR: uint32(tpBlockNR),
|
||||
tpFrameSize: uint32(tpFrameSize),
|
||||
tpFrameNR: uint32(tpFrameNR),
|
||||
}
|
||||
// Setup PACKET_RX_RING.
|
||||
if err := setsockopt(d.fd, unix.SOL_PACKET, unix.PACKET_RX_RING, unsafe.Pointer(&tReq), unsafe.Sizeof(tReq)); err != nil {
|
||||
return nil, fmt.Errorf("failed to enable PACKET_RX_RING: %v", err)
|
||||
}
|
||||
// Let's mmap the blocks.
|
||||
sz := tpBlockSize * tpBlockNR
|
||||
buf, err := unix.Mmap(d.fd, 0, sz, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix.Mmap(...,0, %v, ...) failed = %v", sz, err)
|
||||
}
|
||||
d.mgr = newProcessorManager(opts, e)
|
||||
d.mgr.start()
|
||||
d.ringBuffer = buf
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func setsockopt(fd, level, name int, val unsafe.Pointer, vallen uintptr) error {
|
||||
if _, _, errno := unix.Syscall6(unix.SYS_SETSOCKOPT, uintptr(fd), uintptr(level), uintptr(name), uintptr(val), vallen, 0); errno != 0 {
|
||||
return error(errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
330
pkg/tcpip/link/fdbased/packet_dispatchers.go
Normal file
330
pkg/tcpip/link/fdbased/packet_dispatchers.go
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/stopfd"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack/gro"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// BufConfig defines the shape of the buffer used to read packets from the NIC.
|
||||
// The duplication of 256 is intended so that the sum of the elements can cover
|
||||
// the maximum packet size we expect to receive. See TestBufConfigMaxLength.
|
||||
var BufConfig = []int{128, 256, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768}
|
||||
|
||||
// +stateify savable
|
||||
type iovecBuffer struct {
|
||||
// buffer is the actual buffer that holds the packet contents. Some contents
|
||||
// are reused across calls to pullBuffer if number of requested bytes is
|
||||
// smaller than the number of bytes allocated in the buffer.
|
||||
views []*buffer.View
|
||||
|
||||
// iovecs are initialized with base pointers/len of the corresponding
|
||||
// entries in the views defined above, except when GSO is enabled
|
||||
// (skipsVnetHdr) then the first iovec points to a buffer for the vnet header
|
||||
// which is stripped before the views are passed up the stack for further
|
||||
// processing.
|
||||
iovecs []unix.Iovec `state:"nosave"`
|
||||
|
||||
// sizes is an array of buffer sizes for the underlying views. sizes is
|
||||
// immutable.
|
||||
sizes []int
|
||||
|
||||
// skipsVnetHdr is true if virtioNetHdr is to skipped.
|
||||
skipsVnetHdr bool
|
||||
|
||||
// pulledIndex is the index of the last []byte buffer pulled from the
|
||||
// underlying buffer storage during a call to pullBuffers. It is -1
|
||||
// if no buffer is pulled.
|
||||
pulledIndex int
|
||||
}
|
||||
|
||||
func newIovecBuffer(sizes []int, skipsVnetHdr bool) *iovecBuffer {
|
||||
b := &iovecBuffer{
|
||||
views: make([]*buffer.View, len(sizes)),
|
||||
sizes: sizes,
|
||||
skipsVnetHdr: skipsVnetHdr,
|
||||
}
|
||||
niov := len(b.views)
|
||||
if b.skipsVnetHdr {
|
||||
niov++
|
||||
}
|
||||
b.iovecs = make([]unix.Iovec, niov)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) nextIovecs() []unix.Iovec {
|
||||
vnetHdrOff := 0
|
||||
if b.skipsVnetHdr {
|
||||
var vnetHdr [virtioNetHdrSize]byte
|
||||
// The kernel adds virtioNetHdr before each packet, but
|
||||
// we don't use it, so we allocate a buffer for it,
|
||||
// add it in iovecs but don't add it in a view.
|
||||
b.iovecs[0] = unix.Iovec{Base: &vnetHdr[0]}
|
||||
b.iovecs[0].SetLen(virtioNetHdrSize)
|
||||
vnetHdrOff++
|
||||
}
|
||||
|
||||
for i := range b.views {
|
||||
if b.views[i] != nil {
|
||||
break
|
||||
}
|
||||
v := buffer.NewViewSize(b.sizes[i])
|
||||
b.views[i] = v
|
||||
b.iovecs[i+vnetHdrOff] = unix.Iovec{Base: v.BasePtr()}
|
||||
b.iovecs[i+vnetHdrOff].SetLen(v.Size())
|
||||
}
|
||||
return b.iovecs
|
||||
}
|
||||
|
||||
// pullBuffer extracts the enough underlying storage from b.buffer to hold n
|
||||
// bytes. It removes this storage from b.buffer, returns a new buffer
|
||||
// that holds the storage, and updates pulledIndex to indicate which part
|
||||
// of b.buffer's storage must be reallocated during the next call to
|
||||
// nextIovecs.
|
||||
func (b *iovecBuffer) pullBuffer(n int) buffer.Buffer {
|
||||
var views []*buffer.View
|
||||
c := 0
|
||||
if b.skipsVnetHdr {
|
||||
c += virtioNetHdrSize
|
||||
if c >= n {
|
||||
// Nothing in the packet.
|
||||
return buffer.Buffer{}
|
||||
}
|
||||
}
|
||||
// Remove the used views from the buffer.
|
||||
for i, v := range b.views {
|
||||
c += v.Size()
|
||||
if c >= n {
|
||||
b.views[i].CapLength(v.Size() - (c - n))
|
||||
views = append(views, b.views[:i+1]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range views {
|
||||
b.views[i] = nil
|
||||
}
|
||||
if b.skipsVnetHdr {
|
||||
// Exclude the size of the vnet header.
|
||||
n -= virtioNetHdrSize
|
||||
}
|
||||
pulled := buffer.Buffer{}
|
||||
for _, v := range views {
|
||||
pulled.Append(v)
|
||||
}
|
||||
pulled.Truncate(int64(n))
|
||||
return pulled
|
||||
}
|
||||
|
||||
func (b *iovecBuffer) release() {
|
||||
for _, v := range b.views {
|
||||
if v != nil {
|
||||
v.Release()
|
||||
v = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readVDispatcher uses readv() system call to read inbound packets and
|
||||
// dispatches them.
|
||||
//
|
||||
// +stateify savable
|
||||
type readVDispatcher struct {
|
||||
stopfd.StopFD
|
||||
// fd is the file descriptor used to send and receive packets.
|
||||
fd int
|
||||
|
||||
// e is the endpoint this dispatcher is attached to.
|
||||
e *endpoint
|
||||
|
||||
// buf is the iovec buffer that contains the packet contents.
|
||||
buf *iovecBuffer
|
||||
|
||||
// mgr is the processor goroutine manager.
|
||||
mgr *processorManager
|
||||
}
|
||||
|
||||
func newReadVDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
|
||||
stopFD, err := stopfd.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &readVDispatcher{
|
||||
StopFD: stopFD,
|
||||
fd: fd,
|
||||
e: e,
|
||||
}
|
||||
skipsVnetHdr := d.e.gsoKind == stack.HostGSOSupported
|
||||
d.buf = newIovecBuffer(BufConfig, skipsVnetHdr)
|
||||
d.mgr = newProcessorManager(opts, e)
|
||||
d.mgr.start()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *readVDispatcher) release() {
|
||||
d.buf.release()
|
||||
d.mgr.close()
|
||||
}
|
||||
|
||||
// dispatch reads one packet from the file descriptor and dispatches it.
|
||||
func (d *readVDispatcher) dispatch() (bool, tcpip.Error) {
|
||||
n, errno := rawfile.BlockingReadvUntilStopped(d.EFD, d.fd, d.buf.nextIovecs())
|
||||
if n <= 0 || errno != 0 {
|
||||
return false, tcpip.TranslateErrno(errno)
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: d.buf.pullBuffer(n),
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
|
||||
d.e.mu.RLock()
|
||||
addr := d.e.addr
|
||||
d.e.mu.RUnlock()
|
||||
if !d.e.parseInboundHeader(pkt, addr) {
|
||||
return false, nil
|
||||
}
|
||||
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
|
||||
d.mgr.wakeReady()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// recvMMsgDispatcher uses the recvmmsg system call to read inbound packets and
|
||||
// dispatches them.
|
||||
//
|
||||
// +stateify savable
|
||||
type recvMMsgDispatcher struct {
|
||||
stopfd.StopFD
|
||||
// fd is the file descriptor used to send and receive packets.
|
||||
fd int
|
||||
|
||||
// e is the endpoint this dispatcher is attached to.
|
||||
e *endpoint
|
||||
|
||||
// bufs is an array of iovec buffers that contain packet contents.
|
||||
bufs []*iovecBuffer
|
||||
|
||||
// msgHdrs is an array of MMsgHdr objects where each MMsghdr is used to
|
||||
// reference an array of iovecs in the iovecs field defined above. This
|
||||
// array is passed as the parameter to recvmmsg call to retrieve
|
||||
// potentially more than 1 packet per unix.
|
||||
msgHdrs []rawfile.MMsgHdr `state:"nosave"`
|
||||
|
||||
// pkts is reused to avoid allocations.
|
||||
pkts stack.PacketBufferList
|
||||
|
||||
// gro coalesces incoming packets to increase throughput.
|
||||
gro gro.GRO
|
||||
|
||||
// mgr is the processor goroutine manager.
|
||||
mgr *processorManager
|
||||
}
|
||||
|
||||
const (
|
||||
// MaxMsgsPerRecv is the maximum number of packets we want to retrieve
|
||||
// in a single RecvMMsg call.
|
||||
MaxMsgsPerRecv = 8
|
||||
)
|
||||
|
||||
func newRecvMMsgDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher, error) {
|
||||
stopFD, err := stopfd.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &recvMMsgDispatcher{
|
||||
StopFD: stopFD,
|
||||
fd: fd,
|
||||
e: e,
|
||||
bufs: make([]*iovecBuffer, MaxMsgsPerRecv),
|
||||
msgHdrs: make([]rawfile.MMsgHdr, MaxMsgsPerRecv),
|
||||
}
|
||||
skipsVnetHdr := d.e.gsoKind == stack.HostGSOSupported
|
||||
for i := range d.bufs {
|
||||
d.bufs[i] = newIovecBuffer(BufConfig, skipsVnetHdr)
|
||||
}
|
||||
d.gro.Init(opts.GRO)
|
||||
d.mgr = newProcessorManager(opts, e)
|
||||
d.mgr.start()
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *recvMMsgDispatcher) release() {
|
||||
for _, iov := range d.bufs {
|
||||
iov.release()
|
||||
}
|
||||
d.mgr.close()
|
||||
}
|
||||
|
||||
// recvMMsgDispatch reads more than one packet at a time from the file
|
||||
// descriptor and dispatches it.
|
||||
func (d *recvMMsgDispatcher) dispatch() (bool, tcpip.Error) {
|
||||
// Fill message headers.
|
||||
for k := range d.msgHdrs {
|
||||
if d.msgHdrs[k].Msg.Iovlen > 0 {
|
||||
break
|
||||
}
|
||||
iovecs := d.bufs[k].nextIovecs()
|
||||
iovLen := len(iovecs)
|
||||
d.msgHdrs[k].Len = 0
|
||||
d.msgHdrs[k].Msg.Iov = &iovecs[0]
|
||||
d.msgHdrs[k].Msg.SetIovlen(iovLen)
|
||||
}
|
||||
|
||||
nMsgs, errno := rawfile.BlockingRecvMMsgUntilStopped(d.EFD, d.fd, d.msgHdrs)
|
||||
if errno != 0 {
|
||||
return false, tcpip.TranslateErrno(errno)
|
||||
}
|
||||
if nMsgs == -1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process each of received packets.
|
||||
|
||||
d.e.mu.RLock()
|
||||
addr := d.e.addr
|
||||
dsp := d.e.dispatcher
|
||||
d.e.mu.RUnlock()
|
||||
|
||||
d.gro.Dispatcher = dsp
|
||||
defer d.pkts.Reset()
|
||||
|
||||
for k := 0; k < nMsgs; k++ {
|
||||
n := int(d.msgHdrs[k].Len)
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: d.bufs[k].pullBuffer(n),
|
||||
})
|
||||
d.pkts.PushBack(pkt)
|
||||
|
||||
// Mark that this iovec has been processed.
|
||||
d.msgHdrs[k].Msg.Iovlen = 0
|
||||
|
||||
if d.e.parseInboundHeader(pkt, addr) {
|
||||
pkt.RXChecksumValidated = d.e.caps&stack.CapabilityRXChecksumOffload != 0
|
||||
d.mgr.queuePacket(pkt, d.e.hdrSize > 0)
|
||||
}
|
||||
}
|
||||
d.mgr.wakeReady()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
64
pkg/tcpip/link/fdbased/processor_mutex.go
Normal file
64
pkg/tcpip/link/fdbased/processor_mutex.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package fdbased
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// Mutex is sync.Mutex with the correctness validator.
|
||||
type processorMutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var processorprefixIndex *locking.MutexClass
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var processorlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type processorlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *processorMutex) Lock() {
|
||||
locking.AddGLock(processorprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *processorMutex) NestedLock(i processorlockNameIndex) {
|
||||
locking.AddGLock(processorprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *processorMutex) Unlock() {
|
||||
locking.DelGLock(processorprefixIndex, -1)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *processorMutex) NestedUnlock(i processorlockNameIndex) {
|
||||
locking.DelGLock(processorprefixIndex, int(i))
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func processorinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
processorinitLockNames()
|
||||
processorprefixIndex = locking.NewMutexClass(reflect.TypeOf(processorMutex{}), processorlockNames)
|
||||
}
|
||||
278
pkg/tcpip/link/fdbased/processors.go
Normal file
278
pkg/tcpip/link/fdbased/processors.go
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// Copyright 2024 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/rand"
|
||||
"github.com/sagernet/gvisor/pkg/sleep"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/hash/jenkins"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack/gro"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type processor struct {
|
||||
mu processorMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
pkts stack.PacketBufferList
|
||||
|
||||
e *endpoint
|
||||
gro gro.GRO
|
||||
sleeper sleep.Sleeper
|
||||
packetWaker sleep.Waker
|
||||
closeWaker sleep.Waker
|
||||
}
|
||||
|
||||
func (p *processor) start(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
defer p.sleeper.Done()
|
||||
for {
|
||||
switch w := p.sleeper.Fetch(true); {
|
||||
case w == &p.packetWaker:
|
||||
p.deliverPackets()
|
||||
case w == &p.closeWaker:
|
||||
p.mu.Lock()
|
||||
p.pkts.Reset()
|
||||
p.mu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *processor) deliverPackets() {
|
||||
p.e.mu.RLock()
|
||||
p.gro.Dispatcher = p.e.dispatcher
|
||||
p.e.mu.RUnlock()
|
||||
if p.gro.Dispatcher == nil {
|
||||
p.mu.Lock()
|
||||
p.pkts.Reset()
|
||||
p.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
for p.pkts.Len() > 0 {
|
||||
pkt := p.pkts.PopFront()
|
||||
p.mu.Unlock()
|
||||
p.gro.Enqueue(pkt)
|
||||
pkt.DecRef()
|
||||
p.mu.Lock()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
p.gro.Flush()
|
||||
}
|
||||
|
||||
// processorManager handles starting, closing, and queuing packets on processor
|
||||
// goroutines.
|
||||
//
|
||||
// +stateify savable
|
||||
type processorManager struct {
|
||||
processors []processor
|
||||
seed uint32
|
||||
wg sync.WaitGroup `state:"nosave"`
|
||||
e *endpoint
|
||||
ready []bool
|
||||
}
|
||||
|
||||
// newProcessorManager creates a new processor manager.
|
||||
func newProcessorManager(opts *Options, e *endpoint) *processorManager {
|
||||
m := &processorManager{}
|
||||
m.seed = rand.Uint32()
|
||||
m.ready = make([]bool, opts.ProcessorsPerChannel)
|
||||
m.processors = make([]processor, opts.ProcessorsPerChannel)
|
||||
m.e = e
|
||||
m.wg.Add(opts.ProcessorsPerChannel)
|
||||
|
||||
for i := range m.processors {
|
||||
p := &m.processors[i]
|
||||
p.sleeper.AddWaker(&p.packetWaker)
|
||||
p.sleeper.AddWaker(&p.closeWaker)
|
||||
p.gro.Init(opts.GRO)
|
||||
p.e = e
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// start starts the processor goroutines if the processor manager is configured
|
||||
// with more than one processor.
|
||||
func (m *processorManager) start() {
|
||||
for i := range m.processors {
|
||||
p := &m.processors[i]
|
||||
// Only start processor in a separate goroutine if we have multiple of them.
|
||||
if len(m.processors) > 1 {
|
||||
go p.start(&m.wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (m *processorManager) afterLoad(context.Context) {
|
||||
m.wg.Add(len(m.processors))
|
||||
m.start()
|
||||
}
|
||||
|
||||
func (m *processorManager) connectionHash(cid *connectionID) uint32 {
|
||||
var payload [4]byte
|
||||
binary.LittleEndian.PutUint16(payload[0:], cid.srcPort)
|
||||
binary.LittleEndian.PutUint16(payload[2:], cid.dstPort)
|
||||
|
||||
h := jenkins.Sum32(m.seed)
|
||||
h.Write(payload[:])
|
||||
h.Write(cid.srcAddr)
|
||||
h.Write(cid.dstAddr)
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
// queuePacket queues a packet to be delivered to the appropriate processor.
|
||||
func (m *processorManager) queuePacket(pkt *stack.PacketBuffer, hasEthHeader bool) {
|
||||
var pIdx uint32
|
||||
cid, nonConnectionPkt := tcpipConnectionID(pkt)
|
||||
if !hasEthHeader {
|
||||
if nonConnectionPkt {
|
||||
// If there's no eth header this should be a standard tcpip packet. If
|
||||
// it isn't the packet is invalid so drop it.
|
||||
return
|
||||
}
|
||||
pkt.NetworkProtocolNumber = cid.proto
|
||||
}
|
||||
if len(m.processors) == 1 || nonConnectionPkt {
|
||||
// If the packet is not associated with an active connection, use the
|
||||
// first processor.
|
||||
pIdx = 0
|
||||
} else {
|
||||
pIdx = m.connectionHash(&cid) % uint32(len(m.processors))
|
||||
}
|
||||
p := &m.processors[pIdx]
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.pkts.PushBack(pkt.IncRef())
|
||||
m.ready[pIdx] = true
|
||||
}
|
||||
|
||||
type connectionID struct {
|
||||
srcAddr, dstAddr []byte
|
||||
srcPort, dstPort uint16
|
||||
proto tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// tcpipConnectionID returns a tcpip connection id tuple based on the data found
|
||||
// in the packet. It returns true if the packet is not associated with an active
|
||||
// connection (e.g ARP, NDP, etc). The method assumes link headers have already
|
||||
// been processed if they were present.
|
||||
func tcpipConnectionID(pkt *stack.PacketBuffer) (connectionID, bool) {
|
||||
var cid connectionID
|
||||
h, ok := pkt.Data().PullUp(1)
|
||||
if !ok {
|
||||
// Skip this packet.
|
||||
return cid, true
|
||||
}
|
||||
|
||||
const tcpSrcDstPortLen = 4
|
||||
switch header.IPVersion(h) {
|
||||
case header.IPv4Version:
|
||||
hdrLen := header.IPv4(h).HeaderLength()
|
||||
h, ok = pkt.Data().PullUp(int(hdrLen) + tcpSrcDstPortLen)
|
||||
if !ok {
|
||||
return cid, true
|
||||
}
|
||||
ipHdr := header.IPv4(h[:hdrLen])
|
||||
tcpHdr := header.TCP(h[hdrLen:][:tcpSrcDstPortLen])
|
||||
|
||||
cid.srcAddr = ipHdr.SourceAddressSlice()
|
||||
cid.dstAddr = ipHdr.DestinationAddressSlice()
|
||||
// All fragment packets need to be processed by the same goroutine, so
|
||||
// only record the TCP ports if this is not a fragment packet.
|
||||
if ipHdr.IsValid(pkt.Data().Size()) && !ipHdr.More() && ipHdr.FragmentOffset() == 0 {
|
||||
cid.srcPort = tcpHdr.SourcePort()
|
||||
cid.dstPort = tcpHdr.DestinationPort()
|
||||
}
|
||||
cid.proto = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
h, ok = pkt.Data().PullUp(header.IPv6FixedHeaderSize + tcpSrcDstPortLen)
|
||||
if !ok {
|
||||
return cid, true
|
||||
}
|
||||
ipHdr := header.IPv6(h)
|
||||
|
||||
var tcpHdr header.TCP
|
||||
if tcpip.TransportProtocolNumber(ipHdr.NextHeader()) == header.TCPProtocolNumber {
|
||||
tcpHdr = header.TCP(h[header.IPv6FixedHeaderSize:][:tcpSrcDstPortLen])
|
||||
} else {
|
||||
// Slow path for IPv6 extension headers :(.
|
||||
dataBuf := pkt.Data().ToBuffer()
|
||||
dataBuf.TrimFront(header.IPv6MinimumSize)
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), dataBuf)
|
||||
defer it.Release()
|
||||
for {
|
||||
hdr, done, err := it.Next()
|
||||
if done || err != nil {
|
||||
break
|
||||
}
|
||||
hdr.Release()
|
||||
}
|
||||
h, ok = pkt.Data().PullUp(int(it.HeaderOffset()) + tcpSrcDstPortLen)
|
||||
if !ok {
|
||||
return cid, true
|
||||
}
|
||||
tcpHdr = header.TCP(h[it.HeaderOffset():][:tcpSrcDstPortLen])
|
||||
}
|
||||
cid.srcAddr = ipHdr.SourceAddressSlice()
|
||||
cid.dstAddr = ipHdr.DestinationAddressSlice()
|
||||
cid.srcPort = tcpHdr.SourcePort()
|
||||
cid.dstPort = tcpHdr.DestinationPort()
|
||||
cid.proto = header.IPv6ProtocolNumber
|
||||
default:
|
||||
return cid, true
|
||||
}
|
||||
return cid, false
|
||||
}
|
||||
|
||||
func (m *processorManager) close() {
|
||||
if len(m.processors) < 2 {
|
||||
return
|
||||
}
|
||||
for i := range m.processors {
|
||||
p := &m.processors[i]
|
||||
p.closeWaker.Assert()
|
||||
}
|
||||
}
|
||||
|
||||
// wakeReady wakes up all processors that have a packet queued. If there is only
|
||||
// one processor, the method delivers the packet inline without waking a
|
||||
// goroutine.
|
||||
func (m *processorManager) wakeReady() {
|
||||
for i, ready := range m.ready {
|
||||
if !ready {
|
||||
continue
|
||||
}
|
||||
p := &m.processors[i]
|
||||
if len(m.processors) > 1 {
|
||||
p.packetWaker.Assert()
|
||||
} else {
|
||||
p.deliverPackets()
|
||||
}
|
||||
m.ready[i] = false
|
||||
}
|
||||
}
|
||||
26
pkg/tcpip/link/fdbased/save_restore.go
Normal file
26
pkg/tcpip/link/fdbased/save_restore.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2024 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 fdbased
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
)
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (r *recvMMsgDispatcher) afterLoad(context.Context) {
|
||||
r.msgHdrs = make([]rawfile.MMsgHdr, MaxMsgsPerRecv)
|
||||
}
|
||||
96
pkg/tcpip/link/loopback/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/loopback/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package loopback
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
146
pkg/tcpip/link/loopback/loopback.go
Normal file
146
pkg/tcpip/link/loopback/loopback.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// 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 loopback provides the implementation of loopback data-link layer
|
||||
// endpoints. Such endpoints just turn outbound packets into inbound ones.
|
||||
//
|
||||
// Loopback 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 loopback
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
loopbackMTU = 65536
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type endpoint struct {
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
dispatcher stack.NetworkDispatcher
|
||||
// +checklocks:mu
|
||||
addr tcpip.LinkAddress
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
// New creates a new loopback endpoint. This link-layer endpoint just turns
|
||||
// outbound packets into inbound packets.
|
||||
func New() stack.LinkEndpoint {
|
||||
return &endpoint{
|
||||
mtu: loopbackMTU,
|
||||
}
|
||||
}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.Attach. It just saves the stack network-
|
||||
// layer dispatcher for later use when packets need to be dispatched.
|
||||
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
||||
func (e *endpoint) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.MTU.
|
||||
func (e *endpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.SetMTU. It has no impact.
|
||||
func (e *endpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities. Loopback advertises
|
||||
// itself as supporting checksum offload, but in reality it's just omitted.
|
||||
func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload | stack.CapabilityTXChecksumOffload | stack.CapabilitySaveRestore | stack.CapabilityLoopback
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. Given that the
|
||||
// loopback interface doesn't have a header, it just returns 0.
|
||||
func (*endpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// LinkAddress returns the link address of this endpoint.
|
||||
func (e *endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.addr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.addr = addr
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait.
|
||||
func (*endpoint) Wait() {}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.WritePackets. If the endpoint is
|
||||
// not attached, the packets are not delivered.
|
||||
func (e *endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
e.mu.RLock()
|
||||
d := e.dispatcher
|
||||
e.mu.RUnlock()
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
// In order to properly loop back to the inbound side we must create a
|
||||
// fresh packet that only contains the underlying payload with no headers
|
||||
// or struct fields set.
|
||||
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: pkt.ToBuffer(),
|
||||
})
|
||||
if d != nil {
|
||||
d.DeliverNetworkPacket(pkt.NetworkProtocolNumber, newPkt)
|
||||
}
|
||||
newPkt.DecRef()
|
||||
}
|
||||
return pkts.Len(), nil
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
|
||||
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareLoopback
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.
|
||||
func (*endpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.
|
||||
func (*endpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
|
||||
// Close implements stack.LinkEndpoint.
|
||||
func (*endpoint) Close() {}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.
|
||||
func (*endpoint) SetOnCloseAction(func()) {}
|
||||
44
pkg/tcpip/link/loopback/loopback_state_autogen.go
Normal file
44
pkg/tcpip/link/loopback/loopback_state_autogen.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package loopback
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (e *endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/loopback.endpoint"
|
||||
}
|
||||
|
||||
func (e *endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"dispatcher",
|
||||
"addr",
|
||||
"mtu",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.dispatcher)
|
||||
stateSinkObject.Save(1, &e.addr)
|
||||
stateSinkObject.Save(2, &e.mtu)
|
||||
}
|
||||
|
||||
func (e *endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.dispatcher)
|
||||
stateSourceObject.Load(1, &e.addr)
|
||||
stateSourceObject.Load(2, &e.mtu)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*endpoint)(nil))
|
||||
}
|
||||
96
pkg/tcpip/link/muxed/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/muxed/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package muxed
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
174
pkg/tcpip/link/muxed/injectable.go
Normal file
174
pkg/tcpip/link/muxed/injectable.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2019 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 muxed provides a muxed link endpoints.
|
||||
package muxed
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// InjectableEndpoint is an injectable multi endpoint. The endpoint has
|
||||
// trivial routing rules that determine which InjectableEndpoint a given packet
|
||||
// will be written to. Note that HandleLocal works differently for this
|
||||
// endpoint (see WritePacket).
|
||||
//
|
||||
// +stateify savable
|
||||
type InjectableEndpoint struct {
|
||||
routes map[tcpip.Address]stack.InjectableLinkEndpoint
|
||||
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
dispatcher stack.NetworkDispatcher
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) MTU() uint32 {
|
||||
minMTU := ^uint32(0)
|
||||
for _, endpoint := range m.routes {
|
||||
if endpointMTU := endpoint.MTU(); endpointMTU < minMTU {
|
||||
minMTU = endpointMTU
|
||||
}
|
||||
}
|
||||
return minMTU
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) SetMTU(mtu uint32) {
|
||||
for _, endpoint := range m.routes {
|
||||
endpoint.SetMTU(mtu)
|
||||
}
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
minCapabilities := stack.LinkEndpointCapabilities(^uint(0))
|
||||
for _, endpoint := range m.routes {
|
||||
minCapabilities &= endpoint.Capabilities()
|
||||
}
|
||||
return minCapabilities
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) MaxHeaderLength() uint16 {
|
||||
minHeaderLen := ^uint16(0)
|
||||
for _, endpoint := range m.routes {
|
||||
if headerLen := endpoint.MaxHeaderLength(); headerLen < minHeaderLen {
|
||||
minHeaderLen = headerLen
|
||||
}
|
||||
}
|
||||
return minHeaderLen
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (m *InjectableEndpoint) SetLinkAddress(tcpip.LinkAddress) {}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
for _, endpoint := range m.routes {
|
||||
endpoint.Attach(dispatcher)
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.dispatcher = dispatcher
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.
|
||||
func (m *InjectableEndpoint) IsAttached() bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.dispatcher != nil
|
||||
}
|
||||
|
||||
// InjectInbound implements stack.InjectableLinkEndpoint.
|
||||
func (m *InjectableEndpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
m.mu.RLock()
|
||||
d := m.dispatcher
|
||||
m.mu.RUnlock()
|
||||
d.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
|
||||
// WritePackets writes outbound packets to the appropriate
|
||||
// LinkInjectableEndpoint based on the RemoteAddress. HandleLocal only works if
|
||||
// pkt.EgressRoute.RemoteAddress has a route registered in this endpoint.
|
||||
func (m *InjectableEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
i := 0
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
endpoint, ok := m.routes[pkt.EgressRoute.RemoteAddress]
|
||||
if !ok {
|
||||
return i, &tcpip.ErrHostUnreachable{}
|
||||
}
|
||||
|
||||
var tmpPkts stack.PacketBufferList
|
||||
tmpPkts.PushBack(pkt)
|
||||
|
||||
n, err := endpoint.WritePackets(tmpPkts)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
||||
i += n
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// InjectOutbound writes outbound packets to the appropriate
|
||||
// LinkInjectableEndpoint based on the dest address.
|
||||
func (m *InjectableEndpoint) InjectOutbound(dest tcpip.Address, packet *buffer.View) tcpip.Error {
|
||||
endpoint, ok := m.routes[dest]
|
||||
if !ok {
|
||||
return &tcpip.ErrHostUnreachable{}
|
||||
}
|
||||
return endpoint.InjectOutbound(dest, packet)
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait.
|
||||
func (m *InjectableEndpoint) Wait() {
|
||||
for _, ep := range m.routes {
|
||||
ep.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
|
||||
func (*InjectableEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
panic("unsupported operation")
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (*InjectableEndpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (*InjectableEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
|
||||
// Close implements stack.LinkEndpoint.
|
||||
func (*InjectableEndpoint) Close() {}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
|
||||
func (*InjectableEndpoint) SetOnCloseAction(func()) {}
|
||||
|
||||
// NewInjectableEndpoint creates a new multi-endpoint injectable endpoint.
|
||||
func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) *InjectableEndpoint {
|
||||
return &InjectableEndpoint{
|
||||
routes: routes,
|
||||
}
|
||||
}
|
||||
41
pkg/tcpip/link/muxed/muxed_state_autogen.go
Normal file
41
pkg/tcpip/link/muxed/muxed_state_autogen.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package muxed
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (m *InjectableEndpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/muxed.InjectableEndpoint"
|
||||
}
|
||||
|
||||
func (m *InjectableEndpoint) StateFields() []string {
|
||||
return []string{
|
||||
"routes",
|
||||
"dispatcher",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InjectableEndpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *InjectableEndpoint) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.routes)
|
||||
stateSinkObject.Save(1, &m.dispatcher)
|
||||
}
|
||||
|
||||
func (m *InjectableEndpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *InjectableEndpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.routes)
|
||||
stateSourceObject.Load(1, &m.dispatcher)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*InjectableEndpoint)(nil))
|
||||
}
|
||||
185
pkg/tcpip/link/nested/nested.go
Normal file
185
pkg/tcpip/link/nested/nested.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2020 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 nested provides helpers to implement the pattern of nested
|
||||
// stack.LinkEndpoints.
|
||||
package nested
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// Endpoint is a wrapper around stack.LinkEndpoint and stack.NetworkDispatcher
|
||||
// that can be used to implement nesting safely by providing lifecycle
|
||||
// concurrency guards.
|
||||
//
|
||||
// See the tests in this package for example usage.
|
||||
//
|
||||
// +stateify savable
|
||||
type Endpoint struct {
|
||||
child stack.LinkEndpoint
|
||||
embedder stack.NetworkDispatcher
|
||||
|
||||
// mu protects dispatcher.
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
dispatcher stack.NetworkDispatcher
|
||||
}
|
||||
|
||||
var (
|
||||
_ stack.GSOEndpoint = (*Endpoint)(nil)
|
||||
_ stack.LinkEndpoint = (*Endpoint)(nil)
|
||||
_ stack.NetworkDispatcher = (*Endpoint)(nil)
|
||||
)
|
||||
|
||||
// Init initializes a nested.Endpoint that uses embedder as the dispatcher for
|
||||
// child on Attach.
|
||||
//
|
||||
// See the tests in this package for example usage.
|
||||
func (e *Endpoint) Init(child stack.LinkEndpoint, embedder stack.NetworkDispatcher) {
|
||||
e.child = child
|
||||
e.embedder = embedder
|
||||
}
|
||||
|
||||
// DeliverNetworkPacket implements stack.NetworkDispatcher.
|
||||
func (e *Endpoint) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
d := e.dispatcher
|
||||
e.mu.RUnlock()
|
||||
if d != nil {
|
||||
d.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// DeliverLinkPacket implements stack.NetworkDispatcher.
|
||||
func (e *Endpoint) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
d := e.dispatcher
|
||||
e.mu.RUnlock()
|
||||
if d != nil {
|
||||
d.DeliverLinkPacket(protocol, pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
e.dispatcher = dispatcher
|
||||
e.mu.Unlock()
|
||||
// If we're attaching to a valid dispatcher, pass embedder as the dispatcher
|
||||
// to our child, otherwise detach the child by giving it a nil dispatcher.
|
||||
var pass stack.NetworkDispatcher
|
||||
if dispatcher != nil {
|
||||
pass = e.embedder
|
||||
}
|
||||
e.child.Attach(pass)
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
isAttached := e.dispatcher != nil
|
||||
e.mu.RUnlock()
|
||||
return isAttached
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MTU() uint32 {
|
||||
return e.child.MTU()
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) SetMTU(mtu uint32) {
|
||||
e.child.SetMTU(mtu)
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return e.child.Capabilities()
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MaxHeaderLength() uint16 {
|
||||
return e.child.MaxHeaderLength()
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return e.child.LinkAddress()
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *Endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.child.SetLinkAddress(addr)
|
||||
}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
return e.child.WritePackets(pkts)
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Wait() {
|
||||
e.child.Wait()
|
||||
}
|
||||
|
||||
// GSOMaxSize implements stack.GSOEndpoint.
|
||||
func (e *Endpoint) GSOMaxSize() uint32 {
|
||||
if e, ok := e.child.(stack.GSOEndpoint); ok {
|
||||
return e.GSOMaxSize()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SupportedGSO implements stack.GSOEndpoint.
|
||||
func (e *Endpoint) SupportedGSO() stack.SupportedGSO {
|
||||
if e, ok := e.child.(stack.GSOEndpoint); ok {
|
||||
return e.SupportedGSO()
|
||||
}
|
||||
return stack.GSONotSupported
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
|
||||
func (e *Endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return e.child.ARPHardwareType()
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (e *Endpoint) AddHeader(pkt *stack.PacketBuffer) {
|
||||
e.child.AddHeader(pkt)
|
||||
}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (e *Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
||||
return e.child.ParseHeader(pkt)
|
||||
}
|
||||
|
||||
// Close implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Close() {
|
||||
e.child.Close()
|
||||
}
|
||||
|
||||
// SetOnCloseAction implement stack.LinkEndpoints.
|
||||
func (e *Endpoint) SetOnCloseAction(action func()) {
|
||||
e.child.SetOnCloseAction(action)
|
||||
}
|
||||
|
||||
// Child returns the child endpoint.
|
||||
func (e *Endpoint) Child() stack.LinkEndpoint {
|
||||
return e.child
|
||||
}
|
||||
44
pkg/tcpip/link/nested/nested_state_autogen.go
Normal file
44
pkg/tcpip/link/nested/nested_state_autogen.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package nested
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (e *Endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/nested.Endpoint"
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"child",
|
||||
"embedder",
|
||||
"dispatcher",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.child)
|
||||
stateSinkObject.Save(1, &e.embedder)
|
||||
stateSinkObject.Save(2, &e.dispatcher)
|
||||
}
|
||||
|
||||
func (e *Endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.child)
|
||||
stateSourceObject.Load(1, &e.embedder)
|
||||
stateSourceObject.Load(2, &e.dispatcher)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*Endpoint)(nil))
|
||||
}
|
||||
62
pkg/tcpip/link/packetsocket/packetsocket.go
Normal file
62
pkg/tcpip/link/packetsocket/packetsocket.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2022 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 packetsocket provides a link endpoint that enables delivery of
|
||||
// incoming and outgoing packets to any interested packet sockets.
|
||||
package packetsocket
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/nested"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var (
|
||||
_ stack.NetworkDispatcher = (*Endpoint)(nil)
|
||||
_ stack.LinkEndpoint = (*Endpoint)(nil)
|
||||
)
|
||||
|
||||
// Endpoint is a link endpoint that enables delivery of incoming and outgoing
|
||||
// packets to any interested packet sockets.
|
||||
//
|
||||
// +stateify savable
|
||||
type Endpoint struct {
|
||||
nested.Endpoint
|
||||
}
|
||||
|
||||
// New creates a new packetsocket link endpoint wrapping a lower link endpoint.
|
||||
//
|
||||
// On ingress, the lower link endpoint must only deliver packets that have
|
||||
// a link-layer header set if one is required for the link.
|
||||
func New(lower stack.LinkEndpoint) stack.LinkEndpoint {
|
||||
e := &Endpoint{}
|
||||
e.Endpoint.Init(lower, e)
|
||||
return e
|
||||
}
|
||||
|
||||
// DeliverNetworkPacket implements stack.NetworkDispatcher.
|
||||
func (e *Endpoint) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
e.Endpoint.DeliverLinkPacket(protocol, pkt)
|
||||
|
||||
e.Endpoint.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
e.Endpoint.DeliverLinkPacket(pkt.NetworkProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
return e.Endpoint.WritePackets(pkts)
|
||||
}
|
||||
38
pkg/tcpip/link/packetsocket/packetsocket_state_autogen.go
Normal file
38
pkg/tcpip/link/packetsocket/packetsocket_state_autogen.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package packetsocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (e *Endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/packetsocket.Endpoint"
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"Endpoint",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.Endpoint)
|
||||
}
|
||||
|
||||
func (e *Endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.Endpoint)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*Endpoint)(nil))
|
||||
}
|
||||
96
pkg/tcpip/link/pipe/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/pipe/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package pipe
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
154
pkg/tcpip/link/pipe/pipe.go
Normal file
154
pkg/tcpip/link/pipe/pipe.go
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2020 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 pipe provides the implementation of pipe-like data-link layer
|
||||
// endpoints. Such endpoints allow packets to be sent between two interfaces.
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var _ stack.LinkEndpoint = (*Endpoint)(nil)
|
||||
|
||||
// New returns both ends of a new pipe.
|
||||
func New(linkAddr1, linkAddr2 tcpip.LinkAddress, mtu uint32) (*Endpoint, *Endpoint) {
|
||||
ep1 := &Endpoint{
|
||||
linkAddr: linkAddr1,
|
||||
mtu: mtu,
|
||||
}
|
||||
ep2 := &Endpoint{
|
||||
linkAddr: linkAddr2,
|
||||
mtu: mtu,
|
||||
}
|
||||
ep1.linked = ep2
|
||||
ep2.linked = ep1
|
||||
return ep1, ep2
|
||||
}
|
||||
|
||||
// Endpoint is one end of a pipe.
|
||||
//
|
||||
// +stateify savable
|
||||
type Endpoint struct {
|
||||
linked *Endpoint
|
||||
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
dispatcher stack.NetworkDispatcher
|
||||
// +checklocks:mu
|
||||
linkAddr tcpip.LinkAddress
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
func (e *Endpoint) deliverPackets(pkts stack.PacketBufferList) {
|
||||
e.linked.mu.RLock()
|
||||
d := e.linked.dispatcher
|
||||
e.linked.mu.RUnlock()
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
// Create a fresh packet with pkt's payload but without struct fields
|
||||
// or headers set so the next link protocol can properly set the link
|
||||
// header.
|
||||
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: pkt.ToBuffer(),
|
||||
})
|
||||
d.DeliverNetworkPacket(pkt.NetworkProtocolNumber, newPkt)
|
||||
newPkt.DecRef()
|
||||
}
|
||||
}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := pkts.Len()
|
||||
e.deliverPackets(pkts)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) IsAttached() bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.
|
||||
func (*Endpoint) Wait() {}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
// SetMTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.
|
||||
func (*Endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return 0
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.
|
||||
func (*Endpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.linkAddr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.linkAddr = addr
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.
|
||||
func (*Endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.
|
||||
func (*Endpoint) AddHeader(*stack.PacketBuffer) {}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.
|
||||
func (*Endpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
|
||||
|
||||
// Close implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) Close() {}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
|
||||
func (*Endpoint) SetOnCloseAction(func()) {}
|
||||
47
pkg/tcpip/link/pipe/pipe_state_autogen.go
Normal file
47
pkg/tcpip/link/pipe/pipe_state_autogen.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (e *Endpoint) StateTypeName() string {
|
||||
return "pkg/tcpip/link/pipe.Endpoint"
|
||||
}
|
||||
|
||||
func (e *Endpoint) StateFields() []string {
|
||||
return []string{
|
||||
"linked",
|
||||
"dispatcher",
|
||||
"linkAddr",
|
||||
"mtu",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.linked)
|
||||
stateSinkObject.Save(1, &e.dispatcher)
|
||||
stateSinkObject.Save(2, &e.linkAddr)
|
||||
stateSinkObject.Save(3, &e.mtu)
|
||||
}
|
||||
|
||||
func (e *Endpoint) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *Endpoint) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.linked)
|
||||
stateSourceObject.Load(1, &e.dispatcher)
|
||||
stateSourceObject.Load(2, &e.linkAddr)
|
||||
stateSourceObject.Load(3, &e.mtu)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*Endpoint)(nil))
|
||||
}
|
||||
64
pkg/tcpip/link/qdisc/fifo/dispatcher_mutex.go
Normal file
64
pkg/tcpip/link/qdisc/fifo/dispatcher_mutex.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package fifo
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// Mutex is sync.Mutex with the correctness validator.
|
||||
type queueDispatcherMutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var queueDispatcherprefixIndex *locking.MutexClass
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var queueDispatcherlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type queueDispatcherlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *queueDispatcherMutex) Lock() {
|
||||
locking.AddGLock(queueDispatcherprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueDispatcherMutex) NestedLock(i queueDispatcherlockNameIndex) {
|
||||
locking.AddGLock(queueDispatcherprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *queueDispatcherMutex) Unlock() {
|
||||
locking.DelGLock(queueDispatcherprefixIndex, -1)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueDispatcherMutex) NestedUnlock(i queueDispatcherlockNameIndex) {
|
||||
locking.DelGLock(queueDispatcherprefixIndex, int(i))
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func queueDispatcherinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
queueDispatcherinitLockNames()
|
||||
queueDispatcherprefixIndex = locking.NewMutexClass(reflect.TypeOf(queueDispatcherMutex{}), queueDispatcherlockNames)
|
||||
}
|
||||
158
pkg/tcpip/link/qdisc/fifo/fifo.go
Normal file
158
pkg/tcpip/link/qdisc/fifo/fifo.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2020 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 fifo provides the implementation of FIFO queuing discipline that
|
||||
// queues all outbound packets and asynchronously dispatches them to the
|
||||
// lower link endpoint in the order that they were queued.
|
||||
package fifo
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/sleep"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var _ stack.QueueingDiscipline = (*discipline)(nil)
|
||||
|
||||
const (
|
||||
// BatchSize is the number of packets to write in each syscall. It is 47
|
||||
// because when GVisorGSO is in use then a single 65KB TCP segment can get
|
||||
// split into 46 segments of 1420 bytes and a single 216 byte segment.
|
||||
BatchSize = 47
|
||||
|
||||
qDiscClosed = 1
|
||||
)
|
||||
|
||||
// discipline represents a QueueingDiscipline which implements a FIFO queue for
|
||||
// all outgoing packets. discipline can have 1 or more underlying
|
||||
// queueDispatchers. All outgoing packets are consistently hashed to a single
|
||||
// underlying queue using the PacketBuffer.Hash if set, otherwise all packets
|
||||
// are queued to the first queue to avoid reordering in case of missing hash.
|
||||
//
|
||||
// +stateify savable
|
||||
type discipline struct {
|
||||
wg sync.WaitGroup `state:"nosave"`
|
||||
dispatchers []queueDispatcher
|
||||
|
||||
closed atomicbitops.Int32
|
||||
}
|
||||
|
||||
// queueDispatcher is responsible for dispatching all outbound packets in its
|
||||
// queue. It will also smartly batch packets when possible and write them
|
||||
// through the lower LinkWriter.
|
||||
//
|
||||
// +stateify savable
|
||||
type queueDispatcher struct {
|
||||
lower stack.LinkWriter
|
||||
|
||||
mu queueDispatcherMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
queue packetBufferCircularList
|
||||
|
||||
newPacketWaker sleep.Waker `state:"nosave"`
|
||||
closeWaker sleep.Waker `state:"nosave"`
|
||||
}
|
||||
|
||||
// New creates a new fifo queuing discipline with the n queues with maximum
|
||||
// capacity of queueLen.
|
||||
//
|
||||
// +checklocksignore: we don't have to hold locks during initialization.
|
||||
func New(lower stack.LinkWriter, n int, queueLen int) stack.QueueingDiscipline {
|
||||
d := &discipline{
|
||||
dispatchers: make([]queueDispatcher, n),
|
||||
}
|
||||
// Create the required dispatchers
|
||||
for i := range d.dispatchers {
|
||||
qd := &d.dispatchers[i]
|
||||
qd.lower = lower
|
||||
qd.queue.init(queueLen)
|
||||
|
||||
d.wg.Add(1)
|
||||
go func() {
|
||||
defer d.wg.Done()
|
||||
qd.dispatchLoop()
|
||||
}()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (qd *queueDispatcher) dispatchLoop() {
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&qd.newPacketWaker)
|
||||
s.AddWaker(&qd.closeWaker)
|
||||
defer s.Done()
|
||||
|
||||
var batch stack.PacketBufferList
|
||||
for {
|
||||
switch w := s.Fetch(true); w {
|
||||
case &qd.newPacketWaker:
|
||||
case &qd.closeWaker:
|
||||
qd.mu.Lock()
|
||||
for p := qd.queue.removeFront(); p != nil; p = qd.queue.removeFront() {
|
||||
p.DecRef()
|
||||
}
|
||||
qd.queue.decRef()
|
||||
qd.mu.Unlock()
|
||||
return
|
||||
default:
|
||||
panic("unknown waker")
|
||||
}
|
||||
qd.mu.Lock()
|
||||
for pkt := qd.queue.removeFront(); pkt != nil; pkt = qd.queue.removeFront() {
|
||||
batch.PushBack(pkt)
|
||||
if batch.Len() < BatchSize && !qd.queue.isEmpty() {
|
||||
continue
|
||||
}
|
||||
qd.mu.Unlock()
|
||||
_, _ = qd.lower.WritePackets(batch)
|
||||
batch.Reset()
|
||||
qd.mu.Lock()
|
||||
}
|
||||
qd.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// WritePacket implements stack.QueueingDiscipline.WritePacket.
|
||||
//
|
||||
// The packet must have the following fields populated:
|
||||
// - pkt.EgressRoute
|
||||
// - pkt.GSOOptions
|
||||
// - pkt.NetworkProtocolNumber
|
||||
func (d *discipline) WritePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
if d.closed.Load() == qDiscClosed {
|
||||
return &tcpip.ErrClosedForSend{}
|
||||
}
|
||||
qd := &d.dispatchers[int(pkt.Hash)%len(d.dispatchers)]
|
||||
qd.mu.Lock()
|
||||
haveSpace := qd.queue.hasSpace()
|
||||
if haveSpace {
|
||||
qd.queue.pushBack(pkt.IncRef())
|
||||
}
|
||||
qd.mu.Unlock()
|
||||
if !haveSpace {
|
||||
return &tcpip.ErrNoBufferSpace{}
|
||||
}
|
||||
qd.newPacketWaker.Assert()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *discipline) Close() {
|
||||
d.closed.Store(qDiscClosed)
|
||||
for i := range d.dispatchers {
|
||||
d.dispatchers[i].closeWaker.Assert()
|
||||
}
|
||||
d.wg.Wait()
|
||||
}
|
||||
102
pkg/tcpip/link/qdisc/fifo/fifo_state_autogen.go
Normal file
102
pkg/tcpip/link/qdisc/fifo/fifo_state_autogen.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package fifo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (d *discipline) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc/fifo.discipline"
|
||||
}
|
||||
|
||||
func (d *discipline) StateFields() []string {
|
||||
return []string{
|
||||
"dispatchers",
|
||||
"closed",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *discipline) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *discipline) StateSave(stateSinkObject state.Sink) {
|
||||
d.beforeSave()
|
||||
stateSinkObject.Save(0, &d.dispatchers)
|
||||
stateSinkObject.Save(1, &d.closed)
|
||||
}
|
||||
|
||||
func (d *discipline) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *discipline) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &d.dispatchers)
|
||||
stateSourceObject.Load(1, &d.closed)
|
||||
}
|
||||
|
||||
func (qd *queueDispatcher) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc/fifo.queueDispatcher"
|
||||
}
|
||||
|
||||
func (qd *queueDispatcher) StateFields() []string {
|
||||
return []string{
|
||||
"lower",
|
||||
"queue",
|
||||
}
|
||||
}
|
||||
|
||||
func (qd *queueDispatcher) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (qd *queueDispatcher) StateSave(stateSinkObject state.Sink) {
|
||||
qd.beforeSave()
|
||||
stateSinkObject.Save(0, &qd.lower)
|
||||
stateSinkObject.Save(1, &qd.queue)
|
||||
}
|
||||
|
||||
func (qd *queueDispatcher) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (qd *queueDispatcher) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &qd.lower)
|
||||
stateSourceObject.Load(1, &qd.queue)
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc/fifo.packetBufferCircularList"
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) StateFields() []string {
|
||||
return []string{
|
||||
"pbs",
|
||||
"head",
|
||||
"size",
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *packetBufferCircularList) StateSave(stateSinkObject state.Sink) {
|
||||
pl.beforeSave()
|
||||
stateSinkObject.Save(0, &pl.pbs)
|
||||
stateSinkObject.Save(1, &pl.head)
|
||||
stateSinkObject.Save(2, &pl.size)
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *packetBufferCircularList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &pl.pbs)
|
||||
stateSourceObject.Load(1, &pl.head)
|
||||
stateSourceObject.Load(2, &pl.size)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*discipline)(nil))
|
||||
state.Register((*queueDispatcher)(nil))
|
||||
state.Register((*packetBufferCircularList)(nil))
|
||||
}
|
||||
93
pkg/tcpip/link/qdisc/fifo/packet_buffer_circular_list.go
Normal file
93
pkg/tcpip/link/qdisc/fifo/packet_buffer_circular_list.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2022 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 fifo
|
||||
|
||||
import "github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
|
||||
// packetBufferCircularList is a slice-backed circular list. All operations are
|
||||
// O(1) unless otherwise noted. It only allocates once, during the call to
|
||||
// init().
|
||||
//
|
||||
// Users should call init() before using packetBufferCircularList.
|
||||
//
|
||||
// +stateify savable
|
||||
type packetBufferCircularList struct {
|
||||
pbs []*stack.PacketBuffer
|
||||
head int
|
||||
size int
|
||||
}
|
||||
|
||||
// init initializes the list with the given size.
|
||||
func (pl *packetBufferCircularList) init(size int) {
|
||||
pl.pbs = make([]*stack.PacketBuffer, size)
|
||||
}
|
||||
|
||||
// length returns the number of elements in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) length() int {
|
||||
return pl.size
|
||||
}
|
||||
|
||||
// hasSpace returns whether there is space left in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) hasSpace() bool {
|
||||
return pl.size < len(pl.pbs)
|
||||
}
|
||||
|
||||
// isEmpty returns whether the list is empty.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) isEmpty() bool {
|
||||
return pl.size == 0
|
||||
}
|
||||
|
||||
// pushBack inserts the PacketBuffer at the end of the list.
|
||||
//
|
||||
// Users must check beforehand that there is space via a call to hasSpace().
|
||||
// Failing to do so may clobber existing entries.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) pushBack(pb *stack.PacketBuffer) {
|
||||
next := (pl.head + pl.size) % len(pl.pbs)
|
||||
pl.pbs[next] = pb
|
||||
pl.size++
|
||||
}
|
||||
|
||||
// removeFront returns the first element of the list or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) removeFront() *stack.PacketBuffer {
|
||||
if pl.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
ret := pl.pbs[pl.head]
|
||||
pl.pbs[pl.head] = nil
|
||||
pl.head = (pl.head + 1) % len(pl.pbs)
|
||||
pl.size--
|
||||
return ret
|
||||
}
|
||||
|
||||
// decRef decreases the reference count on each stack.PacketBuffer stored in
|
||||
// the list.
|
||||
//
|
||||
// NOTE: runs in O(n) time.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) decRef() {
|
||||
for i := 0; i < pl.size; i++ {
|
||||
pl.pbs[(pl.head+i)%len(pl.pbs)].DecRef()
|
||||
}
|
||||
}
|
||||
96
pkg/tcpip/link/sharedmem/endpoint_mutex.go
Normal file
96
pkg/tcpip/link/sharedmem/endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package sharedmem
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type endpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var endpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type endpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Lock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedLock(i endpointlockNameIndex) {
|
||||
locking.AddGLock(endpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) NestedUnlock(i endpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(endpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLock() {
|
||||
locking.AddGLock(endpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(endpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *endpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var endpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func endpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
}
|
||||
80
pkg/tcpip/link/sharedmem/pipe/pipe.go
Normal file
80
pkg/tcpip/link/sharedmem/pipe/pipe.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// 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 pipe implements a shared memory ring buffer on which a single reader
|
||||
// and a single writer can operate (read/write) concurrently. The ring buffer
|
||||
// allows for data of different sizes to be written, and preserves the boundary
|
||||
// of the written data.
|
||||
//
|
||||
// Example usage is as follows:
|
||||
//
|
||||
// wb := t.Push(20)
|
||||
// // Write data to wb.
|
||||
// t.Flush()
|
||||
//
|
||||
// rb := r.Pull()
|
||||
// // Do something with data in rb.
|
||||
// t.Flush()
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
jump uint64 = math.MaxUint32 + 1
|
||||
offsetMask uint64 = math.MaxUint32
|
||||
revolutionMask uint64 = ^offsetMask
|
||||
|
||||
sizeOfSlotHeader = 8 // sizeof(uint64)
|
||||
slotFree uint64 = 1 << 63
|
||||
slotSizeMask uint64 = math.MaxUint32
|
||||
)
|
||||
|
||||
// payloadToSlotSize calculates the total size of a slot based on its payload
|
||||
// size. The total size is the header size, plus the payload size, plus padding
|
||||
// if necessary to make the total size a multiple of sizeOfSlotHeader.
|
||||
func payloadToSlotSize(payloadSize uint64) uint64 {
|
||||
s := sizeOfSlotHeader + payloadSize
|
||||
return (s + sizeOfSlotHeader - 1) &^ (sizeOfSlotHeader - 1)
|
||||
}
|
||||
|
||||
// slotToPayloadSize calculates the payload size of a slot based on the total
|
||||
// size of the slot. This is only meant to be used when creating slots that
|
||||
// don't carry information (e.g., free slots or wrap slots).
|
||||
func slotToPayloadSize(offset uint64) uint64 {
|
||||
return offset - sizeOfSlotHeader
|
||||
}
|
||||
|
||||
// pipe is a basic data structure used by both (transmit & receive) ends of a
|
||||
// pipe. Indices into this pipe are split into two fields: offset, which counts
|
||||
// the number of bytes from the beginning of the buffer, and revolution, which
|
||||
// counts the number of times the index has wrapped around.
|
||||
//
|
||||
// +stateify savable
|
||||
type pipe struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// init initializes the pipe buffer such that its size is a multiple of the size
|
||||
// of the slot header.
|
||||
func (p *pipe) init(b []byte) {
|
||||
p.buffer = b[:len(b)&^(sizeOfSlotHeader-1)]
|
||||
}
|
||||
|
||||
// data returns a section of the buffer starting at the given index (which may
|
||||
// include revolution information) and with the given size.
|
||||
func (p *pipe) data(idx uint64, size uint64) []byte {
|
||||
return p.buffer[(idx&offsetMask)+sizeOfSlotHeader:][:size]
|
||||
}
|
||||
111
pkg/tcpip/link/sharedmem/pipe/pipe_state_autogen.go
Normal file
111
pkg/tcpip/link/sharedmem/pipe/pipe_state_autogen.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (p *pipe) StateTypeName() string {
|
||||
return "pkg/tcpip/link/sharedmem/pipe.pipe"
|
||||
}
|
||||
|
||||
func (p *pipe) StateFields() []string {
|
||||
return []string{
|
||||
"buffer",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (p *pipe) StateSave(stateSinkObject state.Sink) {
|
||||
p.beforeSave()
|
||||
stateSinkObject.Save(0, &p.buffer)
|
||||
}
|
||||
|
||||
func (p *pipe) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (p *pipe) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &p.buffer)
|
||||
}
|
||||
|
||||
func (r *Rx) StateTypeName() string {
|
||||
return "pkg/tcpip/link/sharedmem/pipe.Rx"
|
||||
}
|
||||
|
||||
func (r *Rx) StateFields() []string {
|
||||
return []string{
|
||||
"p",
|
||||
"tail",
|
||||
"head",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rx) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *Rx) StateSave(stateSinkObject state.Sink) {
|
||||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.p)
|
||||
stateSinkObject.Save(1, &r.tail)
|
||||
stateSinkObject.Save(2, &r.head)
|
||||
}
|
||||
|
||||
func (r *Rx) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *Rx) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.p)
|
||||
stateSourceObject.Load(1, &r.tail)
|
||||
stateSourceObject.Load(2, &r.head)
|
||||
}
|
||||
|
||||
func (t *Tx) StateTypeName() string {
|
||||
return "pkg/tcpip/link/sharedmem/pipe.Tx"
|
||||
}
|
||||
|
||||
func (t *Tx) StateFields() []string {
|
||||
return []string{
|
||||
"p",
|
||||
"maxPayloadSize",
|
||||
"head",
|
||||
"tail",
|
||||
"next",
|
||||
"tailHeader",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tx) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *Tx) StateSave(stateSinkObject state.Sink) {
|
||||
t.beforeSave()
|
||||
stateSinkObject.Save(0, &t.p)
|
||||
stateSinkObject.Save(1, &t.maxPayloadSize)
|
||||
stateSinkObject.Save(2, &t.head)
|
||||
stateSinkObject.Save(3, &t.tail)
|
||||
stateSinkObject.Save(4, &t.next)
|
||||
stateSinkObject.Save(5, &t.tailHeader)
|
||||
}
|
||||
|
||||
func (t *Tx) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (t *Tx) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &t.p)
|
||||
stateSourceObject.Load(1, &t.maxPayloadSize)
|
||||
stateSourceObject.Load(2, &t.head)
|
||||
stateSourceObject.Load(3, &t.tail)
|
||||
stateSourceObject.Load(4, &t.next)
|
||||
stateSourceObject.Load(5, &t.tailHeader)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*pipe)(nil))
|
||||
state.Register((*Rx)(nil))
|
||||
state.Register((*Tx)(nil))
|
||||
}
|
||||
36
pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go
Normal file
36
pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// 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 pipe
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
)
|
||||
|
||||
func (p *pipe) write(idx uint64, v uint64) {
|
||||
ptr := (*uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
|
||||
*ptr = v
|
||||
}
|
||||
|
||||
func (p *pipe) writeAtomic(idx uint64, v uint64) {
|
||||
ptr := (*atomicbitops.Uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
|
||||
ptr.Store(v)
|
||||
}
|
||||
|
||||
func (p *pipe) readAtomic(idx uint64) uint64 {
|
||||
ptr := (*atomicbitops.Uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
|
||||
return ptr.Load()
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package pipe
|
||||
108
pkg/tcpip/link/sharedmem/pipe/rx.go
Normal file
108
pkg/tcpip/link/sharedmem/pipe/rx.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// 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 pipe
|
||||
|
||||
// Rx is the receive side of the shared memory ring buffer.
|
||||
//
|
||||
// +stateify savable
|
||||
type Rx struct {
|
||||
p pipe
|
||||
|
||||
tail uint64
|
||||
head uint64
|
||||
}
|
||||
|
||||
// Init initializes the receive end of the pipe. In the initial state, the next
|
||||
// slot to be inspected is the very first one.
|
||||
func (r *Rx) Init(b []byte) {
|
||||
r.p.init(b)
|
||||
r.tail = 0xfffffffe * jump
|
||||
r.head = r.tail
|
||||
}
|
||||
|
||||
// Pull reads the next buffer from the pipe, returning nil if there isn't one
|
||||
// currently available.
|
||||
//
|
||||
// The returned slice is available until Flush() is next called. After that, it
|
||||
// must not be touched.
|
||||
func (r *Rx) Pull() []byte {
|
||||
if r.head == r.tail+jump {
|
||||
// We've already pulled the whole pipe.
|
||||
return nil
|
||||
}
|
||||
|
||||
header := r.p.readAtomic(r.head)
|
||||
if header&slotFree != 0 {
|
||||
// The next slot is free, we can't pull it yet.
|
||||
return nil
|
||||
}
|
||||
|
||||
payloadSize := header & slotSizeMask
|
||||
newHead := r.head + payloadToSlotSize(payloadSize)
|
||||
headWrap := (r.head & revolutionMask) | uint64(len(r.p.buffer))
|
||||
|
||||
// Check if this is a wrapping slot. If that's the case, it carries no
|
||||
// data, so we just skip it and try again from the first slot.
|
||||
if int64(newHead-headWrap) >= 0 {
|
||||
// If newHead passes the tail, the pipe is either damaged or the
|
||||
// RX view of the pipe has completely wrapped without an
|
||||
// intervening flush.
|
||||
if int64(newHead-(r.tail+jump)) > 0 {
|
||||
return nil
|
||||
}
|
||||
// The pipe is damaged if newHead doesn't point to the start of
|
||||
// the ring.
|
||||
if newHead&offsetMask != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.tail == r.head {
|
||||
// If this is the first pull since the last Flush()
|
||||
// call, we flush the state so that the sender can use
|
||||
// this space if it needs to.
|
||||
r.p.writeAtomic(r.head, slotFree|slotToPayloadSize(newHead-r.head))
|
||||
r.tail = newHead
|
||||
}
|
||||
|
||||
r.head = newHead
|
||||
return r.Pull()
|
||||
}
|
||||
|
||||
// Grab the buffer before updating r.head.
|
||||
b := r.p.data(r.head, payloadSize)
|
||||
r.head = newHead
|
||||
return b
|
||||
}
|
||||
|
||||
// Flush tells the transmitter that all buffers pulled since the last Flush()
|
||||
// have been used, so the transmitter is free to used their slots for further
|
||||
// transmission.
|
||||
func (r *Rx) Flush() {
|
||||
if r.head == r.tail {
|
||||
return
|
||||
}
|
||||
r.p.writeAtomic(r.tail, slotFree|slotToPayloadSize(r.head-r.tail))
|
||||
r.tail = r.head
|
||||
}
|
||||
|
||||
// Abort unpulls any pulled buffers.
|
||||
func (r *Rx) Abort() {
|
||||
r.head = r.tail
|
||||
}
|
||||
|
||||
// Bytes returns the byte slice on which the pipe operates.
|
||||
func (r *Rx) Bytes() []byte {
|
||||
return r.p.buffer
|
||||
}
|
||||
164
pkg/tcpip/link/sharedmem/pipe/tx.go
Normal file
164
pkg/tcpip/link/sharedmem/pipe/tx.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// 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 pipe
|
||||
|
||||
// Tx is the transmit side of the shared memory ring buffer.
|
||||
//
|
||||
// +stateify savable
|
||||
type Tx struct {
|
||||
p pipe
|
||||
maxPayloadSize uint64
|
||||
|
||||
head uint64
|
||||
tail uint64
|
||||
next uint64
|
||||
|
||||
tailHeader uint64
|
||||
}
|
||||
|
||||
// Init initializes the transmit end of the pipe. In the initial state, the next
|
||||
// slot to be written is the very first one, and the transmitter has the whole
|
||||
// ring buffer available to it.
|
||||
func (t *Tx) Init(b []byte) {
|
||||
t.p.init(b)
|
||||
// maxPayloadSize excludes the header of the payload, and the header
|
||||
// of the wrapping message.
|
||||
t.maxPayloadSize = uint64(len(t.p.buffer)) - 2*sizeOfSlotHeader
|
||||
t.tail = 0xfffffffe * jump
|
||||
t.next = t.tail
|
||||
t.head = t.tail + jump
|
||||
t.p.write(t.tail, slotFree)
|
||||
}
|
||||
|
||||
// Capacity determines how many records of the given size can be written to the
|
||||
// pipe before it fills up.
|
||||
func (t *Tx) Capacity(recordSize uint64) uint64 {
|
||||
available := uint64(len(t.p.buffer)) - sizeOfSlotHeader
|
||||
entryLen := payloadToSlotSize(recordSize)
|
||||
return available / entryLen
|
||||
}
|
||||
|
||||
// Push reserves "payloadSize" bytes for transmission in the pipe. The caller
|
||||
// populates the returned slice with the data to be transferred and enventually
|
||||
// calls Flush() to make the data visible to the reader, or Abort() to make the
|
||||
// pipe forget all Push() calls since the last Flush().
|
||||
//
|
||||
// The returned slice is available until Flush() or Abort() is next called.
|
||||
// After that, it must not be touched.
|
||||
func (t *Tx) Push(payloadSize uint64) []byte {
|
||||
// Fail request if we know we will never have enough room.
|
||||
if payloadSize > t.maxPayloadSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
// True if TxPipe currently has a pushed message, i.e., it is not
|
||||
// Flush()'ed.
|
||||
messageAhead := t.next != t.tail
|
||||
totalLen := payloadToSlotSize(payloadSize)
|
||||
newNext := t.next + totalLen
|
||||
nextWrap := (t.next & revolutionMask) | uint64(len(t.p.buffer))
|
||||
if int64(newNext-nextWrap) >= 0 {
|
||||
// The new buffer would overflow the pipe, so we push a wrapping
|
||||
// slot, then try to add the actual slot to the front of the
|
||||
// pipe.
|
||||
newNext = (newNext & revolutionMask) + jump
|
||||
if !t.reclaim(newNext) {
|
||||
return nil
|
||||
}
|
||||
wrappingPayloadSize := slotToPayloadSize(newNext - t.next)
|
||||
oldNext := t.next
|
||||
t.next = newNext
|
||||
if messageAhead {
|
||||
t.p.write(oldNext, wrappingPayloadSize)
|
||||
} else {
|
||||
t.tailHeader = wrappingPayloadSize
|
||||
t.Flush()
|
||||
}
|
||||
return t.Push(payloadSize)
|
||||
}
|
||||
|
||||
// Check that we have enough room for the buffer.
|
||||
if !t.reclaim(newNext) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if messageAhead {
|
||||
t.p.write(t.next, payloadSize)
|
||||
} else {
|
||||
t.tailHeader = payloadSize
|
||||
}
|
||||
|
||||
// Grab the buffer before updating t.next.
|
||||
b := t.p.data(t.next, payloadSize)
|
||||
t.next = newNext
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// reclaim attempts to advance the head until at least newNext. If the head is
|
||||
// already at or beyond newNext, nothing happens and true is returned; otherwise
|
||||
// it tries to reclaim slots that have already been consumed by the receive end
|
||||
// of the pipe (they will be marked as free) and returns a boolean indicating
|
||||
// whether it was successful in reclaiming enough slots.
|
||||
func (t *Tx) reclaim(newNext uint64) bool {
|
||||
for int64(newNext-t.head) > 0 {
|
||||
// Can't reclaim if slot is not free.
|
||||
header := t.p.readAtomic(t.head)
|
||||
if header&slotFree == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
payloadSize := header & slotSizeMask
|
||||
newHead := t.head + payloadToSlotSize(payloadSize)
|
||||
|
||||
// Check newHead is within bounds and valid.
|
||||
if int64(newHead-t.tail) > int64(jump) || newHead&offsetMask >= uint64(len(t.p.buffer)) {
|
||||
return false
|
||||
}
|
||||
|
||||
t.head = newHead
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Abort causes all Push() calls since the last Flush() to be forgotten and
|
||||
// therefore they will not be made visible to the receiver.
|
||||
func (t *Tx) Abort() {
|
||||
t.next = t.tail
|
||||
}
|
||||
|
||||
// Flush causes all buffers pushed since the last Flush() [or Abort(), whichever
|
||||
// is the most recent] to be made visible to the receiver.
|
||||
func (t *Tx) Flush() {
|
||||
if t.next == t.tail {
|
||||
// Nothing to do if there are no pushed buffers.
|
||||
return
|
||||
}
|
||||
|
||||
if t.next != t.head {
|
||||
// The receiver will spin in t.next, so we must make sure that
|
||||
// the slotFree bit is set.
|
||||
t.p.write(t.next, slotFree)
|
||||
}
|
||||
|
||||
t.p.writeAtomic(t.tail, t.tailHeader)
|
||||
t.tail = t.next
|
||||
}
|
||||
|
||||
// Bytes returns the byte slice on which the pipe operates.
|
||||
func (t *Tx) Bytes() []byte {
|
||||
return t.p.buffer
|
||||
}
|
||||
3
pkg/tcpip/link/sharedmem/queue/queue_state_autogen.go
Normal file
3
pkg/tcpip/link/sharedmem/queue/queue_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package queue
|
||||
226
pkg/tcpip/link/sharedmem/queue/rx.go
Normal file
226
pkg/tcpip/link/sharedmem/queue/rx.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// 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 queue provides the implementation of transmit and receive queues
|
||||
// based on shared memory ring buffers.
|
||||
package queue
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/pipe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Offsets within a posted buffer.
|
||||
postedOffset = 0
|
||||
postedSize = 8
|
||||
postedRemainingInGroup = 12
|
||||
postedUserData = 16
|
||||
postedID = 24
|
||||
|
||||
sizeOfPostedBuffer = 32
|
||||
|
||||
// Offsets within a received packet header.
|
||||
consumedPacketSize = 0
|
||||
consumedPacketReserved = 4
|
||||
|
||||
sizeOfConsumedPacketHeader = 8
|
||||
|
||||
// Offsets within a consumed buffer.
|
||||
consumedOffset = 0
|
||||
consumedSize = 8
|
||||
consumedUserData = 12
|
||||
consumedID = 20
|
||||
|
||||
sizeOfConsumedBuffer = 28
|
||||
|
||||
// The following are the allowed states of the shared data area.
|
||||
// EventFDUinitialized is the value stored at the start of the shared data
|
||||
// region when it hasn't been initialized.
|
||||
EventFDUninitialized = 0
|
||||
// EventFDDisabled is the value stored at the start of the shared data region
|
||||
// when notifications using eventFD has been disabled.
|
||||
EventFDDisabled = 1
|
||||
// EventFDEnabled is the value stored at the start of the shared data region
|
||||
// when eventFD should be notified as the peer might be blocked waiting on
|
||||
// notifications.
|
||||
EventFDEnabled = 2
|
||||
)
|
||||
|
||||
// RxBuffer is the descriptor of a receive buffer.
|
||||
type RxBuffer struct {
|
||||
Offset uint64
|
||||
Size uint32
|
||||
ID uint64
|
||||
UserData uint64
|
||||
}
|
||||
|
||||
// Rx is a receive queue. It is implemented with one tx and one rx pipe: the tx
|
||||
// pipe is used to "post" buffers, while the rx pipe is used to receive packets
|
||||
// whose contents have been written to previously posted buffers.
|
||||
//
|
||||
// This struct is thread-compatible.
|
||||
type Rx struct {
|
||||
tx pipe.Tx
|
||||
rx pipe.Rx
|
||||
sharedEventFDState *atomicbitops.Uint32
|
||||
}
|
||||
|
||||
// Init initializes the receive queue with the given pipes, and shared state
|
||||
// pointer -- the latter is used to enable/disable eventfd notifications.
|
||||
func (r *Rx) Init(tx, rx []byte, sharedEventFDState *atomicbitops.Uint32) {
|
||||
r.sharedEventFDState = sharedEventFDState
|
||||
r.tx.Init(tx)
|
||||
r.rx.Init(rx)
|
||||
}
|
||||
|
||||
// EnableNotification updates the shared state such that the peer will notify
|
||||
// the eventfd when there are packets to be dequeued.
|
||||
func (r *Rx) EnableNotification() {
|
||||
r.sharedEventFDState.Store(EventFDEnabled)
|
||||
}
|
||||
|
||||
// DisableNotification updates the shared state such that the peer will not
|
||||
// notify the eventfd.
|
||||
func (r *Rx) DisableNotification() {
|
||||
r.sharedEventFDState.Store(EventFDDisabled)
|
||||
}
|
||||
|
||||
// PostedBuffersLimit returns the maximum number of buffers that can be posted
|
||||
// before the tx queue fills up.
|
||||
func (r *Rx) PostedBuffersLimit() uint64 {
|
||||
return r.tx.Capacity(sizeOfPostedBuffer)
|
||||
}
|
||||
|
||||
// PostBuffers makes the given buffers available for receiving data from the
|
||||
// peer. Once they are posted, the peer is free to write to them and will
|
||||
// eventually post them back for consumption.
|
||||
func (r *Rx) PostBuffers(buffers []RxBuffer) bool {
|
||||
for i := range buffers {
|
||||
b := r.tx.Push(sizeOfPostedBuffer)
|
||||
if b == nil {
|
||||
r.tx.Abort()
|
||||
return false
|
||||
}
|
||||
|
||||
pb := &buffers[i]
|
||||
binary.LittleEndian.PutUint64(b[postedOffset:], pb.Offset)
|
||||
binary.LittleEndian.PutUint32(b[postedSize:], pb.Size)
|
||||
binary.LittleEndian.PutUint32(b[postedRemainingInGroup:], 0)
|
||||
binary.LittleEndian.PutUint64(b[postedUserData:], pb.UserData)
|
||||
binary.LittleEndian.PutUint64(b[postedID:], pb.ID)
|
||||
}
|
||||
|
||||
r.tx.Flush()
|
||||
return true
|
||||
}
|
||||
|
||||
// Dequeue receives buffers that have been previously posted by PostBuffers()
|
||||
// and that have been filled by the peer and posted back.
|
||||
//
|
||||
// This is similar to append() in that new buffers are appended to "bufs", with
|
||||
// reallocation only if "bufs" doesn't have enough capacity.
|
||||
func (r *Rx) Dequeue(bufs []RxBuffer) ([]RxBuffer, uint32) {
|
||||
for {
|
||||
outBufs := bufs
|
||||
// Pull the next descriptor from the rx pipe.
|
||||
b := r.rx.Pull()
|
||||
if b == nil {
|
||||
return bufs, 0
|
||||
}
|
||||
|
||||
if len(b) < sizeOfConsumedPacketHeader {
|
||||
log.Warningf("Ignoring packet header: size (%v) is less than header size (%v)", len(b), sizeOfConsumedPacketHeader)
|
||||
r.rx.Flush()
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataSize := binary.LittleEndian.Uint32(b[consumedPacketSize:])
|
||||
|
||||
// Calculate the number of buffer descriptors and copy them
|
||||
// over to the output.
|
||||
count := (len(b) - sizeOfConsumedPacketHeader) / sizeOfConsumedBuffer
|
||||
offset := sizeOfConsumedPacketHeader
|
||||
buffersSize := uint32(0)
|
||||
for i := count; i > 0; i-- {
|
||||
s := binary.LittleEndian.Uint32(b[offset+consumedSize:])
|
||||
buffersSize += s
|
||||
if buffersSize < s {
|
||||
// The buffer size overflows an unsigned 32-bit
|
||||
// integer, so break out and force it to be
|
||||
// ignored.
|
||||
totalDataSize = 1
|
||||
buffersSize = 0
|
||||
break
|
||||
}
|
||||
|
||||
outBufs = append(outBufs, RxBuffer{
|
||||
Offset: binary.LittleEndian.Uint64(b[offset+consumedOffset:]),
|
||||
Size: s,
|
||||
ID: binary.LittleEndian.Uint64(b[offset+consumedID:]),
|
||||
})
|
||||
|
||||
offset += sizeOfConsumedBuffer
|
||||
}
|
||||
|
||||
r.rx.Flush()
|
||||
|
||||
if buffersSize < totalDataSize {
|
||||
// The descriptor is corrupted, ignore it.
|
||||
log.Warningf("Ignoring packet: actual data size (%v) less than expected size (%v)", buffersSize, totalDataSize)
|
||||
continue
|
||||
}
|
||||
|
||||
return outBufs, totalDataSize
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the byte slices on which the queue operates.
|
||||
func (r *Rx) Bytes() (tx, rx []byte) {
|
||||
return r.tx.Bytes(), r.rx.Bytes()
|
||||
}
|
||||
|
||||
// DecodeRxBufferHeader decodes the header of a buffer posted on an rx queue.
|
||||
func DecodeRxBufferHeader(b []byte) RxBuffer {
|
||||
return RxBuffer{
|
||||
Offset: binary.LittleEndian.Uint64(b[postedOffset:]),
|
||||
Size: binary.LittleEndian.Uint32(b[postedSize:]),
|
||||
ID: binary.LittleEndian.Uint64(b[postedID:]),
|
||||
UserData: binary.LittleEndian.Uint64(b[postedUserData:]),
|
||||
}
|
||||
}
|
||||
|
||||
// RxCompletionSize returns the number of bytes needed to encode an rx
|
||||
// completion containing "count" buffers.
|
||||
func RxCompletionSize(count int) uint64 {
|
||||
return sizeOfConsumedPacketHeader + uint64(count)*sizeOfConsumedBuffer
|
||||
}
|
||||
|
||||
// EncodeRxCompletion encodes an rx completion header.
|
||||
func EncodeRxCompletion(b []byte, size, reserved uint32) {
|
||||
binary.LittleEndian.PutUint32(b[consumedPacketSize:], size)
|
||||
binary.LittleEndian.PutUint32(b[consumedPacketReserved:], reserved)
|
||||
}
|
||||
|
||||
// EncodeRxCompletionBuffer encodes the i-th rx completion buffer header.
|
||||
func EncodeRxCompletionBuffer(b []byte, i int, rxb RxBuffer) {
|
||||
b = b[RxCompletionSize(i):]
|
||||
binary.LittleEndian.PutUint64(b[consumedOffset:], rxb.Offset)
|
||||
binary.LittleEndian.PutUint32(b[consumedSize:], rxb.Size)
|
||||
binary.LittleEndian.PutUint64(b[consumedUserData:], rxb.UserData)
|
||||
binary.LittleEndian.PutUint64(b[consumedID:], rxb.ID)
|
||||
}
|
||||
161
pkg/tcpip/link/sharedmem/queue/tx.go
Normal file
161
pkg/tcpip/link/sharedmem/queue/tx.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// 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 queue
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/pipe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Offsets within a packet header.
|
||||
packetID = 0
|
||||
packetSize = 8
|
||||
packetReserved = 12
|
||||
|
||||
sizeOfPacketHeader = 16
|
||||
|
||||
// Offsets with a buffer descriptor
|
||||
bufferOffset = 0
|
||||
bufferSize = 8
|
||||
|
||||
sizeOfBufferDescriptor = 12
|
||||
)
|
||||
|
||||
// TxBuffer is the descriptor of a transmit buffer.
|
||||
type TxBuffer struct {
|
||||
Next *TxBuffer
|
||||
Offset uint64
|
||||
Size uint32
|
||||
}
|
||||
|
||||
// Tx is a transmit queue. It is implemented with one tx and one rx pipe: the
|
||||
// tx pipe is used to request the transmission of packets, while the rx pipe
|
||||
// is used to receive which transmissions have completed.
|
||||
//
|
||||
// This struct is thread-compatible.
|
||||
type Tx struct {
|
||||
tx pipe.Tx
|
||||
rx pipe.Rx
|
||||
sharedEventFDState *atomicbitops.Uint32
|
||||
}
|
||||
|
||||
// Init initializes the transmit queue with the given pipes.
|
||||
func (t *Tx) Init(tx, rx []byte, sharedEventFDState *atomicbitops.Uint32) {
|
||||
t.tx.Init(tx)
|
||||
t.rx.Init(rx)
|
||||
t.sharedEventFDState = sharedEventFDState
|
||||
}
|
||||
|
||||
// NotificationsEnabled returns true if eventFD should be used to notify the
|
||||
// peer of events (eg. packet transmit etc).
|
||||
func (t *Tx) NotificationsEnabled() bool {
|
||||
// Notifications are considered enabled unless explicitly disabled.
|
||||
return t.sharedEventFDState.Load() != EventFDDisabled
|
||||
}
|
||||
|
||||
// Enqueue queues the given linked list of buffers for transmission as one
|
||||
// packet. While it is queued, the caller must not modify them.
|
||||
func (t *Tx) Enqueue(id uint64, totalDataLen, bufferCount uint32, buffer *TxBuffer) bool {
|
||||
// Reserve room in the tx pipe.
|
||||
totalLen := sizeOfPacketHeader + uint64(bufferCount)*sizeOfBufferDescriptor
|
||||
|
||||
b := t.tx.Push(totalLen)
|
||||
if b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Initialize the packet and buffer descriptors.
|
||||
binary.LittleEndian.PutUint64(b[packetID:], id)
|
||||
binary.LittleEndian.PutUint32(b[packetSize:], totalDataLen)
|
||||
binary.LittleEndian.PutUint32(b[packetReserved:], 0)
|
||||
|
||||
offset := sizeOfPacketHeader
|
||||
for i := bufferCount; i != 0; i-- {
|
||||
binary.LittleEndian.PutUint64(b[offset+bufferOffset:], buffer.Offset)
|
||||
binary.LittleEndian.PutUint32(b[offset+bufferSize:], buffer.Size)
|
||||
offset += sizeOfBufferDescriptor
|
||||
buffer = buffer.Next
|
||||
}
|
||||
|
||||
t.tx.Flush()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// CompletedPacket returns the id of the last completed transmission. The
|
||||
// returned id, if any, refers to a value passed on a previous call to
|
||||
// Enqueue().
|
||||
func (t *Tx) CompletedPacket() (id uint64, ok bool) {
|
||||
for {
|
||||
b := t.rx.Pull()
|
||||
if b == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if len(b) != 8 {
|
||||
t.rx.Flush()
|
||||
log.Warningf("Ignoring completed packet: size (%v) is less than expected (%v)", len(b), 8)
|
||||
continue
|
||||
}
|
||||
|
||||
v := binary.LittleEndian.Uint64(b)
|
||||
|
||||
t.rx.Flush()
|
||||
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the byte slices on which the queue operates.
|
||||
func (t *Tx) Bytes() (tx, rx []byte) {
|
||||
return t.tx.Bytes(), t.rx.Bytes()
|
||||
}
|
||||
|
||||
// TxPacketInfo holds information about a packet sent on a tx queue.
|
||||
type TxPacketInfo struct {
|
||||
ID uint64
|
||||
Size uint32
|
||||
Reserved uint32
|
||||
BufferCount int
|
||||
}
|
||||
|
||||
// DecodeTxPacketHeader decodes the header of a packet sent over a tx queue.
|
||||
func DecodeTxPacketHeader(b []byte) TxPacketInfo {
|
||||
return TxPacketInfo{
|
||||
ID: binary.LittleEndian.Uint64(b[packetID:]),
|
||||
Size: binary.LittleEndian.Uint32(b[packetSize:]),
|
||||
Reserved: binary.LittleEndian.Uint32(b[packetReserved:]),
|
||||
BufferCount: (len(b) - sizeOfPacketHeader) / sizeOfBufferDescriptor,
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeTxBufferHeader decodes the header of the i-th buffer of a packet sent
|
||||
// over a tx queue.
|
||||
func DecodeTxBufferHeader(b []byte, i int) TxBuffer {
|
||||
b = b[sizeOfPacketHeader+i*sizeOfBufferDescriptor:]
|
||||
return TxBuffer{
|
||||
Offset: binary.LittleEndian.Uint64(b[bufferOffset:]),
|
||||
Size: binary.LittleEndian.Uint32(b[bufferSize:]),
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeTxCompletion encodes a tx completion header.
|
||||
func EncodeTxCompletion(b []byte, id uint64) {
|
||||
binary.LittleEndian.PutUint64(b, id)
|
||||
}
|
||||
220
pkg/tcpip/link/sharedmem/queuepair.go
Normal file
220
pkg/tcpip/link/sharedmem/queuepair.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package sharedmem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/eventfd"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultQueueDataSize is the size of the shared memory data region that
|
||||
// holds the scatter/gather buffers.
|
||||
DefaultQueueDataSize = 1 << 20 // 1MiB
|
||||
|
||||
// DefaultQueuePipeSize is the size of the pipe that holds the packet descriptors.
|
||||
//
|
||||
// Assuming each packet data is approximately 1280 bytes (IPv6 Minimum MTU)
|
||||
// then we can hold approximately 1024*1024/1280 ~ 819 packets in the data
|
||||
// area. Which means the pipe needs to be big enough to hold 819
|
||||
// descriptors.
|
||||
//
|
||||
// Each descriptor is approximately 8 (slot descriptor in pipe) +
|
||||
// 16 (packet descriptor) + 12 (for buffer descriptor) assuming each packet is
|
||||
// stored in exactly 1 buffer descriptor (see queue/tx.go and pipe/tx.go.)
|
||||
//
|
||||
// Which means we need approximately 36*819 ~ 29 KiB to store all packet
|
||||
// descriptors. We could go with a 32 KiB pipe but to give it some slack in
|
||||
// how the upper layer may make use of the scatter gather buffers we double
|
||||
// this to hold enough descriptors.
|
||||
DefaultQueuePipeSize = 64 << 10 // 64KiB
|
||||
|
||||
// DefaultSharedDataSize is the size of the sharedData region used to
|
||||
// enable/disable notifications.
|
||||
DefaultSharedDataSize = 4 << 10 // 4KiB
|
||||
|
||||
// DefaultBufferSize is the size of each individual buffer that the data
|
||||
// region is broken down into to hold packet data. Should be larger than
|
||||
// 1500 + 14 (Ethernet header) + 10 (VirtIO header) to fit each packet
|
||||
// in a single buffer.
|
||||
DefaultBufferSize = 2048
|
||||
|
||||
// DefaultTmpDir is the path used to create the memory files if a path
|
||||
// is not provided.
|
||||
DefaultTmpDir = "/dev/shm"
|
||||
)
|
||||
|
||||
// A QueuePair represents a pair of TX/RX queues.
|
||||
type QueuePair struct {
|
||||
// txCfg is the QueueConfig to be used for transmit queue.
|
||||
txCfg QueueConfig
|
||||
|
||||
// rxCfg is the QueueConfig to be used for receive queue.
|
||||
rxCfg QueueConfig
|
||||
}
|
||||
|
||||
// QueueOptions allows queue specific configuration to be specified when
|
||||
// creating a QueuePair.
|
||||
type QueueOptions struct {
|
||||
// SharedMemPath is the path to use to create the shared memory backing
|
||||
// files for the queue.
|
||||
//
|
||||
// If unspecified it defaults to "/dev/shm".
|
||||
SharedMemPath string
|
||||
}
|
||||
|
||||
// NewQueuePair creates a shared memory QueuePair.
|
||||
func NewQueuePair(opts QueueOptions) (*QueuePair, error) {
|
||||
txCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
|
||||
dataSize: DefaultQueueDataSize,
|
||||
txPipeSize: DefaultQueuePipeSize,
|
||||
rxPipeSize: DefaultQueuePipeSize,
|
||||
sharedDataSize: DefaultSharedDataSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create tx queue: %s", err)
|
||||
}
|
||||
|
||||
rxCfg, err := createQueueFDs(opts.SharedMemPath, queueSizes{
|
||||
dataSize: DefaultQueueDataSize,
|
||||
txPipeSize: DefaultQueuePipeSize,
|
||||
rxPipeSize: DefaultQueuePipeSize,
|
||||
sharedDataSize: DefaultSharedDataSize,
|
||||
})
|
||||
if err != nil {
|
||||
closeFDs(txCfg)
|
||||
return nil, fmt.Errorf("failed to create rx queue: %s", err)
|
||||
}
|
||||
|
||||
return &QueuePair{
|
||||
txCfg: txCfg,
|
||||
rxCfg: rxCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes underlying tx/rx queue fds.
|
||||
func (q *QueuePair) Close() {
|
||||
closeFDs(q.txCfg)
|
||||
closeFDs(q.rxCfg)
|
||||
}
|
||||
|
||||
// TXQueueConfig returns the QueueConfig for the receive queue.
|
||||
func (q *QueuePair) TXQueueConfig() QueueConfig {
|
||||
return q.txCfg
|
||||
}
|
||||
|
||||
// RXQueueConfig returns the QueueConfig for the transmit queue.
|
||||
func (q *QueuePair) RXQueueConfig() QueueConfig {
|
||||
return q.rxCfg
|
||||
}
|
||||
|
||||
type queueSizes struct {
|
||||
dataSize int64
|
||||
txPipeSize int64
|
||||
rxPipeSize int64
|
||||
sharedDataSize int64
|
||||
}
|
||||
|
||||
func createQueueFDs(sharedMemPath string, s queueSizes) (QueueConfig, error) {
|
||||
success := false
|
||||
var eventFD eventfd.Eventfd
|
||||
var dataFD, txPipeFD, rxPipeFD, sharedDataFD int
|
||||
defer func() {
|
||||
if success {
|
||||
return
|
||||
}
|
||||
closeFDs(QueueConfig{
|
||||
EventFD: eventFD,
|
||||
DataFD: dataFD,
|
||||
TxPipeFD: txPipeFD,
|
||||
RxPipeFD: rxPipeFD,
|
||||
SharedDataFD: sharedDataFD,
|
||||
})
|
||||
}()
|
||||
eventFD, err := eventfd.Create()
|
||||
if err != nil {
|
||||
return QueueConfig{}, fmt.Errorf("eventfd failed: %v", err)
|
||||
}
|
||||
dataFD, err = createFile(sharedMemPath, s.dataSize, false)
|
||||
if err != nil {
|
||||
return QueueConfig{}, fmt.Errorf("failed to create dataFD: %s", err)
|
||||
}
|
||||
txPipeFD, err = createFile(sharedMemPath, s.txPipeSize, true)
|
||||
if err != nil {
|
||||
return QueueConfig{}, fmt.Errorf("failed to create txPipeFD: %s", err)
|
||||
}
|
||||
rxPipeFD, err = createFile(sharedMemPath, s.rxPipeSize, true)
|
||||
if err != nil {
|
||||
return QueueConfig{}, fmt.Errorf("failed to create rxPipeFD: %s", err)
|
||||
}
|
||||
sharedDataFD, err = createFile(sharedMemPath, s.sharedDataSize, false)
|
||||
if err != nil {
|
||||
return QueueConfig{}, fmt.Errorf("failed to create sharedDataFD: %s", err)
|
||||
}
|
||||
success = true
|
||||
return QueueConfig{
|
||||
EventFD: eventFD,
|
||||
DataFD: dataFD,
|
||||
TxPipeFD: txPipeFD,
|
||||
RxPipeFD: rxPipeFD,
|
||||
SharedDataFD: sharedDataFD,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createFile(sharedMemPath string, size int64, initQueue bool) (fd int, err error) {
|
||||
tmpDir := DefaultTmpDir
|
||||
if sharedMemPath != "" {
|
||||
tmpDir = sharedMemPath
|
||||
}
|
||||
f, err := os.CreateTemp(tmpDir, "sharedmem_test")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("TempFile failed: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
unix.Unlink(f.Name())
|
||||
|
||||
if initQueue {
|
||||
// Write the "slot-free" flag in the initial queue.
|
||||
if _, err := f.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0x80}, 0); err != nil {
|
||||
return -1, fmt.Errorf("WriteAt failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fd, err = unix.Dup(int(f.Fd()))
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("unix.Dup(%d) failed: %v", f.Fd(), err)
|
||||
}
|
||||
|
||||
if err := unix.Ftruncate(fd, size); err != nil {
|
||||
unix.Close(fd)
|
||||
return -1, fmt.Errorf("ftruncate(%d, %d) failed: %v", fd, size, err)
|
||||
}
|
||||
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func closeFDs(c QueueConfig) {
|
||||
unix.Close(c.DataFD)
|
||||
c.EventFD.Close()
|
||||
unix.Close(c.TxPipeFD)
|
||||
unix.Close(c.RxPipeFD)
|
||||
unix.Close(c.SharedDataFD)
|
||||
}
|
||||
152
pkg/tcpip/link/sharedmem/rx.go
Normal file
152
pkg/tcpip/link/sharedmem/rx.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package sharedmem
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/eventfd"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/queue"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// rx holds all state associated with an rx queue.
|
||||
type rx struct {
|
||||
data []byte
|
||||
sharedData []byte
|
||||
q queue.Rx
|
||||
eventFD eventfd.Eventfd
|
||||
}
|
||||
|
||||
// init initializes all state needed by the rx queue based on the information
|
||||
// provided.
|
||||
//
|
||||
// The caller always retains ownership of all file descriptors passed in. The
|
||||
// queue implementation will duplicate any that it may need in the future.
|
||||
func (r *rx) init(mtu uint32, c *QueueConfig) error {
|
||||
// Map in all buffers.
|
||||
txPipe, err := getBuffer(c.TxPipeFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rxPipe, err := getBuffer(c.RxPipeFD)
|
||||
if err != nil {
|
||||
unix.Munmap(txPipe)
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := getBuffer(c.DataFD)
|
||||
if err != nil {
|
||||
unix.Munmap(txPipe)
|
||||
unix.Munmap(rxPipe)
|
||||
return err
|
||||
}
|
||||
|
||||
sharedData, err := getBuffer(c.SharedDataFD)
|
||||
if err != nil {
|
||||
unix.Munmap(txPipe)
|
||||
unix.Munmap(rxPipe)
|
||||
unix.Munmap(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate the eventFD so that caller can close it but we can still
|
||||
// use it.
|
||||
efd, err := c.EventFD.Dup()
|
||||
if err != nil {
|
||||
unix.Munmap(txPipe)
|
||||
unix.Munmap(rxPipe)
|
||||
unix.Munmap(data)
|
||||
unix.Munmap(sharedData)
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize state based on buffers.
|
||||
r.q.Init(txPipe, rxPipe, sharedDataPointer(sharedData))
|
||||
r.data = data
|
||||
r.eventFD = efd
|
||||
r.sharedData = sharedData
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanup releases all resources allocated during init() except r.eventFD. It
|
||||
// must only be called if init() has previously succeeded.
|
||||
func (r *rx) cleanup() {
|
||||
a, b := r.q.Bytes()
|
||||
unix.Munmap(a)
|
||||
unix.Munmap(b)
|
||||
|
||||
unix.Munmap(r.data)
|
||||
unix.Munmap(r.sharedData)
|
||||
}
|
||||
|
||||
// notify writes to the tx.eventFD to indicate to the peer that there is data to
|
||||
// be read.
|
||||
func (r *rx) notify() {
|
||||
r.eventFD.Notify()
|
||||
}
|
||||
|
||||
// postAndReceive posts the provided buffers (if any), and then tries to read
|
||||
// from the receive queue.
|
||||
//
|
||||
// Capacity permitting, it reuses the posted buffer slice to store the buffers
|
||||
// that were read as well.
|
||||
//
|
||||
// This function will block if there aren't any available packets.
|
||||
func (r *rx) postAndReceive(b []queue.RxBuffer, stopRequested *atomicbitops.Uint32) ([]queue.RxBuffer, uint32) {
|
||||
// Post the buffers first. If we cannot post, sleep until we can. We
|
||||
// never post more than will fit concurrently, so it's safe to wait
|
||||
// until enough room is available.
|
||||
if len(b) != 0 && !r.q.PostBuffers(b) {
|
||||
r.q.EnableNotification()
|
||||
for !r.q.PostBuffers(b) {
|
||||
r.eventFD.Wait()
|
||||
if stopRequested.Load() != 0 {
|
||||
r.q.DisableNotification()
|
||||
return nil, 0
|
||||
}
|
||||
}
|
||||
r.q.DisableNotification()
|
||||
}
|
||||
|
||||
// Read the next set of descriptors.
|
||||
b, n := r.q.Dequeue(b[:0])
|
||||
if len(b) != 0 {
|
||||
return b, n
|
||||
}
|
||||
|
||||
// Data isn't immediately available. Enable eventfd notifications.
|
||||
r.q.EnableNotification()
|
||||
for {
|
||||
b, n = r.q.Dequeue(b)
|
||||
if len(b) != 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Wait for notification.
|
||||
r.eventFD.Wait()
|
||||
if stopRequested.Load() != 0 {
|
||||
r.q.DisableNotification()
|
||||
return nil, 0
|
||||
}
|
||||
}
|
||||
r.q.DisableNotification()
|
||||
|
||||
return b, n
|
||||
}
|
||||
96
pkg/tcpip/link/sharedmem/server_endpoint_mutex.go
Normal file
96
pkg/tcpip/link/sharedmem/server_endpoint_mutex.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package sharedmem
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// RWMutex is sync.RWMutex with the correctness validator.
|
||||
type serverEndpointRWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var serverEndpointlockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type serverEndpointlockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) Lock() {
|
||||
locking.AddGLock(serverEndpointprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) NestedLock(i serverEndpointlockNameIndex) {
|
||||
locking.AddGLock(serverEndpointprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(serverEndpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) NestedUnlock(i serverEndpointlockNameIndex) {
|
||||
m.mu.Unlock()
|
||||
locking.DelGLock(serverEndpointprefixIndex, int(i))
|
||||
}
|
||||
|
||||
// RLock locks m for reading.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) RLock() {
|
||||
locking.AddGLock(serverEndpointprefixIndex, -1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
locking.DelGLock(serverEndpointprefixIndex, -1)
|
||||
}
|
||||
|
||||
// RLockBypass locks m for reading without executing the validator.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) RLockBypass() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// RUnlockBypass undoes a single RLockBypass call.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) RUnlockBypass() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
// DowngradeLock atomically unlocks rw for writing and locks it for reading.
|
||||
// +checklocksignore
|
||||
func (m *serverEndpointRWMutex) DowngradeLock() {
|
||||
m.mu.DowngradeLock()
|
||||
}
|
||||
|
||||
var serverEndpointprefixIndex *locking.MutexClass
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func serverEndpointinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
serverEndpointinitLockNames()
|
||||
serverEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(serverEndpointRWMutex{}), serverEndpointlockNames)
|
||||
}
|
||||
162
pkg/tcpip/link/sharedmem/server_rx.go
Normal file
162
pkg/tcpip/link/sharedmem/server_rx.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package sharedmem
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/cleanup"
|
||||
"github.com/sagernet/gvisor/pkg/eventfd"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/pipe"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/queue"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type serverRx struct {
|
||||
// packetPipe represents the receive end of the pipe that carries the packet
|
||||
// descriptors sent by the client.
|
||||
packetPipe pipe.Rx
|
||||
|
||||
// completionPipe represents the transmit end of the pipe that will carry
|
||||
// completion notifications from the server to the client.
|
||||
completionPipe pipe.Tx
|
||||
|
||||
// data represents the buffer area where the packet payload is held.
|
||||
data []byte
|
||||
|
||||
// eventFD is used to notify the peer when transmission is completed.
|
||||
eventFD eventfd.Eventfd
|
||||
|
||||
// sharedData the memory region to use to enable/disable notifications.
|
||||
sharedData []byte
|
||||
|
||||
// sharedEventFDState is the memory region in sharedData used to enable
|
||||
// disable notifications on eventFD.
|
||||
sharedEventFDState *atomicbitops.Uint32
|
||||
}
|
||||
|
||||
// init initializes all state needed by the serverTx queue based on the
|
||||
// information provided.
|
||||
//
|
||||
// The caller always retains ownership of all file descriptors passed in. The
|
||||
// queue implementation will duplicate any that it may need in the future.
|
||||
func (s *serverRx) init(c *QueueConfig) error {
|
||||
// Map in all buffers.
|
||||
packetPipeMem, err := getBuffer(c.TxPipeFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu := cleanup.Make(func() { unix.Munmap(packetPipeMem) })
|
||||
defer cu.Clean()
|
||||
|
||||
completionPipeMem, err := getBuffer(c.RxPipeFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(completionPipeMem) })
|
||||
|
||||
data, err := getBuffer(c.DataFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(data) })
|
||||
|
||||
sharedData, err := getBuffer(c.SharedDataFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(sharedData) })
|
||||
|
||||
// Duplicate the eventFD so that caller can close it but we can still
|
||||
// use it.
|
||||
efd, err := c.EventFD.Dup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { efd.Close() })
|
||||
|
||||
s.packetPipe.Init(packetPipeMem)
|
||||
s.completionPipe.Init(completionPipeMem)
|
||||
s.data = data
|
||||
s.eventFD = efd
|
||||
s.sharedData = sharedData
|
||||
s.sharedEventFDState = sharedDataPointer(sharedData)
|
||||
|
||||
cu.Release()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serverRx) cleanup() {
|
||||
unix.Munmap(s.packetPipe.Bytes())
|
||||
unix.Munmap(s.completionPipe.Bytes())
|
||||
unix.Munmap(s.data)
|
||||
unix.Munmap(s.sharedData)
|
||||
s.eventFD.Close()
|
||||
}
|
||||
|
||||
// EnableNotification updates the shared state such that the peer will notify
|
||||
// the eventfd when there are packets to be dequeued.
|
||||
func (s *serverRx) EnableNotification() {
|
||||
s.sharedEventFDState.Store(queue.EventFDEnabled)
|
||||
}
|
||||
|
||||
// DisableNotification updates the shared state such that the peer will not
|
||||
// notify the eventfd.
|
||||
func (s *serverRx) DisableNotification() {
|
||||
s.sharedEventFDState.Store(queue.EventFDDisabled)
|
||||
}
|
||||
|
||||
// completionNotificationSize is size in bytes of a completion notification sent
|
||||
// on the completion queue after a transmitted packet has been handled.
|
||||
const completionNotificationSize = 8
|
||||
|
||||
// receive receives a single packet from the packetPipe.
|
||||
func (s *serverRx) receive() *buffer.View {
|
||||
desc := s.packetPipe.Pull()
|
||||
if desc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pktInfo := queue.DecodeTxPacketHeader(desc)
|
||||
contents := buffer.NewView(int(pktInfo.Size))
|
||||
toCopy := pktInfo.Size
|
||||
for i := 0; i < pktInfo.BufferCount; i++ {
|
||||
txBuf := queue.DecodeTxBufferHeader(desc, i)
|
||||
if txBuf.Size <= toCopy {
|
||||
contents.Write(s.data[txBuf.Offset:][:txBuf.Size])
|
||||
toCopy -= txBuf.Size
|
||||
continue
|
||||
}
|
||||
contents.Write(s.data[txBuf.Offset:][:toCopy])
|
||||
break
|
||||
}
|
||||
|
||||
// Flush to let peer know that slots queued for transmission have been handled
|
||||
// and its free to reuse the slots.
|
||||
s.packetPipe.Flush()
|
||||
// Encode packet completion.
|
||||
b := s.completionPipe.Push(completionNotificationSize)
|
||||
queue.EncodeTxCompletion(b, pktInfo.ID)
|
||||
s.completionPipe.Flush()
|
||||
return contents
|
||||
}
|
||||
|
||||
func (s *serverRx) waitForPackets() {
|
||||
s.eventFD.Wait()
|
||||
}
|
||||
194
pkg/tcpip/link/sharedmem/server_tx.go
Normal file
194
pkg/tcpip/link/sharedmem/server_tx.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package sharedmem
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/cleanup"
|
||||
"github.com/sagernet/gvisor/pkg/eventfd"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/pipe"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/queue"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// serverTx represents the server end of the sharedmem queue and is used to send
|
||||
// packets to the peer in the buffers posted by the peer in the fillPipe.
|
||||
//
|
||||
// +stateify savable
|
||||
type serverTx struct {
|
||||
// fillPipe represents the receive end of the pipe that carries the RxBuffers
|
||||
// posted by the peer.
|
||||
fillPipe pipe.Rx
|
||||
|
||||
// completionPipe represents the transmit end of the pipe that carries the
|
||||
// descriptors for filled RxBuffers.
|
||||
completionPipe pipe.Tx
|
||||
|
||||
// data represents the buffer area where the packet payload is held.
|
||||
data []byte
|
||||
|
||||
// eventFD is used to notify the peer when fill requests are fulfilled.
|
||||
eventFD eventfd.Eventfd
|
||||
|
||||
// sharedData the memory region to use to enable/disable notifications.
|
||||
sharedData []byte
|
||||
|
||||
// sharedEventFDState is the memory region in sharedData used to enable/disable
|
||||
// notifications on eventFD.
|
||||
sharedEventFDState *atomicbitops.Uint32
|
||||
}
|
||||
|
||||
// init initializes all tstate needed by the serverTx queue based on the
|
||||
// information provided.
|
||||
//
|
||||
// The caller always retains ownership of all file descriptors passed in. The
|
||||
// queue implementation will duplicate any that it may need in the future.
|
||||
func (s *serverTx) init(c *QueueConfig) error {
|
||||
// Map in all buffers.
|
||||
fillPipeMem, err := getBuffer(c.TxPipeFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu := cleanup.Make(func() { unix.Munmap(fillPipeMem) })
|
||||
defer cu.Clean()
|
||||
|
||||
completionPipeMem, err := getBuffer(c.RxPipeFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(completionPipeMem) })
|
||||
|
||||
data, err := getBuffer(c.DataFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(data) })
|
||||
|
||||
sharedData, err := getBuffer(c.SharedDataFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { unix.Munmap(sharedData) })
|
||||
|
||||
// Duplicate the eventFD so that caller can close it but we can still
|
||||
// use it.
|
||||
efd, err := c.EventFD.Dup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cu.Add(func() { efd.Close() })
|
||||
|
||||
cu.Release()
|
||||
|
||||
s.fillPipe.Init(fillPipeMem)
|
||||
s.completionPipe.Init(completionPipeMem)
|
||||
s.data = data
|
||||
s.eventFD = efd
|
||||
s.sharedData = sharedData
|
||||
s.sharedEventFDState = sharedDataPointer(sharedData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serverTx) cleanup() {
|
||||
unix.Munmap(s.fillPipe.Bytes())
|
||||
unix.Munmap(s.completionPipe.Bytes())
|
||||
unix.Munmap(s.data)
|
||||
unix.Munmap(s.sharedData)
|
||||
s.eventFD.Close()
|
||||
}
|
||||
|
||||
// acquireBuffers acquires enough buffers to hold all the data in views or
|
||||
// returns nil if not enough buffers are currently available.
|
||||
func (s *serverTx) acquireBuffers(pktBuffer buffer.Buffer, buffers []queue.RxBuffer) (acquiredBuffers []queue.RxBuffer) {
|
||||
acquiredBuffers = buffers[:0]
|
||||
wantBytes := int(pktBuffer.Size())
|
||||
for wantBytes > 0 {
|
||||
var b []byte
|
||||
if b = s.fillPipe.Pull(); b == nil {
|
||||
s.fillPipe.Abort()
|
||||
return nil
|
||||
}
|
||||
rxBuffer := queue.DecodeRxBufferHeader(b)
|
||||
acquiredBuffers = append(acquiredBuffers, rxBuffer)
|
||||
wantBytes -= int(rxBuffer.Size)
|
||||
}
|
||||
return acquiredBuffers
|
||||
}
|
||||
|
||||
// fillPacket copies the data in the provided views into buffers pulled from the
|
||||
// fillPipe and returns a slice of RxBuffers that contain the copied data as
|
||||
// well as the total number of bytes copied.
|
||||
//
|
||||
// To avoid allocations the filledBuffers are appended to the buffers slice
|
||||
// which will be grown as required. This method takes ownership of pktBuffer.
|
||||
func (s *serverTx) fillPacket(pktBuffer buffer.Buffer, buffers []queue.RxBuffer) (filledBuffers []queue.RxBuffer, totalCopied uint32) {
|
||||
bufs := s.acquireBuffers(pktBuffer, buffers)
|
||||
if bufs == nil {
|
||||
pktBuffer.Release()
|
||||
return nil, 0
|
||||
}
|
||||
br := pktBuffer.AsBufferReader()
|
||||
defer br.Close()
|
||||
|
||||
for i := 0; br.Len() > 0 && i < len(bufs); i++ {
|
||||
buf := bufs[i]
|
||||
copied, err := br.Read(s.data[buf.Offset:][:buf.Size])
|
||||
buf.Size = uint32(copied)
|
||||
// Copy the packet into the posted buffer.
|
||||
totalCopied += bufs[i].Size
|
||||
if err != nil {
|
||||
return bufs, totalCopied
|
||||
}
|
||||
}
|
||||
return bufs, totalCopied
|
||||
}
|
||||
|
||||
func (s *serverTx) transmit(pkt *stack.PacketBuffer) bool {
|
||||
buffers := make([]queue.RxBuffer, 8)
|
||||
buffers, totalCopied := s.fillPacket(pkt.ToBuffer(), buffers)
|
||||
if totalCopied == 0 {
|
||||
// drop the packet as not enough buffers were probably available
|
||||
// to send.
|
||||
return false
|
||||
}
|
||||
b := s.completionPipe.Push(queue.RxCompletionSize(len(buffers)))
|
||||
if b == nil {
|
||||
return false
|
||||
}
|
||||
queue.EncodeRxCompletion(b, totalCopied, 0 /* reserved */)
|
||||
for i := 0; i < len(buffers); i++ {
|
||||
queue.EncodeRxCompletionBuffer(b, i, buffers[i])
|
||||
}
|
||||
s.completionPipe.Flush()
|
||||
s.fillPipe.Flush()
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *serverTx) notificationsEnabled() bool {
|
||||
// notifications are considered to be enabled unless explicitly disabled.
|
||||
return s.sharedEventFDState.Load() != queue.EventFDDisabled
|
||||
}
|
||||
|
||||
func (s *serverTx) notify() {
|
||||
if s.notificationsEnabled() {
|
||||
s.eventFD.Notify()
|
||||
}
|
||||
}
|
||||
559
pkg/tcpip/link/sharedmem/sharedmem.go
Normal file
559
pkg/tcpip/link/sharedmem/sharedmem.go
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
// 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// Package sharedmem provides the implementation of data-link layer endpoints
|
||||
// backed by shared memory.
|
||||
//
|
||||
// Shared memory 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 sharedmem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/eventfd"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/sharedmem/queue"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// QueueConfig holds all the file descriptors needed to describe a tx or rx
|
||||
// queue over shared memory. It is used when creating new shared memory
|
||||
// endpoints to describe tx and rx queues.
|
||||
//
|
||||
// +stateify savable
|
||||
type QueueConfig struct {
|
||||
// DataFD is a file descriptor for the file that contains the data to
|
||||
// be transmitted via this queue. Descriptors contain offsets within
|
||||
// this file.
|
||||
DataFD int
|
||||
|
||||
// EventFD is a file descriptor for the event that is signaled when
|
||||
// data is becomes available in this queue.
|
||||
EventFD eventfd.Eventfd
|
||||
|
||||
// TxPipeFD is a file descriptor for the tx pipe associated with the
|
||||
// queue.
|
||||
TxPipeFD int
|
||||
|
||||
// RxPipeFD is a file descriptor for the rx pipe associated with the
|
||||
// queue.
|
||||
RxPipeFD int
|
||||
|
||||
// SharedDataFD is a file descriptor for the file that contains shared
|
||||
// state between the two ends of the queue. This data specifies, for
|
||||
// example, whether EventFD signaling is enabled or disabled.
|
||||
SharedDataFD int
|
||||
}
|
||||
|
||||
// FDs returns the FD's in the QueueConfig as a slice of ints. This must
|
||||
// be used in conjunction with QueueConfigFromFDs to ensure the order
|
||||
// of FDs matches when reconstructing the config when serialized or sent
|
||||
// as part of control messages.
|
||||
func (q *QueueConfig) FDs() []int {
|
||||
return []int{q.DataFD, q.EventFD.FD(), q.TxPipeFD, q.RxPipeFD, q.SharedDataFD}
|
||||
}
|
||||
|
||||
// QueueConfigFromFDs constructs a QueueConfig out of a slice of ints where each
|
||||
// entry represents an file descriptor. The order of FDs in the slice must be in
|
||||
// the order specified below for the config to be valid. QueueConfig.FDs()
|
||||
// should be used when the config needs to be serialized or sent as part of a
|
||||
// control message to ensure the correct order.
|
||||
func QueueConfigFromFDs(fds []int) (QueueConfig, error) {
|
||||
if len(fds) != 5 {
|
||||
return QueueConfig{}, fmt.Errorf("insufficient number of fds: len(fds): %d, want: 5", len(fds))
|
||||
}
|
||||
return QueueConfig{
|
||||
DataFD: fds[0],
|
||||
EventFD: eventfd.Wrap(fds[1]),
|
||||
TxPipeFD: fds[2],
|
||||
RxPipeFD: fds[3],
|
||||
SharedDataFD: fds[4],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Options specify the details about the sharedmem endpoint to be created.
|
||||
//
|
||||
// +stateify savable
|
||||
type Options struct {
|
||||
// MTU is the mtu to use for this endpoint.
|
||||
MTU uint32
|
||||
|
||||
// BufferSize is the size of each scatter/gather buffer that will hold packet
|
||||
// data.
|
||||
//
|
||||
// NOTE: This directly determines number of packets that can be held in
|
||||
// the ring buffer at any time. This does not have to be sized to the MTU as
|
||||
// the shared memory queue design allows usage of more than one buffer to be
|
||||
// used to make up a given packet.
|
||||
BufferSize uint32
|
||||
|
||||
// LinkAddress is the link address for this endpoint (required).
|
||||
LinkAddress tcpip.LinkAddress
|
||||
|
||||
// TX is the transmit queue configuration for this shared memory endpoint.
|
||||
TX QueueConfig
|
||||
|
||||
// RX is the receive queue configuration for this shared memory endpoint.
|
||||
RX QueueConfig
|
||||
|
||||
// PeerFD is the fd for the connected peer which can be used to detect
|
||||
// peer disconnects.
|
||||
PeerFD int
|
||||
|
||||
// OnClosed is a function that is called when the endpoint is being closed
|
||||
// (probably due to peer going away)
|
||||
OnClosed func(err tcpip.Error)
|
||||
|
||||
// TXChecksumOffload if true, indicates that this endpoints capability
|
||||
// set should include CapabilityTXChecksumOffload.
|
||||
TXChecksumOffload bool
|
||||
|
||||
// RXChecksumOffload if true, indicates that this endpoints capability
|
||||
// set should include CapabilityRXChecksumOffload.
|
||||
RXChecksumOffload bool
|
||||
|
||||
// VirtioNetHeaderRequired if true, indicates that all outbound packets should have
|
||||
// a virtio header and inbound packets should have a virtio header as well.
|
||||
VirtioNetHeaderRequired bool
|
||||
|
||||
// GSOMaxSize is the maximum GSO packet size. It is zero if GSO is
|
||||
// disabled. Note that only gVisor GSO is supported, not host GSO.
|
||||
GSOMaxSize uint32
|
||||
}
|
||||
|
||||
var (
|
||||
_ stack.LinkEndpoint = (*endpoint)(nil)
|
||||
_ stack.GSOEndpoint = (*endpoint)(nil)
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type endpoint struct {
|
||||
// bufferSize is the size of each individual buffer.
|
||||
// bufferSize is immutable.
|
||||
bufferSize uint32
|
||||
|
||||
// peerFD is an fd to the peer that can be used to detect when the
|
||||
// peer is gone.
|
||||
// peerFD is immutable.
|
||||
peerFD int
|
||||
|
||||
// caps holds the endpoint capabilities.
|
||||
caps stack.LinkEndpointCapabilities
|
||||
|
||||
// hdrSize is the size of the link layer header if any.
|
||||
// hdrSize is immutable.
|
||||
hdrSize uint32
|
||||
|
||||
// gSOMaxSize is the maximum GSO packet size. It is zero if GSO is
|
||||
// disabled. Note that only gVisor GSO is supported, not host GSO.
|
||||
// gsoMaxSize is immutable.
|
||||
gsoMaxSize uint32
|
||||
|
||||
// virtioNetHeaderRequired if true indicates that a virtio header is expected
|
||||
// in all inbound/outbound packets.
|
||||
virtioNetHeaderRequired bool
|
||||
|
||||
// rx is the receive queue.
|
||||
rx rx
|
||||
|
||||
// stopRequested determines whether the worker goroutines should stop.
|
||||
stopRequested atomicbitops.Uint32
|
||||
|
||||
// Wait group used to indicate that all workers have stopped.
|
||||
completed sync.WaitGroup
|
||||
|
||||
// onClosed is a function to be called when the FD's peer (if any) closes
|
||||
// its end of the communication pipe.
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
onClosed func(tcpip.Error) `state:"nosave"`
|
||||
|
||||
// mu protects the following fields.
|
||||
mu endpointRWMutex `state:"nosave"`
|
||||
|
||||
// tx is the transmit queue.
|
||||
// +checklocks:mu
|
||||
tx tx
|
||||
|
||||
// workerStarted specifies whether the worker goroutine was started.
|
||||
// +checklocks:mu
|
||||
workerStarted bool
|
||||
|
||||
// addr is the local address of this endpoint.
|
||||
//
|
||||
// +checklocks:mu
|
||||
addr tcpip.LinkAddress
|
||||
// mtu (maximum transmission unit) is the maximum size of a packet.
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
// New creates a new shared-memory-based endpoint. Buffers will be broken up
|
||||
// into buffers of "bufferSize" bytes.
|
||||
//
|
||||
// In order to release all resources held by the returned endpoint, Close()
|
||||
// must be called followed by Wait().
|
||||
func New(opts Options) (stack.LinkEndpoint, error) {
|
||||
e := &endpoint{
|
||||
mtu: opts.MTU,
|
||||
bufferSize: opts.BufferSize,
|
||||
addr: opts.LinkAddress,
|
||||
peerFD: opts.PeerFD,
|
||||
onClosed: opts.OnClosed,
|
||||
virtioNetHeaderRequired: opts.VirtioNetHeaderRequired,
|
||||
gsoMaxSize: opts.GSOMaxSize,
|
||||
}
|
||||
|
||||
if err := e.tx.init(opts.BufferSize, &opts.TX); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := e.rx.init(opts.BufferSize, &opts.RX); err != nil {
|
||||
e.tx.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.caps = stack.LinkEndpointCapabilities(0)
|
||||
if opts.RXChecksumOffload {
|
||||
e.caps |= stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
if opts.TXChecksumOffload {
|
||||
e.caps |= stack.CapabilityTXChecksumOffload
|
||||
}
|
||||
|
||||
if opts.LinkAddress != "" {
|
||||
e.hdrSize = header.EthernetMinimumSize
|
||||
e.caps |= stack.CapabilityResolutionRequired
|
||||
}
|
||||
|
||||
if opts.VirtioNetHeaderRequired {
|
||||
e.hdrSize += header.VirtioNetHeaderSize
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
|
||||
func (e *endpoint) SetOnCloseAction(func()) {}
|
||||
|
||||
// Close frees most resources associated with the endpoint. Wait() must be
|
||||
// called after Close() in order to free the rest.
|
||||
func (e *endpoint) Close() {
|
||||
// Tell dispatch goroutine to stop, then write to the eventfd so that
|
||||
// it wakes up in case it's sleeping.
|
||||
if e.stopRequested.Swap(1) == 1 {
|
||||
// It is already closed.
|
||||
return
|
||||
}
|
||||
e.rx.eventFD.Notify()
|
||||
|
||||
// Cleanup the queues inline if the worker hasn't started yet; we also
|
||||
// know it won't start from now on because stopRequested is set to 1.
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
workerPresent := e.workerStarted
|
||||
|
||||
if !workerPresent {
|
||||
e.tx.cleanup()
|
||||
e.rx.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait. It waits until all workers have
|
||||
// stopped after a Close() call.
|
||||
func (e *endpoint) Wait() {
|
||||
e.completed.Wait()
|
||||
e.rx.eventFD.Close()
|
||||
}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.Attach. It launches the goroutine that
|
||||
// reads packets from the rx queue.
|
||||
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
if dispatcher == nil {
|
||||
e.Close()
|
||||
return
|
||||
}
|
||||
e.mu.Lock()
|
||||
if !e.workerStarted && e.stopRequested.Load() == 0 {
|
||||
e.workerStarted = true
|
||||
e.completed.Add(1)
|
||||
|
||||
// Spin up a goroutine to monitor for peer shutdown.
|
||||
if e.peerFD >= 0 {
|
||||
e.completed.Add(1)
|
||||
go func() {
|
||||
defer e.completed.Done()
|
||||
b := make([]byte, 1)
|
||||
// When sharedmem endpoint is in use the peerFD is never used for any data
|
||||
// transfer and this Read should only return if the peer is shutting down.
|
||||
_, errno := rawfile.BlockingRead(e.peerFD, b)
|
||||
if e.onClosed != nil {
|
||||
if errno == 0 {
|
||||
e.onClosed(nil)
|
||||
} else {
|
||||
e.onClosed(tcpip.TranslateErrno(errno))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 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.
|
||||
}
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
||||
func (e *endpoint) IsAttached() bool {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.workerStarted
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.MTU.
|
||||
func (e *endpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
func (e *endpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
||||
func (e *endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return e.caps
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. It returns the
|
||||
// ethernet frame header size.
|
||||
func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
return uint16(e.hdrSize)
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.LinkAddress. It returns the local
|
||||
// link address.
|
||||
func (e *endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.addr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *endpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.addr = addr
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (e *endpoint) AddHeader(pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
// Add ethernet header if needed.
|
||||
if len(e.addr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
|
||||
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
|
||||
Type: pkt.NetworkProtocolNumber,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *endpoint) parseHeader(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (e *endpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
// Add ethernet header if needed.
|
||||
if len(e.addr) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return e.parseHeader(pkt)
|
||||
}
|
||||
|
||||
func (e *endpoint) AddVirtioNetHeader(pkt *stack.PacketBuffer) {
|
||||
virtio := header.VirtioNetHeader(pkt.VirtioNetHeader().Push(header.VirtioNetHeaderSize))
|
||||
virtio.Encode(&header.VirtioNetHeaderFields{})
|
||||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
func (e *endpoint) writePacketLocked(r stack.RouteInfo, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
if e.virtioNetHeaderRequired {
|
||||
e.AddVirtioNetHeader(pkt)
|
||||
}
|
||||
|
||||
// Transmit the packet.
|
||||
b := pkt.ToBuffer()
|
||||
defer b.Release()
|
||||
ok := e.tx.transmit(b)
|
||||
if !ok {
|
||||
return &tcpip.ErrWouldBlock{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.WritePackets.
|
||||
func (e *endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
var err tcpip.Error
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if err = e.writePacketLocked(pkt.EgressRoute, pkt.NetworkProtocolNumber, pkt); err != nil {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
// WritePackets never returns an error if it successfully transmitted at least
|
||||
// one packet.
|
||||
if err != nil && n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
e.tx.notify()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// dispatchLoop reads packets from the rx queue in a loop and dispatches them
|
||||
// to the network stack.
|
||||
func (e *endpoint) dispatchLoop(d stack.NetworkDispatcher) {
|
||||
// Post initial set of buffers.
|
||||
limit := e.rx.q.PostedBuffersLimit()
|
||||
if l := uint64(len(e.rx.data)) / uint64(e.bufferSize); limit > l {
|
||||
limit = l
|
||||
}
|
||||
for i := uint64(0); i < limit; i++ {
|
||||
b := queue.RxBuffer{
|
||||
Offset: i * uint64(e.bufferSize),
|
||||
Size: e.bufferSize,
|
||||
ID: i,
|
||||
}
|
||||
if !e.rx.q.PostBuffers([]queue.RxBuffer{b}) {
|
||||
log.Warningf("Unable to post %v-th buffer", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Read in a loop until a stop is requested.
|
||||
var rxb []queue.RxBuffer
|
||||
for e.stopRequested.Load() == 0 {
|
||||
var n uint32
|
||||
rxb, n = e.rx.postAndReceive(rxb, &e.stopRequested)
|
||||
|
||||
// Copy data from the shared area to its own buffer, then
|
||||
// prepare to repost the buffer.
|
||||
v := buffer.NewView(int(n))
|
||||
v.Grow(int(n))
|
||||
offset := uint32(0)
|
||||
for i := range rxb {
|
||||
v.WriteAt(e.rx.data[rxb[i].Offset:][:rxb[i].Size], int(offset))
|
||||
offset += rxb[i].Size
|
||||
|
||||
rxb[i].Size = e.bufferSize
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithView(v),
|
||||
})
|
||||
|
||||
if e.virtioNetHeaderRequired {
|
||||
_, ok := pkt.VirtioNetHeader().Consume(header.VirtioNetHeaderSize)
|
||||
if !ok {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var proto tcpip.NetworkProtocolNumber
|
||||
e.mu.RLock()
|
||||
addrLen := len(e.addr)
|
||||
e.mu.RUnlock()
|
||||
if addrLen != 0 {
|
||||
if !e.parseHeader(pkt) {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
proto = header.Ethernet(pkt.LinkHeader().Slice()).Type()
|
||||
} else {
|
||||
// We don't get any indication of what the packet is, so try to guess
|
||||
// if it's an IPv4 or IPv6 packet.
|
||||
// IP version information is at the first octet, so pulling up 1 byte.
|
||||
h, ok := pkt.Data().PullUp(1)
|
||||
if !ok {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
switch header.IPVersion(h) {
|
||||
case header.IPv4Version:
|
||||
proto = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
proto = header.IPv6ProtocolNumber
|
||||
default:
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Send packet up the stack.
|
||||
d.DeliverNetworkPacket(proto, pkt)
|
||||
pkt.DecRef()
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
// Clean state.
|
||||
e.tx.cleanup()
|
||||
e.rx.cleanup()
|
||||
|
||||
e.completed.Done()
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
|
||||
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareEther
|
||||
}
|
||||
|
||||
// GSOMaxSize implements stack.GSOEndpoint.
|
||||
func (e *endpoint) GSOMaxSize() uint32 {
|
||||
return e.gsoMaxSize
|
||||
}
|
||||
|
||||
// SupportsGSO implements stack.GSOEndpoint.
|
||||
func (e *endpoint) SupportedGSO() stack.SupportedGSO {
|
||||
return stack.GVisorGSOSupported
|
||||
}
|
||||
399
pkg/tcpip/link/sharedmem/sharedmem_server.go
Normal file
399
pkg/tcpip/link/sharedmem/sharedmem_server.go
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package sharedmem
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type serverEndpoint struct {
|
||||
// bufferSize is the size of each individual buffer.
|
||||
// bufferSize is immutable.
|
||||
bufferSize uint32
|
||||
|
||||
// rx is the receive queue.
|
||||
rx serverRx
|
||||
|
||||
// stopRequested determines whether the worker goroutines should stop.
|
||||
stopRequested atomicbitops.Uint32
|
||||
|
||||
// Wait group used to indicate that all workers have stopped.
|
||||
completed sync.WaitGroup `state:"nosave"`
|
||||
|
||||
// peerFD is an fd to the peer that can be used to detect when the peer is
|
||||
// gone.
|
||||
// peerFD is immutable.
|
||||
peerFD int
|
||||
|
||||
// caps holds the endpoint capabilities.
|
||||
caps stack.LinkEndpointCapabilities
|
||||
|
||||
// hdrSize is the size of the link layer header if any.
|
||||
// hdrSize is immutable.
|
||||
hdrSize uint32
|
||||
|
||||
// virtioNetHeaderRequired if true indicates that a virtio header is expected
|
||||
// in all inbound/outbound packets.
|
||||
virtioNetHeaderRequired bool
|
||||
|
||||
// onClosed is a function to be called when the FD's peer (if any) closes its
|
||||
// end of the communication pipe.
|
||||
onClosed func(tcpip.Error) `state:"nosave"`
|
||||
|
||||
// mu protects the following fields.
|
||||
mu serverEndpointRWMutex `state:"nosave"`
|
||||
|
||||
// tx is the transmit queue.
|
||||
// +checklocks:mu
|
||||
tx serverTx
|
||||
|
||||
// workerStarted specifies whether the worker goroutine was started.
|
||||
// +checklocks:mu
|
||||
workerStarted bool
|
||||
|
||||
// addr is the local address of this endpoint.
|
||||
//
|
||||
// +checklocks:mu
|
||||
addr tcpip.LinkAddress
|
||||
// mtu (maximum transmission unit) is the maximum size of a packet.
|
||||
// +checklocks:mu
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
// NewServerEndpoint creates a new shared-memory-based endpoint. Buffers will be
|
||||
// broken up into buffers of "bufferSize" bytes.
|
||||
func NewServerEndpoint(opts Options) (stack.LinkEndpoint, error) {
|
||||
e := &serverEndpoint{
|
||||
mtu: opts.MTU,
|
||||
bufferSize: opts.BufferSize,
|
||||
addr: opts.LinkAddress,
|
||||
peerFD: opts.PeerFD,
|
||||
onClosed: opts.OnClosed,
|
||||
}
|
||||
|
||||
if err := e.tx.init(&opts.RX); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := e.rx.init(&opts.TX); err != nil {
|
||||
e.tx.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.caps = stack.LinkEndpointCapabilities(0)
|
||||
if opts.RXChecksumOffload {
|
||||
e.caps |= stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
if opts.TXChecksumOffload {
|
||||
e.caps |= stack.CapabilityTXChecksumOffload
|
||||
}
|
||||
|
||||
if opts.LinkAddress != "" {
|
||||
e.hdrSize = header.EthernetMinimumSize
|
||||
e.caps |= stack.CapabilityResolutionRequired
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
|
||||
func (*serverEndpoint) SetOnCloseAction(func()) {}
|
||||
|
||||
// Close frees all resources associated with the endpoint.
|
||||
func (e *serverEndpoint) Close() {
|
||||
// Tell dispatch goroutine to stop, then write to the eventfd so that it wakes
|
||||
// up in case it's sleeping.
|
||||
e.stopRequested.Store(1)
|
||||
e.rx.eventFD.Notify()
|
||||
|
||||
// Cleanup the queues inline if the worker hasn't started yet; we also know it
|
||||
// won't start from now on because stopRequested is set to 1.
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
workerPresent := e.workerStarted
|
||||
|
||||
if !workerPresent {
|
||||
e.tx.cleanup()
|
||||
e.rx.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait implements stack.LinkEndpoint.Wait. It waits until all workers have
|
||||
// stopped after a Close() call.
|
||||
func (e *serverEndpoint) Wait() {
|
||||
e.completed.Wait()
|
||||
}
|
||||
|
||||
// Attach implements stack.LinkEndpoint.Attach. It launches the goroutine that
|
||||
// reads packets from the rx queue.
|
||||
func (e *serverEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.mu.Lock()
|
||||
if !e.workerStarted && e.stopRequested.Load() == 0 {
|
||||
e.workerStarted = true
|
||||
e.completed.Add(1)
|
||||
if e.peerFD >= 0 {
|
||||
e.completed.Add(1)
|
||||
// Spin up a goroutine to monitor for peer shutdown.
|
||||
go func() {
|
||||
b := make([]byte, 1)
|
||||
// When sharedmem endpoint is in use the peerFD is never used for any
|
||||
// data transfer and this Read should only return if the peer is
|
||||
// shutting down.
|
||||
_, errno := rawfile.BlockingRead(e.peerFD, b)
|
||||
if e.onClosed != nil {
|
||||
if errno == 0 {
|
||||
e.onClosed(nil)
|
||||
} else {
|
||||
e.onClosed(tcpip.TranslateErrno(errno))
|
||||
}
|
||||
}
|
||||
e.completed.Done()
|
||||
}()
|
||||
}
|
||||
// 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.
|
||||
}
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// IsAttached implements stack.LinkEndpoint.IsAttached.
|
||||
func (e *serverEndpoint) IsAttached() bool {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.workerStarted
|
||||
}
|
||||
|
||||
// MTU implements stack.LinkEndpoint.MTU.
|
||||
func (e *serverEndpoint) MTU() uint32 {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.mtu
|
||||
}
|
||||
|
||||
func (e *serverEndpoint) SetMTU(mtu uint32) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.mtu = mtu
|
||||
}
|
||||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
||||
func (e *serverEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return e.caps
|
||||
}
|
||||
|
||||
// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. It returns the
|
||||
// ethernet frame header size.
|
||||
func (e *serverEndpoint) MaxHeaderLength() uint16 {
|
||||
return uint16(e.hdrSize)
|
||||
}
|
||||
|
||||
// LinkAddress implements stack.LinkEndpoint.LinkAddress. It returns the local
|
||||
// link address.
|
||||
func (e *serverEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.addr
|
||||
}
|
||||
|
||||
// SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress.
|
||||
func (e *serverEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.addr = addr
|
||||
}
|
||||
|
||||
// AddHeader implements stack.LinkEndpoint.AddHeader.
|
||||
func (e *serverEndpoint) AddHeader(pkt *stack.PacketBuffer) {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
// Add ethernet header if needed.
|
||||
if len(e.addr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
|
||||
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
|
||||
Type: pkt.NetworkProtocolNumber,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *serverEndpoint) parseHeader(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
|
||||
func (e *serverEndpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
// Add ethernet header if needed.
|
||||
if len(e.addr) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return e.parseHeader(pkt)
|
||||
}
|
||||
|
||||
func (e *serverEndpoint) AddVirtioNetHeader(pkt *stack.PacketBuffer) {
|
||||
virtio := header.VirtioNetHeader(pkt.VirtioNetHeader().Push(header.VirtioNetHeaderSize))
|
||||
virtio.Encode(&header.VirtioNetHeaderFields{})
|
||||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
func (e *serverEndpoint) writePacketLocked(r stack.RouteInfo, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
if e.virtioNetHeaderRequired {
|
||||
e.AddVirtioNetHeader(pkt)
|
||||
}
|
||||
|
||||
ok := e.tx.transmit(pkt)
|
||||
if !ok {
|
||||
return &tcpip.ErrWouldBlock{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePacket writes outbound packets to the file descriptor. If it is not
|
||||
// currently writable, the packet is dropped.
|
||||
// WritePacket implements stack.LinkEndpoint.WritePacket.
|
||||
func (e *serverEndpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
// Transmit the packet.
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if err := e.writePacketLocked(pkt.EgressRoute, pkt.NetworkProtocolNumber, pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
e.tx.notify()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePackets implements stack.LinkEndpoint.WritePackets.
|
||||
func (e *serverEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
var err tcpip.Error
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if err = e.writePacketLocked(pkt.EgressRoute, pkt.NetworkProtocolNumber, pkt); err != nil {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
// WritePackets never returns an error if it successfully transmitted at least
|
||||
// one packet.
|
||||
if err != nil && n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
e.tx.notify()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// dispatchLoop reads packets from the rx queue in a loop and dispatches them
|
||||
// to the network stack.
|
||||
func (e *serverEndpoint) dispatchLoop(d stack.NetworkDispatcher) {
|
||||
for e.stopRequested.Load() == 0 {
|
||||
b := e.rx.receive()
|
||||
if b == nil {
|
||||
e.rx.EnableNotification()
|
||||
// Now pull again to make sure we didn't receive any packets
|
||||
// while notifications were not enabled.
|
||||
for {
|
||||
b = e.rx.receive()
|
||||
if b != nil {
|
||||
// Disable notifications as we only need to be notified when we are going
|
||||
// to block on eventFD. This should prevent the peer from needlessly
|
||||
// writing to eventFD when this end is already awake and processing
|
||||
// packets.
|
||||
e.rx.DisableNotification()
|
||||
break
|
||||
}
|
||||
e.rx.waitForPackets()
|
||||
}
|
||||
}
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithView(b),
|
||||
})
|
||||
if e.virtioNetHeaderRequired {
|
||||
_, ok := pkt.VirtioNetHeader().Consume(header.VirtioNetHeaderSize)
|
||||
if !ok {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
}
|
||||
var proto tcpip.NetworkProtocolNumber
|
||||
e.mu.RLock()
|
||||
addrLen := len(e.addr)
|
||||
e.mu.RUnlock()
|
||||
if addrLen != 0 {
|
||||
if !e.parseHeader(pkt) {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
proto = header.Ethernet(pkt.LinkHeader().Slice()).Type()
|
||||
} else {
|
||||
// We don't get any indication of what the packet is, so try to guess
|
||||
// if it's an IPv4 or IPv6 packet.
|
||||
// IP version information is at the first octet, so pulling up 1 byte.
|
||||
h, ok := pkt.Data().PullUp(1)
|
||||
if !ok {
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
switch header.IPVersion(h) {
|
||||
case header.IPv4Version:
|
||||
proto = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
proto = header.IPv6ProtocolNumber
|
||||
default:
|
||||
pkt.DecRef()
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Send packet up the stack.
|
||||
d.DeliverNetworkPacket(proto, pkt)
|
||||
pkt.DecRef()
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
// Clean state.
|
||||
e.tx.cleanup()
|
||||
e.rx.cleanup()
|
||||
|
||||
e.completed.Done()
|
||||
}
|
||||
|
||||
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType
|
||||
func (e *serverEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
if e.hdrSize > 0 {
|
||||
return header.ARPHardwareEther
|
||||
}
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue