Compare commits

...
Author SHA1 Message Date
moloch-- 211cd7750a updated deps 2022-02-17 19:38:39 -06:00
moloch-- 93225d07a4 updated deps 2022-02-17 19:38:35 -06:00
moloch-- a46257c2c4 updated inet.af 2022-02-17 19:24:07 -06:00
moloch-- 2f0b953210 updated inet.af 2022-02-17 19:23:42 -06:00
169 changed files with 19045 additions and 7621 deletions

No files matched your search

+2 -1
View File
@@ -57,7 +57,7 @@ require (
gorm.io/driver/postgres v1.0.5
gorm.io/driver/sqlite v1.1.3
gorm.io/gorm v1.21.14
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
inet.af/netstack v0.0.0-20220214151720-7585b01ddccf
)
require (
@@ -65,6 +65,7 @@ require (
github.com/shirou/gopsutil/v3 v3.21.10
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d
modernc.org/sqlite v1.14.3
)
+4 -3
View File
@@ -595,7 +595,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -655,6 +654,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d h1:9+v0G0naRhLPOJEeJOL6NuXTtAHHwmkyZlgQJ0XcQ8I=
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
golang.zx2c4.com/wireguard v0.0.0-20210311162910-5f0c8b942d93 h1:nb+w1m5cgug4cfeqcWJx4lcj8cYA+wWAz7aGI96Wp/Q=
golang.zx2c4.com/wireguard v0.0.0-20210311162910-5f0c8b942d93/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc=
@@ -744,8 +745,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
inet.af/netstack v0.0.0-20220214151720-7585b01ddccf h1:kwr6UZ4fMnzVJfpnjIl4ej23vfByfAUVLaiIr69326s=
inet.af/netstack v0.0.0-20220214151720-7585b01ddccf/go.mod h1:KOJdAzQzMLKzwFEdOOnrnSrLIhaFVB+NQoME/e5wllA=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+329 -89
View File
@@ -15,10 +15,12 @@ import (
"io"
"net"
"os"
"regexp"
"strconv"
"strings"
"time"
"golang.zx2c4.com/go118/netip"
"golang.zx2c4.com/wireguard/tun"
"golang.org/x/net/dns/dnsmessage"
@@ -29,8 +31,10 @@ import (
"inet.af/netstack/tcpip/network/ipv4"
"inet.af/netstack/tcpip/network/ipv6"
"inet.af/netstack/tcpip/stack"
"inet.af/netstack/tcpip/transport/icmp"
"inet.af/netstack/tcpip/transport/tcp"
"inet.af/netstack/tcpip/transport/udp"
"inet.af/netstack/waiter"
)
type netTun struct {
@@ -39,11 +43,14 @@ type netTun struct {
events chan tun.Event
incomingPacket chan buffer.VectorisedView
mtu int
dnsServers []net.IP
dnsServers []netip.Addr
hasV4, hasV6 bool
}
type endpoint netTun
type Net netTun
type (
endpoint netTun
Net netTun
)
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcher = dispatcher
@@ -75,12 +82,16 @@ func (*endpoint) LinkAddress() tcpip.LinkAddress {
func (*endpoint) Wait() {}
func (e *endpoint) WritePacket(_ stack.RouteInfo, _ *stack.GSO, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
func (e *endpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
e.incomingPacket <- buffer.NewVectorisedView(pkt.Size(), pkt.Views())
return nil
}
func (e *endpoint) WritePackets(stack.RouteInfo, *stack.GSO, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
func (e *endpoint) WritePackets(stack.RouteInfo, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
panic("not implemented")
}
func (e *endpoint) WriteRawPacket(*stack.PacketBuffer) tcpip.Error {
panic("not implemented")
}
@@ -91,10 +102,10 @@ func (*endpoint) ARPHardwareType() header.ARPHardwareType {
func (e *endpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
}
func CreateNetTUN(localAddresses, dnsServers []net.IP, mtu int) (tun.Device, *Net, error) {
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device, *Net, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
HandleLocal: true,
}
dev := &netTun{
@@ -109,33 +120,31 @@ func CreateNetTUN(localAddresses, dnsServers []net.IP, mtu int) (tun.Device, *Ne
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
}
for _, ip := range localAddresses {
if ip4 := ip.To4(); ip4 != nil {
tcpipErr = dev.stack.AddAddress(1, ipv4.ProtocolNumber, tcpip.Address(ip4))
if tcpipErr != nil {
return nil, nil, fmt.Errorf("AddAddress(%v): %v", ip4, tcpipErr)
}
var protoNumber tcpip.NetworkProtocolNumber
if ip.Is4() {
protoNumber = ipv4.ProtocolNumber
} else if ip.Is6() {
protoNumber = ipv6.ProtocolNumber
}
protoAddr := tcpip.ProtocolAddress{
Protocol: protoNumber,
AddressWithPrefix: tcpip.Address(ip.AsSlice()).WithPrefix(),
}
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
if tcpipErr != nil {
return nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
}
if ip.Is4() {
dev.hasV4 = true
} else {
tcpipErr = dev.stack.AddAddress(1, ipv6.ProtocolNumber, tcpip.Address(ip))
if tcpipErr != nil {
return nil, nil, fmt.Errorf("AddAddress(%v): %v", ip4, tcpipErr)
}
} else if ip.Is6() {
dev.hasV6 = true
}
}
if dev.hasV4 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
tcpipErr = dev.stack.SetForwarding(header.IPv4ProtocolNumber, true)
if tcpipErr != nil {
return nil, nil, fmt.Errorf("SetForwarding(): %v", tcpipErr)
}
}
if dev.hasV6 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
tcpipErr = dev.stack.SetForwarding(header.IPv6ProtocolNumber, true)
if tcpipErr != nil {
return nil, nil, fmt.Errorf("SetForwarding(): %v", tcpipErr)
}
}
dev.events <- tun.EventUp
@@ -199,62 +208,286 @@ func (tun *netTun) MTU() (int, error) {
return tun.mtu, nil
}
func convertToFullAddr(ip net.IP, port int) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
if ip4 := ip.To4(); ip4 != nil {
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.Address(ip4),
Port: uint16(port),
}, ipv4.ProtocolNumber
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var protoNumber tcpip.NetworkProtocolNumber
if endpoint.Addr().Is4() {
protoNumber = ipv4.ProtocolNumber
} else {
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.Address(ip),
Port: uint16(port),
}, ipv6.ProtocolNumber
protoNumber = ipv6.ProtocolNumber
}
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.Address(endpoint.Addr().AsSlice()),
Port: endpoint.Port(),
}, protoNumber
}
func (net *Net) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error) {
fa, pn := convertToFullAddr(addr)
return gonet.DialContextTCP(ctx, net.stack, fa, pn)
}
func (net *Net) DialContextTCP(ctx context.Context, addr *net.TCPAddr) (*gonet.TCPConn, error) {
if addr == nil {
panic("todo: deal with auto addr semantics for nil addr")
return net.DialContextTCPAddrPort(ctx, netip.AddrPort{})
}
fa, pn := convertToFullAddr(addr.IP, addr.Port)
return gonet.DialContextTCP(ctx, net.stack, fa, pn)
return net.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(netip.AddrFromSlice(addr.IP), uint16(addr.Port)))
}
func (net *Net) DialTCPAddrPort(addr netip.AddrPort) (*gonet.TCPConn, error) {
fa, pn := convertToFullAddr(addr)
return gonet.DialTCP(net.stack, fa, pn)
}
func (net *Net) DialTCP(addr *net.TCPAddr) (*gonet.TCPConn, error) {
if addr == nil {
panic("todo: deal with auto addr semantics for nil addr")
return net.DialTCPAddrPort(netip.AddrPort{})
}
fa, pn := convertToFullAddr(addr.IP, addr.Port)
return gonet.DialTCP(net.stack, fa, pn)
return net.DialTCPAddrPort(netip.AddrPortFrom(netip.AddrFromSlice(addr.IP), uint16(addr.Port)))
}
func (net *Net) ListenTCPAddrPort(addr netip.AddrPort) (*gonet.TCPListener, error) {
fa, pn := convertToFullAddr(addr)
return gonet.ListenTCP(net.stack, fa, pn)
}
func (net *Net) ListenTCP(addr *net.TCPAddr) (*gonet.TCPListener, error) {
if addr == nil {
panic("todo: deal with auto addr semantics for nil addr")
return net.ListenTCPAddrPort(netip.AddrPort{})
}
fa, pn := convertToFullAddr(addr.IP, addr.Port)
return gonet.ListenTCP(net.stack, fa, pn)
return net.ListenTCPAddrPort(netip.AddrPortFrom(netip.AddrFromSlice(addr.IP), uint16(addr.Port)))
}
func (net *Net) DialUDP(laddr, raddr *net.UDPAddr) (*gonet.UDPConn, error) {
func (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, error) {
var lfa, rfa *tcpip.FullAddress
var pn tcpip.NetworkProtocolNumber
if laddr != nil {
if laddr.IsValid() || laddr.Port() > 0 {
var addr tcpip.FullAddress
addr, pn = convertToFullAddr(laddr.IP, laddr.Port)
addr, pn = convertToFullAddr(laddr)
lfa = &addr
}
if raddr != nil {
if raddr.IsValid() || raddr.Port() > 0 {
var addr tcpip.FullAddress
addr, pn = convertToFullAddr(raddr.IP, raddr.Port)
addr, pn = convertToFullAddr(raddr)
rfa = &addr
}
return gonet.DialUDP(net.stack, lfa, rfa, pn)
}
func (net *Net) ListenUDPAddrPort(laddr netip.AddrPort) (*gonet.UDPConn, error) {
return net.DialUDPAddrPort(laddr, netip.AddrPort{})
}
func (net *Net) DialUDP(laddr, raddr *net.UDPAddr) (*gonet.UDPConn, error) {
var la, ra netip.AddrPort
if laddr != nil {
la = netip.AddrPortFrom(netip.AddrFromSlice(laddr.IP), uint16(laddr.Port))
}
if raddr != nil {
ra = netip.AddrPortFrom(netip.AddrFromSlice(raddr.IP), uint16(raddr.Port))
}
return net.DialUDPAddrPort(la, ra)
}
func (net *Net) ListenUDP(laddr *net.UDPAddr) (*gonet.UDPConn, error) {
return net.DialUDP(laddr, nil)
}
type PingConn struct {
laddr PingAddr
raddr PingAddr
wq waiter.Queue
ep tcpip.Endpoint
deadline *time.Timer
}
type PingAddr struct{ addr netip.Addr }
func (ia PingAddr) String() string {
return ia.addr.String()
}
func (ia PingAddr) Network() string {
if ia.addr.Is4() {
return "ping4"
} else if ia.addr.Is6() {
return "ping6"
}
return "ping"
}
func (ia PingAddr) Addr() netip.Addr {
return ia.addr
}
func PingAddrFromAddr(addr netip.Addr) *PingAddr {
return &PingAddr{addr}
}
func (net *Net) DialPingAddr(laddr, raddr netip.Addr) (*PingConn, error) {
if !laddr.IsValid() && !raddr.IsValid() {
return nil, errors.New("ping dial: invalid address")
}
v6 := laddr.Is6() || raddr.Is6()
bind := laddr.IsValid()
if !bind {
if v6 {
laddr = netip.IPv6Unspecified()
} else {
laddr = netip.IPv4Unspecified()
}
}
tn := icmp.ProtocolNumber4
pn := ipv4.ProtocolNumber
if v6 {
tn = icmp.ProtocolNumber6
pn = ipv6.ProtocolNumber
}
pc := &PingConn{
laddr: PingAddr{laddr},
deadline: time.NewTimer(time.Hour << 10),
}
pc.deadline.Stop()
ep, tcpipErr := net.stack.NewEndpoint(tn, pn, &pc.wq)
if tcpipErr != nil {
return nil, fmt.Errorf("ping socket: endpoint: %s", tcpipErr)
}
pc.ep = ep
if bind {
fa, _ := convertToFullAddr(netip.AddrPortFrom(laddr, 0))
if tcpipErr = pc.ep.Bind(fa); tcpipErr != nil {
return nil, fmt.Errorf("ping bind: %s", tcpipErr)
}
}
if raddr.IsValid() {
pc.raddr = PingAddr{raddr}
fa, _ := convertToFullAddr(netip.AddrPortFrom(raddr, 0))
if tcpipErr = pc.ep.Connect(fa); tcpipErr != nil {
return nil, fmt.Errorf("ping connect: %s", tcpipErr)
}
}
return pc, nil
}
func (net *Net) ListenPingAddr(laddr netip.Addr) (*PingConn, error) {
return net.DialPingAddr(laddr, netip.Addr{})
}
func (net *Net) DialPing(laddr, raddr *PingAddr) (*PingConn, error) {
var la, ra netip.Addr
if laddr != nil {
la = laddr.addr
}
if raddr != nil {
ra = raddr.addr
}
return net.DialPingAddr(la, ra)
}
func (net *Net) ListenPing(laddr *PingAddr) (*PingConn, error) {
var la netip.Addr
if laddr != nil {
la = laddr.addr
}
return net.ListenPingAddr(la)
}
func (pc *PingConn) LocalAddr() net.Addr {
return pc.laddr
}
func (pc *PingConn) RemoteAddr() net.Addr {
return pc.raddr
}
func (pc *PingConn) Close() error {
pc.deadline.Reset(0)
pc.ep.Close()
return nil
}
func (pc *PingConn) SetWriteDeadline(t time.Time) error {
return errors.New("not implemented")
}
func (pc *PingConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
var na netip.Addr
switch v := addr.(type) {
case *PingAddr:
na = v.addr
case *net.IPAddr:
na = netip.AddrFromSlice(v.IP)
default:
return 0, fmt.Errorf("ping write: wrong net.Addr type")
}
if !((na.Is4() && pc.laddr.addr.Is4()) || (na.Is6() && pc.laddr.addr.Is6())) {
return 0, fmt.Errorf("ping write: mismatched protocols")
}
buf := buffer.NewViewFromBytes(p)
rdr := buf.Reader()
rfa, _ := convertToFullAddr(netip.AddrPortFrom(na, 0))
// won't block, no deadlines
n64, tcpipErr := pc.ep.Write(&rdr, tcpip.WriteOptions{
To: &rfa,
})
if tcpipErr != nil {
return int(n64), fmt.Errorf("ping write: %s", tcpipErr)
}
return int(n64), nil
}
func (pc *PingConn) Write(p []byte) (n int, err error) {
return pc.WriteTo(p, &pc.raddr)
}
func (pc *PingConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
e, notifyCh := waiter.NewChannelEntry(nil)
pc.wq.EventRegister(&e, waiter.EventIn)
defer pc.wq.EventUnregister(&e)
select {
case <-pc.deadline.C:
return 0, nil, os.ErrDeadlineExceeded
case <-notifyCh:
}
w := tcpip.SliceWriter(p)
res, tcpipErr := pc.ep.Read(&w, tcpip.ReadOptions{
NeedRemoteAddr: true,
})
if tcpipErr != nil {
return 0, nil, fmt.Errorf("ping read: %s", tcpipErr)
}
addr = &PingAddr{netip.AddrFromSlice([]byte(res.RemoteAddr.Addr))}
return res.Count, addr, nil
}
func (pc *PingConn) Read(p []byte) (n int, err error) {
n, _, err = pc.ReadFrom(p)
return
}
func (pc *PingConn) SetDeadline(t time.Time) error {
// pc.SetWriteDeadline is unimplemented
return pc.SetReadDeadline(t)
}
func (pc *PingConn) SetReadDeadline(t time.Time) error {
pc.deadline.Reset(t.Sub(time.Now()))
return nil
}
var (
errNoSuchHost = errors.New("no such host")
errLameReferral = errors.New("lame referral")
@@ -430,7 +663,7 @@ func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []by
return p, h, nil
}
func (tnet *Net) exchange(ctx context.Context, server net.IP, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
q.Class = dnsmessage.ClassINET
id, udpReq, tcpReq, err := newRequest(q)
if err != nil {
@@ -444,9 +677,9 @@ func (tnet *Net) exchange(ctx context.Context, server net.IP, q dnsmessage.Quest
var c net.Conn
var err error
if useUDP {
c, err = tnet.DialUDP(nil, &net.UDPAddr{IP: server, Port: 53})
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, netip.AddrPortFrom(server, 53))
} else {
c, err = tnet.DialContextTCP(ctx, &net.TCPAddr{IP: server, Port: 53})
c, err = tnet.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(server, 53))
}
if err != nil {
@@ -597,8 +830,8 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
zlen = zidx
}
}
if ip := net.ParseIP(host[:zlen]); ip != nil {
return []string{host[:zlen]}, nil
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []string{ip.String()}, nil
}
if !isDomainName(host) {
@@ -609,7 +842,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
server string
error
}
var addrsV4, addrsV6 []net.IP
var addrsV4, addrsV6 []netip.Addr
lanes := 0
if tnet.hasV4 {
lanes++
@@ -664,7 +897,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
break loop
}
addrsV4 = append(addrsV4, net.IP(a.A[:]))
addrsV4 = append(addrsV4, netip.AddrFrom4(a.A))
case dnsmessage.TypeAAAA:
aaaa, err := result.p.AAAAResource()
@@ -676,7 +909,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
break loop
}
addrsV6 = append(addrsV6, net.IP(aaaa.AAAA[:]))
addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA))
default:
if err := result.p.SkipAnswer(); err != nil {
@@ -692,7 +925,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
}
// We don't do RFC6724. Instead just put V6 addresess first if an IPv6 address is enabled
var addrs []net.IP
var addrs []netip.Addr
if tnet.hasV6 {
addrs = append(addrsV6, addrsV4...)
} else {
@@ -729,44 +962,48 @@ func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, er
return now.Add(timeout), nil
}
var protoSplitter = regexp.MustCompile(`^(tcp|udp|ping)(4|6)?$`)
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
if ctx == nil {
panic("nil context")
}
var acceptV4, acceptV6, useUDP bool
if len(network) == 3 {
var acceptV4, acceptV6 bool
matches := protoSplitter.FindStringSubmatch(network)
if matches == nil {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
} else if len(matches[2]) == 0 {
acceptV4 = true
acceptV6 = true
} else if len(network) == 4 {
acceptV4 = network[3] == '4'
acceptV6 = network[3] == '6'
} else {
acceptV4 = matches[2][0] == '4'
acceptV6 = !acceptV4
}
if !acceptV4 && !acceptV6 {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
}
if network[:3] == "udp" {
useUDP = true
} else if network[:3] != "tcp" {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
}
host, sport, err := net.SplitHostPort(address)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
port, err := strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
var host string
var port int
if matches[1] == "ping" {
host = address
} else {
var sport string
var err error
host, sport, err = net.SplitHostPort(address)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
port, err = strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
}
}
allAddr, err := tnet.LookupContextHost(ctx, host)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
var addrs []net.IP
var addrs []netip.AddrPort
for _, addr := range allAddr {
if strings.IndexByte(addr, ':') != -1 && acceptV6 {
addrs = append(addrs, net.ParseIP(addr))
} else if strings.IndexByte(addr, '.') != -1 && acceptV4 {
addrs = append(addrs, net.ParseIP(addr))
ip, err := netip.ParseAddr(addr)
if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) {
addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port)))
}
}
if len(addrs) == 0 && len(allAddr) != 0 {
@@ -804,10 +1041,13 @@ func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.
}
var c net.Conn
if useUDP {
c, err = tnet.DialUDP(nil, &net.UDPAddr{IP: addr, Port: port})
} else {
c, err = tnet.DialContextTCP(dialCtx, &net.TCPAddr{IP: addr, Port: port})
switch matches[1] {
case "tcp":
c, err = tnet.DialContextTCPAddrPort(dialCtx, addr)
case "udp":
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, addr)
case "ping":
c, err = tnet.DialPingAddr(netip.Addr{}, addr.Addr())
}
if err == nil {
return c, nil
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+5
View File
@@ -0,0 +1,5 @@
## `net/netip` for Go < 1.18
This is an unmodified extraction of the `net/netip` package from Go 1.18's
standard library, for use in Go 1.17. When Go 1.18 is released, this repository
will be deleted.
+7
View File
@@ -0,0 +1,7 @@
package bytealg
import "strings"
func IndexByteString(s string, c byte) int {
return strings.IndexByte(s, c)
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package intern lets you make smaller comparable values by boxing
// a larger comparable value (such as a 16 byte string header) down
// into a globally unique 8 byte pointer.
//
// The globally unique pointers are garbage collected with weak
// references and finalizers. This package hides that.
package intern
import (
"runtime"
"sync"
"unsafe"
)
// A Value pointer is the handle to an underlying comparable value.
// See func Get for how Value pointers may be used.
type Value struct {
_ [0]func() // prevent people from accidentally using value type as comparable
cmpVal interface{}
// resurrected is guarded by mu (for all instances of Value).
// It is set true whenever v is synthesized from a uintptr.
resurrected bool
}
// Get returns the comparable value passed to the Get func
// that returned v.
func (v *Value) Get() interface{} { return v.cmpVal }
// key is a key in our global value map.
// It contains type-specialized fields to avoid allocations
// when converting common types to empty interfaces.
type key struct {
s string
cmpVal interface{}
// isString reports whether key contains a string.
// Without it, the zero value of key is ambiguous.
isString bool
}
// keyFor returns a key to use with cmpVal.
func keyFor(cmpVal interface{}) key {
if s, ok := cmpVal.(string); ok {
return key{s: s, isString: true}
}
return key{cmpVal: cmpVal}
}
// Value returns a *Value built from k.
func (k key) Value() *Value {
if k.isString {
return &Value{cmpVal: k.s}
}
return &Value{cmpVal: k.cmpVal}
}
var (
// mu guards valMap, a weakref map of *Value by underlying value.
// It also guards the resurrected field of all *Values.
mu sync.Mutex
valMap = map[key]uintptr{} // to uintptr(*Value)
valSafe = safeMap() // non-nil in safe+leaky mode
)
// safeMap returns a non-nil map if we're in safe-but-leaky mode,
// as controlled by GODEBUG=intern=leaky
func safeMap() map[key]*Value {
return nil
}
// Get returns a pointer representing the comparable value cmpVal.
//
// The returned pointer will be the same for Get(v) and Get(v2)
// if and only if v == v2, and can be used as a map key.
func Get(cmpVal interface{}) *Value {
return get(keyFor(cmpVal))
}
// GetByString is identical to Get, except that it is specialized for strings.
// This avoids an allocation from putting a string into an interface{}
// to pass as an argument to Get.
func GetByString(s string) *Value {
return get(key{s: s, isString: true})
}
// We play unsafe games that violate Go's rules (and assume a non-moving
// collector). So we quiet Go here.
// See the comment below Get for more implementation details.
//go:nocheckptr
func get(k key) *Value {
mu.Lock()
defer mu.Unlock()
var v *Value
if valSafe != nil {
v = valSafe[k]
} else if addr, ok := valMap[k]; ok {
v = (*Value)(unsafe.Pointer(addr))
v.resurrected = true
}
if v != nil {
return v
}
v = k.Value()
if valSafe != nil {
valSafe[k] = v
} else {
// SetFinalizer before uintptr conversion (theoretical concern;
// see https://github.com/go4org/intern/issues/13)
runtime.SetFinalizer(v, finalize)
valMap[k] = uintptr(unsafe.Pointer(v))
}
return v
}
func finalize(v *Value) {
mu.Lock()
defer mu.Unlock()
if v.resurrected {
// We lost the race. Somebody resurrected it while we
// were about to finalize it. Try again next round.
v.resurrected = false
runtime.SetFinalizer(v, finalize)
return
}
delete(valMap, keyFor(v.cmpVal))
}
// Interning is simple if you don't require that unused values be
// garbage collectable. But we do require that; we don't want to be
// DOS vector. We do this by using a uintptr to hide the pointer from
// the garbage collector, and using a finalizer to eliminate the
// pointer when no other code is using it.
//
// The obvious implementation of this is to use a
// map[interface{}]uintptr-of-*interface{}, and set up a finalizer to
// delete from the map. Unfortunately, this is racy. Because pointers
// are being created in violation of Go's unsafety rules, it's
// possible to create a pointer to a value concurrently with the GC
// concluding that the value can be collected. There are other races
// that break the equality invariant as well, but the use-after-free
// will cause a runtime crash.
//
// To make this work, the finalizer needs to know that no references
// have been unsafely created since the finalizer was set up. To do
// this, values carry a "resurrected" sentinel, which gets set
// whenever a pointer is unsafely created. If the finalizer encounters
// the sentinel, it clears the sentinel and delays collection for one
// additional GC cycle, by re-installing itself as finalizer. This
// ensures that the unsafely created pointer is visible to the GC, and
// will correctly prevent collection.
//
// This technique does mean that interned values that get reused take
// at least 3 GC cycles to fully collect (1 to clear the sentinel, 1
// to clean up the unsafe map, 1 to be actually deleted).
//
// @ianlancetaylor commented in
// https://github.com/golang/go/issues/41303#issuecomment-717401656
// that it is possible to implement weak references in terms of
// finalizers without unsafe. Unfortunately, the approach he outlined
// does not work here, for two reasons. First, there is no way to
// construct a strong pointer out of a weak pointer; our map stores
// weak pointers, but we must return strong pointers to callers.
// Second, and more fundamentally, we must return not just _a_ strong
// pointer to callers, but _the same_ strong pointer to callers. In
// order to return _the same_ strong pointer to callers, we must track
// it, which is exactly what we cannot do with strong pointers.
//
// See https://github.com/inetaf/netaddr/issues/53 for more
// discussion, and https://github.com/go4org/intern/issues/2 for an
// illustration of the subtleties at play.
+7
View File
@@ -0,0 +1,7 @@
package itoa
import "strconv"
func Itoa(i int) string {
return strconv.Itoa(i)
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Stuff that exists in std, but we can't use due to being a dependency
// of net, for go/build deps_test policy reasons.
package netip
func stringsLastIndexByte(s string, b byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == b {
return i
}
}
return -1
}
func beUint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
func bePutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
func bePutUint32(b []byte, v uint32) {
_ = b[3] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}
func leUint16(b []byte) uint16 {
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint16(b[0]) | uint16(b[1])<<8
}
func lePutUint16(b []byte, v uint16) {
_ = b[1] // early bounds check to guarantee safety of writes below
b[0] = byte(v)
b[1] = byte(v >> 8)
}
+1469
View File
File diff suppressed because it is too large. Load diff
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netip
import "math/bits"
// uint128 represents a uint128 using two uint64s.
//
// When the methods below mention a bit number, bit 0 is the most
// significant bit (in hi) and bit 127 is the lowest (lo&1).
type uint128 struct {
hi uint64
lo uint64
}
// mask6 returns a uint128 bitmask with the topmost n bits of a
// 128-bit number.
func mask6(n int) uint128 {
return uint128{^(^uint64(0) >> n), ^uint64(0) << (128 - n)}
}
// isZero reports whether u == 0.
//
// It's faster than u == (uint128{}) because the compiler (as of Go
// 1.15/1.16b1) doesn't do this trick and instead inserts a branch in
// its eq alg's generated code.
func (u uint128) isZero() bool { return u.hi|u.lo == 0 }
// and returns the bitwise AND of u and m (u&m).
func (u uint128) and(m uint128) uint128 {
return uint128{u.hi & m.hi, u.lo & m.lo}
}
// xor returns the bitwise XOR of u and m (u^m).
func (u uint128) xor(m uint128) uint128 {
return uint128{u.hi ^ m.hi, u.lo ^ m.lo}
}
// or returns the bitwise OR of u and m (u|m).
func (u uint128) or(m uint128) uint128 {
return uint128{u.hi | m.hi, u.lo | m.lo}
}
// not returns the bitwise NOT of u.
func (u uint128) not() uint128 {
return uint128{^u.hi, ^u.lo}
}
// subOne returns u - 1.
func (u uint128) subOne() uint128 {
lo, borrow := bits.Sub64(u.lo, 1, 0)
return uint128{u.hi - borrow, lo}
}
// addOne returns u + 1.
func (u uint128) addOne() uint128 {
lo, carry := bits.Add64(u.lo, 1, 0)
return uint128{u.hi + carry, lo}
}
func u64CommonPrefixLen(a, b uint64) uint8 {
return uint8(bits.LeadingZeros64(a ^ b))
}
func (u uint128) commonPrefixLen(v uint128) (n uint8) {
if n = u64CommonPrefixLen(u.hi, v.hi); n == 64 {
n += u64CommonPrefixLen(u.lo, v.lo)
}
return
}
// halves returns the two uint64 halves of the uint128.
//
// Logically, think of it as returning two uint64s.
// It only returns pointers for inlining reasons on 32-bit platforms.
func (u *uint128) halves() [2]*uint64 {
return [2]*uint64{&u.hi, &u.lo}
}
// bitsSetFrom returns a copy of u with the given bit
// and all subsequent ones set.
func (u uint128) bitsSetFrom(bit uint8) uint128 {
return u.or(mask6(int(bit)).not())
}
// bitsClearedFrom returns a copy of u with the given bit
// and all subsequent ones cleared.
func (u uint128) bitsClearedFrom(bit uint8) uint128 {
return u.and(mask6(int(bit)))
}
@@ -0,0 +1,99 @@
// 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 arm || mips || mipsle || 386
// +build arm mips mipsle 386
package atomicbitops
import (
"sync/atomic"
"unsafe"
)
// AlignedAtomicInt64 is an atomic int64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems.
//
// Per https://golang.org/pkg/sync/atomic/#pkg-note-BUG:
//
// "On ARM, 386, and 32-bit MIPS, it is the caller's responsibility to arrange
// for 64-bit alignment of 64-bit words accessed atomically. The first word in
// a variable or in an allocated struct, array, or slice can be relied upon to
// be 64-bit aligned."
//
// +stateify savable
type AlignedAtomicInt64 struct {
value int64
value32 int32
}
func (aa *AlignedAtomicInt64) ptr() *int64 {
// On 32-bit systems, aa.value is guaranteed to be 32-bit aligned. It means
// that in the 12-byte aa.value, there are guaranteed to be 8 contiguous bytes
// with 64-bit alignment.
return (*int64)(unsafe.Pointer((uintptr(unsafe.Pointer(&aa.value)) + 4) &^ 7))
}
// Load is analagous to atomic.LoadInt64.
func (aa *AlignedAtomicInt64) Load() int64 {
return atomic.LoadInt64(aa.ptr())
}
// Store is analagous to atomic.StoreInt64.
func (aa *AlignedAtomicInt64) Store(v int64) {
atomic.StoreInt64(aa.ptr(), v)
}
// Add is analagous to atomic.AddInt64.
func (aa *AlignedAtomicInt64) Add(v int64) int64 {
return atomic.AddInt64(aa.ptr(), v)
}
// AlignedAtomicUint64 is an atomic uint64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems.
//
// Per https://golang.org/pkg/sync/atomic/#pkg-note-BUG:
//
// "On ARM, 386, and 32-bit MIPS, it is the caller's responsibility to arrange
// for 64-bit alignment of 64-bit words accessed atomically. The first word in
// a variable or in an allocated struct, array, or slice can be relied upon to
// be 64-bit aligned."
//
// +stateify savable
type AlignedAtomicUint64 struct {
value uint64
value32 uint32
}
func (aa *AlignedAtomicUint64) ptr() *uint64 {
// On 32-bit systems, aa.value is guaranteed to be 32-bit aligned. It means
// that in the 12-byte aa.value, there are guaranteed to be 8 contiguous bytes
// with 64-bit alignment.
return (*uint64)(unsafe.Pointer((uintptr(unsafe.Pointer(&aa.value)) + 4) &^ 7))
}
// Load is analagous to atomic.LoadUint64.
func (aa *AlignedAtomicUint64) Load() uint64 {
return atomic.LoadUint64(aa.ptr())
}
// Store is analagous to atomic.StoreUint64.
func (aa *AlignedAtomicUint64) Store(v uint64) {
atomic.StoreUint64(aa.ptr(), v)
}
// Add is analagous to atomic.AddUint64.
func (aa *AlignedAtomicUint64) Add(v uint64) uint64 {
return atomic.AddUint64(aa.ptr(), v)
}
+72
View File
@@ -0,0 +1,72 @@
// 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 !arm && !mips && !mipsle && !386
// +build !arm,!mips,!mipsle,!386
package atomicbitops
import "sync/atomic"
// AlignedAtomicInt64 is an atomic int64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On most architectures, it's just a regular
// int64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicInt64 struct {
value int64
}
// Load is analagous to atomic.LoadInt64.
func (aa *AlignedAtomicInt64) Load() int64 {
return atomic.LoadInt64(&aa.value)
}
// Store is analagous to atomic.StoreInt64.
func (aa *AlignedAtomicInt64) Store(v int64) {
atomic.StoreInt64(&aa.value, v)
}
// Add is analagous to atomic.AddInt64.
func (aa *AlignedAtomicInt64) Add(v int64) int64 {
return atomic.AddInt64(&aa.value, v)
}
// AlignedAtomicUint64 is an atomic uint64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On most architectures, it's just a regular
// uint64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicUint64 struct {
value uint64
}
// Load is analagous to atomic.LoadUint64.
func (aa *AlignedAtomicUint64) Load() uint64 {
return atomic.LoadUint64(&aa.value)
}
// Store is analagous to atomic.StoreUint64.
func (aa *AlignedAtomicUint64) Store(v uint64) {
atomic.StoreUint64(&aa.value, v)
}
// Add is analagous to atomic.AddUint64.
func (aa *AlignedAtomicUint64) Add(v uint64) uint64 {
return atomic.AddUint64(&aa.value, v)
}
+48
View File
@@ -0,0 +1,48 @@
// 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 amd64 || arm64
// +build amd64 arm64
// Package atomicbitops provides extensions to the sync/atomic package.
//
// All read-modify-write operations implemented by this package have
// acquire-release memory ordering (like sync/atomic).
package atomicbitops
// AndUint32 atomically applies bitwise AND operation to *addr with val.
func AndUint32(addr *uint32, val uint32)
// OrUint32 atomically applies bitwise OR operation to *addr with val.
func OrUint32(addr *uint32, val uint32)
// XorUint32 atomically applies bitwise XOR operation to *addr with val.
func XorUint32(addr *uint32, val uint32)
// CompareAndSwapUint32 is like sync/atomic.CompareAndSwapUint32, but returns
// the value previously stored at addr.
func CompareAndSwapUint32(addr *uint32, old, new uint32) uint32
// AndUint64 atomically applies bitwise AND operation to *addr with val.
func AndUint64(addr *uint64, val uint64)
// OrUint64 atomically applies bitwise OR operation to *addr with val.
func OrUint64(addr *uint64, val uint64)
// XorUint64 atomically applies bitwise XOR operation to *addr with val.
func XorUint64(addr *uint64, val uint64)
// CompareAndSwapUint64 is like sync/atomic.CompareAndSwapUint64, but returns
// the value previously stored at addr.
func CompareAndSwapUint64(addr *uint64, old, new uint64) uint64
@@ -0,0 +1,71 @@
// automatically generated by stateify.
//go:build arm || mips || mipsle || 386
// +build arm mips mipsle 386
package atomicbitops
import (
"inet.af/netstack/state"
)
func (aa *AlignedAtomicInt64) StateTypeName() string {
return "pkg/atomicbitops.AlignedAtomicInt64"
}
func (aa *AlignedAtomicInt64) StateFields() []string {
return []string{
"value",
"value32",
}
}
func (aa *AlignedAtomicInt64) beforeSave() {}
// +checklocksignore
func (aa *AlignedAtomicInt64) StateSave(stateSinkObject state.Sink) {
aa.beforeSave()
stateSinkObject.Save(0, &aa.value)
stateSinkObject.Save(1, &aa.value32)
}
func (aa *AlignedAtomicInt64) afterLoad() {}
// +checklocksignore
func (aa *AlignedAtomicInt64) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &aa.value)
stateSourceObject.Load(1, &aa.value32)
}
func (aa *AlignedAtomicUint64) StateTypeName() string {
return "pkg/atomicbitops.AlignedAtomicUint64"
}
func (aa *AlignedAtomicUint64) StateFields() []string {
return []string{
"value",
"value32",
}
}
func (aa *AlignedAtomicUint64) beforeSave() {}
// +checklocksignore
func (aa *AlignedAtomicUint64) StateSave(stateSinkObject state.Sink) {
aa.beforeSave()
stateSinkObject.Save(0, &aa.value)
stateSinkObject.Save(1, &aa.value32)
}
func (aa *AlignedAtomicUint64) afterLoad() {}
// +checklocksignore
func (aa *AlignedAtomicUint64) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &aa.value)
stateSourceObject.Load(1, &aa.value32)
}
func init() {
state.Register((*AlignedAtomicInt64)(nil))
state.Register((*AlignedAtomicUint64)(nil))
}
@@ -0,0 +1,65 @@
// automatically generated by stateify.
//go:build !arm && !mips && !mipsle && !386
// +build !arm,!mips,!mipsle,!386
package atomicbitops
import (
"inet.af/netstack/state"
)
func (aa *AlignedAtomicInt64) StateTypeName() string {
return "pkg/atomicbitops.AlignedAtomicInt64"
}
func (aa *AlignedAtomicInt64) StateFields() []string {
return []string{
"value",
}
}
func (aa *AlignedAtomicInt64) beforeSave() {}
// +checklocksignore
func (aa *AlignedAtomicInt64) StateSave(stateSinkObject state.Sink) {
aa.beforeSave()
stateSinkObject.Save(0, &aa.value)
}
func (aa *AlignedAtomicInt64) afterLoad() {}
// +checklocksignore
func (aa *AlignedAtomicInt64) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &aa.value)
}
func (aa *AlignedAtomicUint64) StateTypeName() string {
return "pkg/atomicbitops.AlignedAtomicUint64"
}
func (aa *AlignedAtomicUint64) StateFields() []string {
return []string{
"value",
}
}
func (aa *AlignedAtomicUint64) beforeSave() {}
// +checklocksignore
func (aa *AlignedAtomicUint64) StateSave(stateSinkObject state.Sink) {
aa.beforeSave()
stateSinkObject.Save(0, &aa.value)
}
func (aa *AlignedAtomicUint64) afterLoad() {}
// +checklocksignore
func (aa *AlignedAtomicUint64) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &aa.value)
}
func init() {
state.Register((*AlignedAtomicInt64)(nil))
state.Register((*AlignedAtomicUint64)(nil))
}
@@ -0,0 +1,77 @@
// 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.
// +build amd64
#include "textflag.h"
TEXT ·AndUint32(SB),NOSPLIT,$0-12
MOVQ addr+0(FP), BX
MOVL val+8(FP), AX
LOCK
ANDL AX, 0(BX)
RET
TEXT ·OrUint32(SB),NOSPLIT,$0-12
MOVQ addr+0(FP), BX
MOVL val+8(FP), AX
LOCK
ORL AX, 0(BX)
RET
TEXT ·XorUint32(SB),NOSPLIT,$0-12
MOVQ addr+0(FP), BX
MOVL val+8(FP), AX
LOCK
XORL AX, 0(BX)
RET
TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-20
MOVQ addr+0(FP), DI
MOVL old+8(FP), AX
MOVL new+12(FP), DX
LOCK
CMPXCHGL DX, 0(DI)
MOVL AX, ret+16(FP)
RET
TEXT ·AndUint64(SB),NOSPLIT,$0-16
MOVQ addr+0(FP), BX
MOVQ val+8(FP), AX
LOCK
ANDQ AX, 0(BX)
RET
TEXT ·OrUint64(SB),NOSPLIT,$0-16
MOVQ addr+0(FP), BX
MOVQ val+8(FP), AX
LOCK
ORQ AX, 0(BX)
RET
TEXT ·XorUint64(SB),NOSPLIT,$0-16
MOVQ addr+0(FP), BX
MOVQ val+8(FP), AX
LOCK
XORQ AX, 0(BX)
RET
TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$0-32
MOVQ addr+0(FP), DI
MOVQ old+8(FP), AX
MOVQ new+16(FP), DX
LOCK
CMPXCHGQ DX, 0(DI)
MOVQ AX, ret+24(FP)
RET
@@ -0,0 +1,105 @@
// 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.
// +build arm64
#include "textflag.h"
TEXT ·AndUint32(SB),NOSPLIT,$0-12
MOVD ptr+0(FP), R0
MOVW val+8(FP), R1
again:
LDAXRW (R0), R2
ANDW R1, R2
STLXRW R2, (R0), R3
CBNZ R3, again
RET
TEXT ·OrUint32(SB),NOSPLIT,$0-12
MOVD ptr+0(FP), R0
MOVW val+8(FP), R1
again:
LDAXRW (R0), R2
ORRW R1, R2
STLXRW R2, (R0), R3
CBNZ R3, again
RET
TEXT ·XorUint32(SB),NOSPLIT,$0-12
MOVD ptr+0(FP), R0
MOVW val+8(FP), R1
again:
LDAXRW (R0), R2
EORW R1, R2
STLXRW R2, (R0), R3
CBNZ R3, again
RET
TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-20
MOVD addr+0(FP), R0
MOVW old+8(FP), R1
MOVW new+12(FP), R2
again:
LDAXRW (R0), R3
CMPW R1, R3
BNE done
STLXRW R2, (R0), R4
CBNZ R4, again
done:
MOVW R3, prev+16(FP)
RET
TEXT ·AndUint64(SB),NOSPLIT,$0-16
MOVD ptr+0(FP), R0
MOVD val+8(FP), R1
again:
LDAXR (R0), R2
AND R1, R2
STLXR R2, (R0), R3
CBNZ R3, again
RET
TEXT ·OrUint64(SB),NOSPLIT,$0-16
MOVD ptr+0(FP), R0
MOVD val+8(FP), R1
again:
LDAXR (R0), R2
ORR R1, R2
STLXR R2, (R0), R3
CBNZ R3, again
RET
TEXT ·XorUint64(SB),NOSPLIT,$0-16
MOVD ptr+0(FP), R0
MOVD val+8(FP), R1
again:
LDAXR (R0), R2
EOR R1, R2
STLXR R2, (R0), R3
CBNZ R3, again
RET
TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$0-32
MOVD addr+0(FP), R0
MOVD old+8(FP), R1
MOVD new+16(FP), R2
again:
LDAXR (R0), R3
CMP R1, R3
BNE done
STLXR R2, (R0), R4
CBNZ R4, again
done:
MOVD R3, prev+24(FP)
RET
@@ -0,0 +1,114 @@
// 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 !amd64 && !arm64
// +build !amd64,!arm64
package atomicbitops
import (
"sync/atomic"
)
//go:nosplit
func AndUint32(addr *uint32, val uint32) {
for {
o := atomic.LoadUint32(addr)
n := o & val
if atomic.CompareAndSwapUint32(addr, o, n) {
break
}
}
}
//go:nosplit
func OrUint32(addr *uint32, val uint32) {
for {
o := atomic.LoadUint32(addr)
n := o | val
if atomic.CompareAndSwapUint32(addr, o, n) {
break
}
}
}
//go:nosplit
func XorUint32(addr *uint32, val uint32) {
for {
o := atomic.LoadUint32(addr)
n := o ^ val
if atomic.CompareAndSwapUint32(addr, o, n) {
break
}
}
}
//go:nosplit
func CompareAndSwapUint32(addr *uint32, old, new uint32) (prev uint32) {
for {
prev = atomic.LoadUint32(addr)
if prev != old {
return
}
if atomic.CompareAndSwapUint32(addr, old, new) {
return
}
}
}
//go:nosplit
func AndUint64(addr *uint64, val uint64) {
for {
o := atomic.LoadUint64(addr)
n := o & val
if atomic.CompareAndSwapUint64(addr, o, n) {
break
}
}
}
//go:nosplit
func OrUint64(addr *uint64, val uint64) {
for {
o := atomic.LoadUint64(addr)
n := o | val
if atomic.CompareAndSwapUint64(addr, o, n) {
break
}
}
}
//go:nosplit
func XorUint64(addr *uint64, val uint64) {
for {
o := atomic.LoadUint64(addr)
n := o ^ val
if atomic.CompareAndSwapUint64(addr, o, n) {
break
}
}
}
//go:nosplit
func CompareAndSwapUint64(addr *uint64, old, new uint64) (prev uint64) {
for {
prev = atomic.LoadUint64(addr)
if prev != old {
return
}
if atomic.CompareAndSwapUint64(addr, old, new) {
return
}
}
}
@@ -0,0 +1,8 @@
// automatically generated by stateify.
//go:build (amd64 || arm64) && !amd64 && !arm64
// +build amd64 arm64
// +build !amd64
// +build !arm64
package atomicbitops
+105
View File
@@ -0,0 +1,105 @@
// 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 buffer provides the implementation of a buffer view.
//
// A view is an flexible buffer, supporting the safecopy operations natively as
// well as the ability to grow via either prepend or append, as well as shrink.
package buffer
// buffer encapsulates a queueable byte buffer.
//
// +stateify savable
type buffer struct {
data []byte
read int
write int
bufferEntry
}
// init performs in-place initialization for zero value.
func (b *buffer) init(size int) {
b.data = make([]byte, size)
}
// initWithData initializes b with data, taking ownership.
func (b *buffer) initWithData(data []byte) {
b.data = data
b.read = 0
b.write = len(data)
}
// Reset resets read and write locations, effectively emptying the buffer.
func (b *buffer) Reset() {
b.read = 0
b.write = 0
}
// Remove removes r from the unread portion. It returns false if r does not
// fully reside in b.
func (b *buffer) Remove(r Range) bool {
sz := b.ReadSize()
switch {
case r.Len() != r.Intersect(Range{end: sz}).Len():
return false
case r.Len() == 0:
// Noop
case r.begin == 0:
b.read += r.end
case r.end == sz:
b.write -= r.Len()
default:
// Remove from the middle of b.data.
copy(b.data[b.read+r.begin:], b.data[b.read+r.end:b.write])
b.write -= r.Len()
}
return true
}
// Full indicates the buffer is full.
//
// This indicates there is no capacity left to write.
func (b *buffer) Full() bool {
return b.write == len(b.data)
}
// ReadSize returns the number of bytes available for reading.
func (b *buffer) ReadSize() int {
return b.write - b.read
}
// ReadMove advances the read index by the given amount.
func (b *buffer) ReadMove(n int) {
b.read += n
}
// ReadSlice returns the read slice for this buffer.
func (b *buffer) ReadSlice() []byte {
return b.data[b.read:b.write]
}
// WriteSize returns the number of bytes available for writing.
func (b *buffer) WriteSize() int {
return len(b.data) - b.write
}
// WriteMove advances the write index by the given amount.
func (b *buffer) WriteMove(n int) {
b.write += n
}
// WriteSlice returns the write slice for this buffer.
func (b *buffer) WriteSlice() []byte {
return b.data[b.write:]
}
+221
View File
@@ -0,0 +1,221 @@
package buffer
// ElementMapper provides an identity mapping by default.
//
// This can be replaced to provide a struct that maps elements to linker
// objects, if they are not the same. An ElementMapper is not typically
// required if: Linker is left as is, Element is left as is, or Linker and
// Element are the same type.
type bufferElementMapper struct{}
// linkerFor maps an Element to a Linker.
//
// This default implementation should be inlined.
//
//go:nosplit
func (bufferElementMapper) linkerFor(elem *buffer) *buffer { return elem }
// List is an intrusive list. Entries can be added to or removed from the list
// in O(1) time and with no additional memory allocations.
//
// The zero value for List is an empty list ready to use.
//
// To iterate over a list (where l is a List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.
// }
//
// +stateify savable
type bufferList struct {
head *buffer
tail *buffer
}
// Reset resets list l to the empty state.
func (l *bufferList) Reset() {
l.head = nil
l.tail = nil
}
// Empty returns true iff the list is empty.
//
//go:nosplit
func (l *bufferList) Empty() bool {
return l.head == nil
}
// Front returns the first element of list l or nil.
//
//go:nosplit
func (l *bufferList) Front() *buffer {
return l.head
}
// Back returns the last element of list l or nil.
//
//go:nosplit
func (l *bufferList) Back() *buffer {
return l.tail
}
// Len returns the number of elements in the list.
//
// NOTE: This is an O(n) operation.
//
//go:nosplit
func (l *bufferList) Len() (count int) {
for e := l.Front(); e != nil; e = (bufferElementMapper{}.linkerFor(e)).Next() {
count++
}
return count
}
// PushFront inserts the element e at the front of list l.
//
//go:nosplit
func (l *bufferList) PushFront(e *buffer) {
linker := bufferElementMapper{}.linkerFor(e)
linker.SetNext(l.head)
linker.SetPrev(nil)
if l.head != nil {
bufferElementMapper{}.linkerFor(l.head).SetPrev(e)
} else {
l.tail = e
}
l.head = e
}
// PushBack inserts the element e at the back of list l.
//
//go:nosplit
func (l *bufferList) PushBack(e *buffer) {
linker := bufferElementMapper{}.linkerFor(e)
linker.SetNext(nil)
linker.SetPrev(l.tail)
if l.tail != nil {
bufferElementMapper{}.linkerFor(l.tail).SetNext(e)
} else {
l.head = e
}
l.tail = e
}
// PushBackList inserts list m at the end of list l, emptying m.
//
//go:nosplit
func (l *bufferList) PushBackList(m *bufferList) {
if l.head == nil {
l.head = m.head
l.tail = m.tail
} else if m.head != nil {
bufferElementMapper{}.linkerFor(l.tail).SetNext(m.head)
bufferElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
l.tail = m.tail
}
m.head = nil
m.tail = nil
}
// InsertAfter inserts e after b.
//
//go:nosplit
func (l *bufferList) InsertAfter(b, e *buffer) {
bLinker := bufferElementMapper{}.linkerFor(b)
eLinker := bufferElementMapper{}.linkerFor(e)
a := bLinker.Next()
eLinker.SetNext(a)
eLinker.SetPrev(b)
bLinker.SetNext(e)
if a != nil {
bufferElementMapper{}.linkerFor(a).SetPrev(e)
} else {
l.tail = e
}
}
// InsertBefore inserts e before a.
//
//go:nosplit
func (l *bufferList) InsertBefore(a, e *buffer) {
aLinker := bufferElementMapper{}.linkerFor(a)
eLinker := bufferElementMapper{}.linkerFor(e)
b := aLinker.Prev()
eLinker.SetNext(a)
eLinker.SetPrev(b)
aLinker.SetPrev(e)
if b != nil {
bufferElementMapper{}.linkerFor(b).SetNext(e)
} else {
l.head = e
}
}
// Remove removes e from l.
//
//go:nosplit
func (l *bufferList) Remove(e *buffer) {
linker := bufferElementMapper{}.linkerFor(e)
prev := linker.Prev()
next := linker.Next()
if prev != nil {
bufferElementMapper{}.linkerFor(prev).SetNext(next)
} else if l.head == e {
l.head = next
}
if next != nil {
bufferElementMapper{}.linkerFor(next).SetPrev(prev)
} else if l.tail == e {
l.tail = prev
}
linker.SetNext(nil)
linker.SetPrev(nil)
}
// Entry is a default implementation of Linker. Users can add anonymous fields
// of this type to their structs to make them automatically implement the
// methods needed by List.
//
// +stateify savable
type bufferEntry struct {
next *buffer
prev *buffer
}
// Next returns the entry that follows e in the list.
//
//go:nosplit
func (e *bufferEntry) Next() *buffer {
return e.next
}
// Prev returns the entry that precedes e in the list.
//
//go:nosplit
func (e *bufferEntry) Prev() *buffer {
return e.prev
}
// SetNext assigns 'entry' as the entry that follows e in the list.
//
//go:nosplit
func (e *bufferEntry) SetNext(elem *buffer) {
e.next = elem
}
// SetPrev assigns 'entry' as the entry that precedes e in the list.
//
//go:nosplit
func (e *bufferEntry) SetPrev(elem *buffer) {
e.prev = elem
}
+163
View File
@@ -0,0 +1,163 @@
// automatically generated by stateify.
package buffer
import (
"inet.af/netstack/state"
)
func (b *buffer) StateTypeName() string {
return "pkg/buffer.buffer"
}
func (b *buffer) StateFields() []string {
return []string{
"data",
"read",
"write",
"bufferEntry",
}
}
func (b *buffer) beforeSave() {}
// +checklocksignore
func (b *buffer) StateSave(stateSinkObject state.Sink) {
b.beforeSave()
stateSinkObject.Save(0, &b.data)
stateSinkObject.Save(1, &b.read)
stateSinkObject.Save(2, &b.write)
stateSinkObject.Save(3, &b.bufferEntry)
}
func (b *buffer) afterLoad() {}
// +checklocksignore
func (b *buffer) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &b.data)
stateSourceObject.Load(1, &b.read)
stateSourceObject.Load(2, &b.write)
stateSourceObject.Load(3, &b.bufferEntry)
}
func (l *bufferList) StateTypeName() string {
return "pkg/buffer.bufferList"
}
func (l *bufferList) StateFields() []string {
return []string{
"head",
"tail",
}
}
func (l *bufferList) beforeSave() {}
// +checklocksignore
func (l *bufferList) StateSave(stateSinkObject state.Sink) {
l.beforeSave()
stateSinkObject.Save(0, &l.head)
stateSinkObject.Save(1, &l.tail)
}
func (l *bufferList) afterLoad() {}
// +checklocksignore
func (l *bufferList) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &l.head)
stateSourceObject.Load(1, &l.tail)
}
func (e *bufferEntry) StateTypeName() string {
return "pkg/buffer.bufferEntry"
}
func (e *bufferEntry) StateFields() []string {
return []string{
"next",
"prev",
}
}
func (e *bufferEntry) beforeSave() {}
// +checklocksignore
func (e *bufferEntry) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
stateSinkObject.Save(0, &e.next)
stateSinkObject.Save(1, &e.prev)
}
func (e *bufferEntry) afterLoad() {}
// +checklocksignore
func (e *bufferEntry) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &e.next)
stateSourceObject.Load(1, &e.prev)
}
func (p *pool) StateTypeName() string {
return "pkg/buffer.pool"
}
func (p *pool) StateFields() []string {
return []string{
"bufferSize",
"embeddedStorage",
}
}
func (p *pool) beforeSave() {}
// +checklocksignore
func (p *pool) StateSave(stateSinkObject state.Sink) {
p.beforeSave()
stateSinkObject.Save(0, &p.bufferSize)
stateSinkObject.Save(1, &p.embeddedStorage)
}
// +checklocksignore
func (p *pool) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &p.bufferSize)
stateSourceObject.LoadWait(1, &p.embeddedStorage)
stateSourceObject.AfterLoad(p.afterLoad)
}
func (v *View) StateTypeName() string {
return "pkg/buffer.View"
}
func (v *View) StateFields() []string {
return []string{
"data",
"size",
"pool",
}
}
func (v *View) beforeSave() {}
// +checklocksignore
func (v *View) StateSave(stateSinkObject state.Sink) {
v.beforeSave()
stateSinkObject.Save(0, &v.data)
stateSinkObject.Save(1, &v.size)
stateSinkObject.Save(2, &v.pool)
}
func (v *View) afterLoad() {}
// +checklocksignore
func (v *View) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &v.data)
stateSourceObject.Load(1, &v.size)
stateSourceObject.Load(2, &v.pool)
}
func init() {
state.Register((*buffer)(nil))
state.Register((*bufferList)(nil))
state.Register((*bufferEntry)(nil))
state.Register((*pool)(nil))
state.Register((*View)(nil))
}
@@ -0,0 +1,3 @@
// automatically generated by stateify.
package buffer
+90
View File
@@ -0,0 +1,90 @@
// 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 buffer
const (
// embeddedCount is the number of buffer structures embedded in the pool. It
// is also the number for overflow allocations.
embeddedCount = 8
// defaultBufferSize is the default size for each underlying storage buffer.
//
// It is slightly less than two pages. This is done intentionally to ensure
// that the buffer object aligns with runtime internals. This two page size
// will effectively minimize internal fragmentation, but still have a large
// enough chunk to limit excessive segmentation.
defaultBufferSize = 8144
)
// pool allocates buffer.
//
// It contains an embedded buffer storage for fast path when the number of
// buffers needed is small.
//
// +stateify savable
type pool struct {
bufferSize int
avail []buffer `state:"nosave"`
embeddedStorage [embeddedCount]buffer `state:"wait"`
}
// get gets a new buffer from p.
func (p *pool) get() *buffer {
buf := p.getNoInit()
buf.init(p.bufferSize)
return buf
}
// get gets a new buffer from p without initializing it.
func (p *pool) getNoInit() *buffer {
if p.avail == nil {
p.avail = p.embeddedStorage[:]
}
if len(p.avail) == 0 {
p.avail = make([]buffer, embeddedCount)
}
if p.bufferSize <= 0 {
p.bufferSize = defaultBufferSize
}
buf := &p.avail[0]
p.avail = p.avail[1:]
return buf
}
// put releases buf.
func (p *pool) put(buf *buffer) {
// Remove reference to the underlying storage, allowing it to be garbage
// collected.
buf.data = nil
buf.Reset()
}
// setBufferSize sets the size of underlying storage buffer for future
// allocations. It can be called at any time.
func (p *pool) setBufferSize(size int) {
p.bufferSize = size
}
// afterLoad is invoked by stateify.
func (p *pool) afterLoad() {
// S/R does not save subslice into embeddedStorage correctly. Restore
// available portion of embeddedStorage manually. Restore as nil if none used.
for i := len(p.embeddedStorage); i > 0; i-- {
if p.embeddedStorage[i-1].data != nil {
p.avail = p.embeddedStorage[i:]
break
}
}
}
+580
View File
@@ -0,0 +1,580 @@
// 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 buffer
import (
"fmt"
"io"
)
// Buffer is an alias to View.
type Buffer = View
// View is a non-linear buffer.
//
// All methods are thread compatible.
//
// +stateify savable
type View struct {
data bufferList
size int64
pool pool
}
// TrimFront removes the first count bytes from the buffer.
func (v *View) TrimFront(count int64) {
if count >= v.size {
v.advanceRead(v.size)
} else {
v.advanceRead(count)
}
}
// Remove deletes data at specified location in v. It returns false if specified
// range does not fully reside in v.
func (v *View) Remove(offset, length int) bool {
if offset < 0 || length < 0 {
return false
}
tgt := Range{begin: offset, end: offset + length}
if tgt.Len() != tgt.Intersect(Range{end: int(v.size)}).Len() {
return false
}
// Scan through each buffer and remove intersections.
var curr Range
for buf := v.data.Front(); buf != nil; {
origLen := buf.ReadSize()
curr.end = curr.begin + origLen
if x := curr.Intersect(tgt); x.Len() > 0 {
if !buf.Remove(x.Offset(-curr.begin)) {
panic("buf.Remove() failed")
}
if buf.ReadSize() == 0 {
// buf fully removed, removing it from the list.
oldBuf := buf
buf = buf.Next()
v.data.Remove(oldBuf)
v.pool.put(oldBuf)
} else {
// Only partial data intersects, moving on to next one.
buf = buf.Next()
}
v.size -= int64(x.Len())
} else {
// This buffer is not in range, moving on to next one.
buf = buf.Next()
}
curr.begin += origLen
if curr.begin >= tgt.end {
break
}
}
return true
}
// ReadAt implements io.ReaderAt.ReadAt.
func (v *View) ReadAt(p []byte, offset int64) (int, error) {
var (
skipped int64
done int64
)
for buf := v.data.Front(); buf != nil && done < int64(len(p)); buf = buf.Next() {
needToSkip := int(offset - skipped)
if sz := buf.ReadSize(); sz <= needToSkip {
skipped += int64(sz)
continue
}
// Actually read data.
n := copy(p[done:], buf.ReadSlice()[needToSkip:])
skipped += int64(needToSkip)
done += int64(n)
}
if int(done) < len(p) || offset+done == v.size {
return int(done), io.EOF
}
return int(done), nil
}
// advanceRead advances the view's read index.
//
// Precondition: there must be sufficient bytes in the buffer.
func (v *View) advanceRead(count int64) {
for buf := v.data.Front(); buf != nil && count > 0; {
sz := int64(buf.ReadSize())
if sz > count {
// There is still data for reading.
buf.ReadMove(int(count))
v.size -= count
count = 0
break
}
// Consume the whole buffer.
oldBuf := buf
buf = buf.Next() // Iterate.
v.data.Remove(oldBuf)
v.pool.put(oldBuf)
// Update counts.
count -= sz
v.size -= sz
}
if count > 0 {
panic(fmt.Sprintf("advanceRead still has %d bytes remaining", count))
}
}
// Truncate truncates the view to the given bytes.
//
// This will not grow the view, only shrink it. If a length is passed that is
// greater than the current size of the view, then nothing will happen.
//
// Precondition: length must be >= 0.
func (v *View) Truncate(length int64) {
if length < 0 {
panic("negative length provided")
}
if length >= v.size {
return // Nothing to do.
}
for buf := v.data.Back(); buf != nil && v.size > length; buf = v.data.Back() {
sz := int64(buf.ReadSize())
if after := v.size - sz; after < length {
// Truncate the buffer locally.
left := (length - after)
buf.write = buf.read + int(left)
v.size = length
break
}
// Drop the buffer completely; see above.
v.data.Remove(buf)
v.pool.put(buf)
v.size -= sz
}
}
// Grow grows the given view to the number of bytes, which will be appended. If
// zero is true, all these bytes will be zero. If zero is false, then this is
// the caller's responsibility.
//
// Precondition: length must be >= 0.
func (v *View) Grow(length int64, zero bool) {
if length < 0 {
panic("negative length provided")
}
for v.size < length {
buf := v.data.Back()
// Is there some space in the last buffer?
if buf == nil || buf.Full() {
buf = v.pool.get()
v.data.PushBack(buf)
}
// Write up to length bytes.
sz := buf.WriteSize()
if int64(sz) > length-v.size {
sz = int(length - v.size)
}
// Zero the written section; note that this pattern is
// specifically recognized and optimized by the compiler.
if zero {
for i := buf.write; i < buf.write+sz; i++ {
buf.data[i] = 0
}
}
// Advance the index.
buf.WriteMove(sz)
v.size += int64(sz)
}
}
// Prepend prepends the given data.
func (v *View) Prepend(data []byte) {
// Is there any space in the first buffer?
if buf := v.data.Front(); buf != nil && buf.read > 0 {
// Fill up before the first write.
avail := buf.read
bStart := 0
dStart := len(data) - avail
if avail > len(data) {
bStart = avail - len(data)
dStart = 0
}
n := copy(buf.data[bStart:], data[dStart:])
data = data[:dStart]
v.size += int64(n)
buf.read -= n
}
for len(data) > 0 {
// Do we need an empty buffer?
buf := v.pool.get()
v.data.PushFront(buf)
// The buffer is empty; copy last chunk.
avail := len(buf.data)
bStart := 0
dStart := len(data) - avail
if avail > len(data) {
bStart = avail - len(data)
dStart = 0
}
// We have to put the data at the end of the current
// buffer in order to ensure that the next prepend will
// correctly fill up the beginning of this buffer.
n := copy(buf.data[bStart:], data[dStart:])
data = data[:dStart]
v.size += int64(n)
buf.read = len(buf.data) - n
buf.write = len(buf.data)
}
}
// Append appends the given data.
func (v *View) Append(data []byte) {
for done := 0; done < len(data); {
buf := v.data.Back()
// Ensure there's a buffer with space.
if buf == nil || buf.Full() {
buf = v.pool.get()
v.data.PushBack(buf)
}
// Copy in to the given buffer.
n := copy(buf.WriteSlice(), data[done:])
done += n
buf.WriteMove(n)
v.size += int64(n)
}
}
// AppendOwned takes ownership of data and appends it to v.
func (v *View) AppendOwned(data []byte) {
if len(data) > 0 {
buf := v.pool.getNoInit()
buf.initWithData(data)
v.data.PushBack(buf)
v.size += int64(len(data))
}
}
// PullUp makes the specified range contiguous and returns the backing memory.
func (v *View) PullUp(offset, length int) ([]byte, bool) {
if length == 0 {
return nil, true
}
tgt := Range{begin: offset, end: offset + length}
if tgt.Intersect(Range{end: int(v.size)}).Len() != length {
return nil, false
}
curr := Range{}
buf := v.data.Front()
for ; buf != nil; buf = buf.Next() {
origLen := buf.ReadSize()
curr.end = curr.begin + origLen
if x := curr.Intersect(tgt); x.Len() == tgt.Len() {
// buf covers the whole requested target range.
sub := x.Offset(-curr.begin)
return buf.ReadSlice()[sub.begin:sub.end], true
} else if x.Len() > 0 {
// buf is pointing at the starting buffer we want to merge.
break
}
curr.begin += origLen
}
// Calculate the total merged length.
totLen := 0
for n := buf; n != nil; n = n.Next() {
totLen += n.ReadSize()
if curr.begin+totLen >= tgt.end {
break
}
}
// Merge the buffers.
data := make([]byte, totLen)
off := 0
for n := buf; n != nil && off < totLen; {
copy(data[off:], n.ReadSlice())
off += n.ReadSize()
// Remove buffers except for the first one, which will be reused.
if n == buf {
n = n.Next()
} else {
old := n
n = n.Next()
v.data.Remove(old)
v.pool.put(old)
}
}
// Update the first buffer with merged data.
buf.initWithData(data)
r := tgt.Offset(-curr.begin)
return buf.data[r.begin:r.end], true
}
// Flatten returns a flattened copy of this data.
//
// This method should not be used in any performance-sensitive paths. It may
// allocate a fresh byte slice sufficiently large to contain all the data in
// the buffer. This is principally for debugging.
//
// N.B. Tee data still belongs to this view, as if there is a single buffer
// present, then it will be returned directly. This should be used for
// temporary use only, and a reference to the given slice should not be held.
func (v *View) Flatten() []byte {
if buf := v.data.Front(); buf == nil {
return nil // No data at all.
} else if buf.Next() == nil {
return buf.ReadSlice() // Only one buffer.
}
data := make([]byte, 0, v.size) // Need to flatten.
for buf := v.data.Front(); buf != nil; buf = buf.Next() {
// Copy to the allocated slice.
data = append(data, buf.ReadSlice()...)
}
return data
}
// Size indicates the total amount of data available in this view.
func (v *View) Size() int64 {
return v.size
}
// Copy makes a strict copy of this view.
func (v *View) Copy() (other View) {
for buf := v.data.Front(); buf != nil; buf = buf.Next() {
other.Append(buf.ReadSlice())
}
return
}
// Clone makes a more shallow copy compared to Copy. The underlying payload
// slice (buffer.data) is shared but the buffers themselves are copied.
func (v *View) Clone() *View {
other := &View{
size: v.size,
}
for buf := v.data.Front(); buf != nil; buf = buf.Next() {
newBuf := other.pool.getNoInit()
*newBuf = *buf
other.data.PushBack(newBuf)
}
return other
}
// Apply applies the given function across all valid data.
func (v *View) Apply(fn func([]byte)) {
for buf := v.data.Front(); buf != nil; buf = buf.Next() {
fn(buf.ReadSlice())
}
}
// SubApply applies fn to a given range of data in v. Any part of the range
// outside of v is ignored.
func (v *View) SubApply(offset, length int, fn func([]byte)) {
for buf := v.data.Front(); length > 0 && buf != nil; buf = buf.Next() {
d := buf.ReadSlice()
if offset >= len(d) {
offset -= len(d)
continue
}
if offset > 0 {
d = d[offset:]
offset = 0
}
if length < len(d) {
d = d[:length]
}
fn(d)
length -= len(d)
}
}
// Merge merges the provided View with this one.
//
// The other view will be appended to v, and other will be empty after this
// operation completes.
func (v *View) Merge(other *View) {
// Copy over all buffers.
for buf := other.data.Front(); buf != nil; buf = other.data.Front() {
other.data.Remove(buf)
v.data.PushBack(buf)
}
// Adjust sizes.
v.size += other.size
other.size = 0
}
// WriteFromReader writes to the buffer from an io.Reader.
//
// A minimum read size equal to unsafe.Sizeof(unintptr) is enforced,
// provided that count is greater than or equal to unsafe.Sizeof(uintptr).
func (v *View) WriteFromReader(r io.Reader, count int64) (int64, error) {
var (
done int64
n int
err error
)
for done < count {
buf := v.data.Back()
// Ensure we have an empty buffer.
if buf == nil || buf.Full() {
buf = v.pool.get()
v.data.PushBack(buf)
}
// Is this less than the minimum batch?
if buf.WriteSize() < minBatch && (count-done) >= int64(minBatch) {
tmp := make([]byte, minBatch)
n, err = r.Read(tmp)
v.Append(tmp[:n])
done += int64(n)
if err != nil {
break
}
continue
}
// Limit the read, if necessary.
sz := buf.WriteSize()
if left := count - done; int64(sz) > left {
sz = int(left)
}
// Pass the relevant portion of the buffer.
n, err = r.Read(buf.WriteSlice()[:sz])
buf.WriteMove(n)
done += int64(n)
v.size += int64(n)
if err == io.EOF {
err = nil // Short write allowed.
break
} else if err != nil {
break
}
}
return done, err
}
// ReadToWriter reads from the buffer into an io.Writer.
//
// N.B. This does not consume the bytes read. TrimFront should
// be called appropriately after this call in order to do so.
//
// A minimum write size equal to unsafe.Sizeof(unintptr) is enforced,
// provided that count is greater than or equal to unsafe.Sizeof(uintptr).
func (v *View) ReadToWriter(w io.Writer, count int64) (int64, error) {
var (
done int64
n int
err error
)
offset := 0 // Spill-over for batching.
for buf := v.data.Front(); buf != nil && done < count; buf = buf.Next() {
// Has this been consumed? Skip it.
sz := buf.ReadSize()
if sz <= offset {
offset -= sz
continue
}
sz -= offset
// Is this less than the minimum batch?
left := count - done
if sz < minBatch && left >= int64(minBatch) && (v.size-done) >= int64(minBatch) {
tmp := make([]byte, minBatch)
n, err = v.ReadAt(tmp, done)
w.Write(tmp[:n])
done += int64(n)
offset = n - sz // Reset below.
if err != nil {
break
}
continue
}
// Limit the write if necessary.
if int64(sz) >= left {
sz = int(left)
}
// Perform the actual write.
n, err = w.Write(buf.ReadSlice()[offset : offset+sz])
done += int64(n)
if err != nil {
break
}
// Reset spill-over.
offset = 0
}
return done, err
}
// A Range specifies a range of buffer.
type Range struct {
begin int
end int
}
// Intersect returns the intersection of x and y.
func (x Range) Intersect(y Range) Range {
if x.begin < y.begin {
x.begin = y.begin
}
if x.end > y.end {
x.end = y.end
}
if x.begin >= x.end {
return Range{}
}
return x
}
// Offset returns x offset by off.
func (x Range) Offset(off int) Range {
x.begin += off
x.end += off
return x
}
// Len returns the length of x.
func (x Range) Len() int {
l := x.end - x.begin
if l < 0 {
l = 0
}
return l
}
@@ -12,18 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
package buffer
import (
"time"
"unsafe"
)
// saveXmitTime is invoked by stateify.
func (rc *rackControl) saveXmitTime() unixTime {
return unixTime{rc.xmitTime.Unix(), rc.xmitTime.UnixNano()}
}
// loadXmitTime is invoked by stateify.
func (rc *rackControl) loadXmitTime(unix unixTime) {
rc.xmitTime = time.Unix(unix.second, unix.nano)
}
// minBatch is the smallest Read or Write operation that the
// WriteFromReader and ReadToWriter functions will use.
//
// This is defined as the size of a native pointer.
const minBatch = int(unsafe.Sizeof(uintptr(0)))
+172
View File
@@ -0,0 +1,172 @@
// 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 context defines an internal context type.
//
// The given Context conforms to the standard Go context, but mandates
// additional methods that are specific to the kernel internals. Note however,
// that the Context described by this package carries additional constraints
// regarding concurrent access and retaining beyond the scope of a call.
//
// See the Context type for complete details.
package context
import (
"context"
"time"
"inet.af/netstack/log"
)
// A Context represents a thread of execution (hereafter "goroutine" to reflect
// Go idiosyncrasy). It carries state associated with the goroutine across API
// boundaries.
//
// While Context exists for essentially the same reasons as Go's standard
// context.Context, the standard type represents the state of an operation
// rather than that of a goroutine. This is a critical distinction:
//
// - Unlike context.Context, which "may be passed to functions running in
// different goroutines", it is *not safe* to use the same Context in multiple
// concurrent goroutines.
//
// - It is *not safe* to retain a Context passed to a function beyond the scope
// of that function call.
//
// In both cases, values extracted from the Context should be used instead.
type Context interface {
log.Logger
context.Context
ChannelSleeper
// UninterruptibleSleepStart indicates the beginning of an uninterruptible
// sleep state (equivalent to Linux's TASK_UNINTERRUPTIBLE). If deactivate
// is true and the Context represents a Task, the Task's AddressSpace is
// deactivated.
UninterruptibleSleepStart(deactivate bool)
// UninterruptibleSleepFinish indicates the end of an uninterruptible sleep
// state that was begun by a previous call to UninterruptibleSleepStart. If
// activate is true and the Context represents a Task, the Task's
// AddressSpace is activated. Normally activate is the same value as the
// deactivate parameter passed to UninterruptibleSleepStart.
UninterruptibleSleepFinish(activate bool)
}
// A ChannelSleeper represents a goroutine that may sleep interruptibly, where
// interruption is indicated by a channel becoming readable.
type ChannelSleeper interface {
// SleepStart is called before going to sleep interruptibly. If SleepStart
// returns a non-nil channel and that channel becomes ready for receiving
// while the goroutine is sleeping, the goroutine should be woken, and
// SleepFinish(false) should be called. Otherwise, SleepFinish(true) should
// be called after the goroutine stops sleeping.
SleepStart() <-chan struct{}
// SleepFinish is called after an interruptibly-sleeping goroutine stops
// sleeping, as documented by SleepStart.
SleepFinish(success bool)
// Interrupted returns true if the channel returned by SleepStart is
// ready for receiving.
Interrupted() bool
}
// NoopSleeper is a noop implementation of ChannelSleeper and
// Context.UninterruptibleSleep* methods for anonymous embedding in other types
// that do not implement special behavior around sleeps.
type NoopSleeper struct{}
// SleepStart implements ChannelSleeper.SleepStart.
func (NoopSleeper) SleepStart() <-chan struct{} {
return nil
}
// SleepFinish implements ChannelSleeper.SleepFinish.
func (NoopSleeper) SleepFinish(success bool) {}
// Interrupted implements ChannelSleeper.Interrupted.
func (NoopSleeper) Interrupted() bool {
return false
}
// UninterruptibleSleepStart implements Context.UninterruptibleSleepStart.
func (NoopSleeper) UninterruptibleSleepStart(deactivate bool) {}
// UninterruptibleSleepFinish implements Context.UninterruptibleSleepFinish.
func (NoopSleeper) UninterruptibleSleepFinish(activate bool) {}
// Deadline implements context.Context.Deadline.
func (NoopSleeper) Deadline() (time.Time, bool) {
return time.Time{}, false
}
// Done implements context.Context.Done.
func (NoopSleeper) Done() <-chan struct{} {
return nil
}
// Err returns context.Context.Err.
func (NoopSleeper) Err() error {
return nil
}
// logContext implements basic logging.
type logContext struct {
log.Logger
NoopSleeper
}
// Value implements Context.Value.
func (logContext) Value(key interface{}) interface{} {
return nil
}
// bgContext is the context returned by context.Background.
var bgContext = &logContext{Logger: log.Log()}
// Background returns an empty context using the default logger.
// Generally, one should use the Task as their context when available, or avoid
// having to use a context in places where a Task is unavailable.
//
// Using a Background context for tests is fine, as long as no values are
// needed from the context in the tested code paths.
func Background() Context {
return bgContext
}
// WithValue returns a copy of parent in which the value associated with key is
// val.
func WithValue(parent Context, key, val interface{}) Context {
return &withValue{
Context: parent,
key: key,
val: val,
}
}
type withValue struct {
Context
key interface{}
val interface{}
}
// Value implements Context.Value.
func (ctx *withValue) Value(key interface{}) interface{} {
if key == ctx.key {
return ctx.val
}
return ctx.Context.Value(key)
}
@@ -0,0 +1,3 @@
// automatically generated by stateify.
package context
+23 -3
View File
@@ -12,10 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.13
// +build !go1.18
//go:build go1.13 && !go1.19
// +build go1.13,!go1.19
// Check type signatures when updating Go version.
// //go:linkname directives type-checked by checklinkname. Any other
// non-linkname assumptions outside the Go 1 compatibility guarantee should
// have an accompanied vet check or version guard build tag.
// Check type signatures and Noescape when updating Go version.
//
// TODO(b/165820485): add these checks to checklinkname.
// Package gohacks contains utilities for subverting the Go compiler.
package gohacks
@@ -75,3 +81,17 @@ func StringFromImmutableBytes(bs []byte) string {
// strings.Builder.String().
return *(*string)(unsafe.Pointer(&bs))
}
// Note that go:linkname silently doesn't work if the local name is exported,
// necessitating an indirection for exported functions.
// Memmove is runtime.memmove, exported for SeqAtomicLoad/SeqAtomicTryLoad<T>.
//
//go:nosplit
func Memmove(to, from unsafe.Pointer, n uintptr) {
memmove(to, from, n)
}
//go:linkname memmove runtime.memmove
//go:noescape
func memmove(to, from unsafe.Pointer, n uintptr)
+2 -2
View File
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.12
// +build !go1.18
//go:build go1.12 && !go1.19
// +build go1.12,!go1.19
// Check type signatures when updating Go version.
+1
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !linux
// +build !linux
// Package rand implements a cryptographically secure pseudorandom number
+1
View File
@@ -1,5 +1,6 @@
// automatically generated by stateify.
//go:build !linux
// +build !linux
package rand
+530
View File
@@ -0,0 +1,530 @@
// 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 refs defines an interface for reference counted objects. It
// also provides a drop-in implementation called AtomicRefCount.
package refs
import (
"bytes"
"fmt"
"reflect"
"runtime"
"sync/atomic"
"inet.af/netstack/context"
"inet.af/netstack/log"
"inet.af/netstack/sync"
)
// RefCounter is the interface to be implemented by objects that are reference
// counted.
//
// TODO(gvisor.dev/issue/1624): Get rid of most of this package and replace it
// with refsvfs2.
type RefCounter interface {
// IncRef increments the reference counter on the object.
IncRef()
// DecRef decrements the reference counter on the object.
//
// Note that AtomicRefCounter.DecRef() does not support destructors.
// If a type has a destructor, it must implement its own DecRef()
// method and call AtomicRefCounter.DecRefWithDestructor(destructor).
DecRef(ctx context.Context)
// TryIncRef attempts to increase the reference counter on the object,
// but may fail if all references have already been dropped. This
// should be used only in special circumstances, such as WeakRefs.
TryIncRef() bool
// addWeakRef adds the given weak reference. Note that you should have a
// reference to the object when calling this method.
addWeakRef(*WeakRef)
// dropWeakRef drops the given weak reference. Note that you should have
// a reference to the object when calling this method.
dropWeakRef(*WeakRef)
}
// A WeakRefUser is notified when the last non-weak reference is dropped.
type WeakRefUser interface {
// WeakRefGone is called when the last non-weak reference is dropped.
WeakRefGone(ctx context.Context)
}
// WeakRef is a weak reference.
//
// +stateify savable
type WeakRef struct {
weakRefEntry `state:"nosave"`
// obj is an atomic value that points to the refCounter.
obj atomic.Value `state:".(savedReference)"`
// user is notified when the weak ref is zapped by the object getting
// destroyed.
user WeakRefUser
}
// weakRefPool is a pool of weak references to avoid allocations on the hot path.
var weakRefPool = sync.Pool{
New: func() interface{} {
return &WeakRef{}
},
}
// NewWeakRef acquires a weak reference for the given object.
//
// An optional user will be notified when the last non-weak reference is
// dropped.
//
// Note that you must hold a reference to the object prior to getting a weak
// reference. (But you may drop the non-weak reference after that.)
func NewWeakRef(rc RefCounter, u WeakRefUser) *WeakRef {
w := weakRefPool.Get().(*WeakRef)
w.init(rc, u)
return w
}
// get attempts to get a normal reference to the underlying object, and returns
// the object. If this weak reference has already been zapped (the object has
// been destroyed) then false is returned. If the object still exists, then
// true is returned.
func (w *WeakRef) get() (RefCounter, bool) {
rc := w.obj.Load().(RefCounter)
if v := reflect.ValueOf(rc); v == reflect.Zero(v.Type()) {
// This pointer has already been zapped by zap() below. We do
// this to ensure that the GC can collect the underlying
// RefCounter objects and they don't hog resources.
return nil, false
}
if !rc.TryIncRef() {
return nil, true
}
return rc, true
}
// Get attempts to get a normal reference to the underlying object, and returns
// the object. If this fails (the object no longer exists), then nil will be
// returned instead.
func (w *WeakRef) Get() RefCounter {
rc, _ := w.get()
return rc
}
// Drop drops this weak reference. You should always call drop when you are
// finished with the weak reference. You may not use this object after calling
// drop.
func (w *WeakRef) Drop(ctx context.Context) {
rc, ok := w.get()
if !ok {
// We've been zapped already. When the refcounter has called
// zap, we're guaranteed it's not holding references.
weakRefPool.Put(w)
return
}
if rc == nil {
// The object is in the process of being destroyed. We can't
// remove this from the object's list, nor can we return this
// object to the pool. It'll just be garbage collected. This is
// a rare edge case, so it's not a big deal.
return
}
// At this point, we have a reference on the object. So destruction
// of the object (and zapping this weak reference) can't race here.
rc.dropWeakRef(w)
// And now aren't on the object's list of weak references. So it won't
// zap us if this causes the reference count to drop to zero.
rc.DecRef(ctx)
// Return to the pool.
weakRefPool.Put(w)
}
// init initializes this weak reference.
func (w *WeakRef) init(rc RefCounter, u WeakRefUser) {
// Reset the contents of the weak reference.
// This is important because we are reseting the atomic value type.
// Otherwise, we could panic here if obj is different than what it was
// the last time this was used.
*w = WeakRef{}
w.user = u
w.obj.Store(rc)
// In the load path, we may already have a nil value. So we need to
// check whether or not that is the case before calling addWeakRef.
if v := reflect.ValueOf(rc); v != reflect.Zero(v.Type()) {
rc.addWeakRef(w)
}
}
// zap zaps this weak reference.
func (w *WeakRef) zap() {
// We need to be careful about types here.
// So reflect is involved. But it's not that bad.
rc := w.obj.Load()
typ := reflect.TypeOf(rc)
w.obj.Store(reflect.Zero(typ).Interface())
}
// AtomicRefCount keeps a reference count using atomic operations and calls the
// destructor when the count reaches zero.
//
// Do not use AtomicRefCount for new ref-counted objects! It is deprecated in
// favor of the refsvfs2 package.
//
// N.B. To allow the zero-object to be initialized, the count is offset by
// 1, that is, when refCount is n, there are really n+1 references.
//
// +stateify savable
type AtomicRefCount struct {
// refCount is composed of two fields:
//
// [32-bit speculative references]:[32-bit real references]
//
// Speculative references are used for TryIncRef, to avoid a
// CompareAndSwap loop. See IncRef, DecRef and TryIncRef for details of
// how these fields are used.
refCount int64
// name is the name of the type which owns this ref count.
//
// name is immutable after EnableLeakCheck is called.
name string
// stack optionally records the caller of EnableLeakCheck.
//
// stack is immutable after EnableLeakCheck is called.
stack []uintptr
// mu protects the list below.
mu sync.Mutex `state:"nosave"`
// weakRefs is our collection of weak references.
weakRefs weakRefList `state:"nosave"`
}
// LeakMode configures the leak checker.
type LeakMode uint32
// TODO(gvisor.dev/issue/1624): Simplify down to two modes (on/off) once vfs1
// ref counting is gone.
const (
// UninitializedLeakChecking indicates that the leak checker has not yet been initialized.
UninitializedLeakChecking LeakMode = iota
// NoLeakChecking indicates that no effort should be made to check for
// leaks.
NoLeakChecking
// LeaksLogWarning indicates that a warning should be logged when leaks
// are found.
LeaksLogWarning
// LeaksLogTraces indicates that a trace collected during allocation
// should be logged when leaks are found.
LeaksLogTraces
)
// Set implements flag.Value.
func (l *LeakMode) Set(v string) error {
switch v {
case "disabled":
*l = NoLeakChecking
case "log-names":
*l = LeaksLogWarning
case "log-traces":
*l = LeaksLogTraces
default:
return fmt.Errorf("invalid ref leak mode %q", v)
}
return nil
}
// Get implements flag.Value.
func (l *LeakMode) Get() interface{} {
return *l
}
// String implements flag.Value.
func (l LeakMode) String() string {
switch l {
case UninitializedLeakChecking:
return "uninitialized"
case NoLeakChecking:
return "disabled"
case LeaksLogWarning:
return "log-names"
case LeaksLogTraces:
return "log-traces"
}
panic(fmt.Sprintf("invalid ref leak mode %d", l))
}
// leakMode stores the current mode for the reference leak checker.
//
// Values must be one of the LeakMode values.
//
// leakMode must be accessed atomically.
var leakMode uint32
// SetLeakMode configures the reference leak checker.
func SetLeakMode(mode LeakMode) {
atomic.StoreUint32(&leakMode, uint32(mode))
}
// GetLeakMode returns the current leak mode.
func GetLeakMode() LeakMode {
return LeakMode(atomic.LoadUint32(&leakMode))
}
const maxStackFrames = 40
type fileLine struct {
file string
line int
}
// A stackKey is a representation of a stack frame for use as a map key.
//
// The fileLine type is used as PC values seem to vary across collections, even
// for the same call stack.
type stackKey [maxStackFrames]fileLine
var stackCache = struct {
sync.Mutex
entries map[stackKey][]uintptr
}{entries: map[stackKey][]uintptr{}}
func makeStackKey(pcs []uintptr) stackKey {
frames := runtime.CallersFrames(pcs)
var key stackKey
keySlice := key[:0]
for {
frame, more := frames.Next()
keySlice = append(keySlice, fileLine{frame.File, frame.Line})
if !more || len(keySlice) == len(key) {
break
}
}
return key
}
// RecordStack constructs and returns the PCs on the current stack.
func RecordStack() []uintptr {
pcs := make([]uintptr, maxStackFrames)
n := runtime.Callers(1, pcs)
if n == 0 {
// No pcs available. Stop now.
//
// This can happen if the first argument to runtime.Callers
// is large.
return nil
}
pcs = pcs[:n]
key := makeStackKey(pcs)
stackCache.Lock()
v, ok := stackCache.entries[key]
if !ok {
// Reallocate to prevent pcs from escaping.
v = append([]uintptr(nil), pcs...)
stackCache.entries[key] = v
}
stackCache.Unlock()
return v
}
// FormatStack converts the given stack into a readable format.
func FormatStack(pcs []uintptr) string {
frames := runtime.CallersFrames(pcs)
var trace bytes.Buffer
for {
frame, more := frames.Next()
fmt.Fprintf(&trace, "%s:%d: %s\n", frame.File, frame.Line, frame.Function)
if !more {
break
}
}
return trace.String()
}
func (r *AtomicRefCount) finalize() {
var note string
switch LeakMode(atomic.LoadUint32(&leakMode)) {
case NoLeakChecking:
return
case UninitializedLeakChecking:
note = "(Leak checker uninitialized): "
}
if n := r.ReadRefs(); n != 0 {
msg := fmt.Sprintf("%sAtomicRefCount %p owned by %q garbage collected with ref count of %d (want 0)", note, r, r.name, n)
if len(r.stack) != 0 {
msg += ":\nCaller:\n" + FormatStack(r.stack)
} else {
msg += " (enable trace logging to debug)"
}
log.Warningf(msg)
}
}
// EnableLeakCheck checks for reference leaks when the AtomicRefCount gets
// garbage collected.
//
// This function adds a finalizer to the AtomicRefCount, so the AtomicRefCount
// must be at the beginning of its parent.
//
// name is a friendly name that will be listed as the owner of the
// AtomicRefCount in logs. It should be the name of the parent type, including
// package.
func (r *AtomicRefCount) EnableLeakCheck(name string) {
if name == "" {
panic("invalid name")
}
switch LeakMode(atomic.LoadUint32(&leakMode)) {
case NoLeakChecking:
return
case LeaksLogTraces:
r.stack = RecordStack()
}
r.name = name
runtime.SetFinalizer(r, (*AtomicRefCount).finalize)
}
// ReadRefs returns the current number of references. The returned count is
// inherently racy and is unsafe to use without external synchronization.
func (r *AtomicRefCount) ReadRefs() int64 {
// Account for the internal -1 offset on refcounts.
return atomic.LoadInt64(&r.refCount) + 1
}
// IncRef increments this object's reference count. While the count is kept
// greater than zero, the destructor doesn't get called.
//
// The sanity check here is limited to real references, since if they have
// dropped beneath zero then the object should have been destroyed.
//
//go:nosplit
func (r *AtomicRefCount) IncRef() {
if v := atomic.AddInt64(&r.refCount, 1); v <= 0 {
panic("Incrementing non-positive ref count")
}
}
// TryIncRef attempts to increment the reference count, *unless the count has
// already reached zero*. If false is returned, then the object has already
// been destroyed, and the weak reference is no longer valid. If true if
// returned then a valid reference is now held on the object.
//
// To do this safely without a loop, a speculative reference is first acquired
// on the object. This allows multiple concurrent TryIncRef calls to
// distinguish other TryIncRef calls from genuine references held.
//
//go:nosplit
func (r *AtomicRefCount) TryIncRef() bool {
const speculativeRef = 1 << 32
v := atomic.AddInt64(&r.refCount, speculativeRef)
if int32(v) < 0 {
// This object has already been freed.
atomic.AddInt64(&r.refCount, -speculativeRef)
return false
}
// Turn into a real reference.
atomic.AddInt64(&r.refCount, -speculativeRef+1)
return true
}
// addWeakRef adds the given weak reference.
func (r *AtomicRefCount) addWeakRef(w *WeakRef) {
r.mu.Lock()
r.weakRefs.PushBack(w)
r.mu.Unlock()
}
// dropWeakRef drops the given weak reference.
func (r *AtomicRefCount) dropWeakRef(w *WeakRef) {
r.mu.Lock()
r.weakRefs.Remove(w)
r.mu.Unlock()
}
// DecRefWithDestructor decrements the object's reference count. If the
// resulting count is negative and the destructor is not nil, then the
// destructor will be called.
//
// Note that speculative references are counted here. Since they were added
// prior to real references reaching zero, they will successfully convert to
// real references. In other words, we see speculative references only in the
// following case:
//
// A: TryIncRef [speculative increase => sees non-negative references]
// B: DecRef [real decrease]
// A: TryIncRef [transform speculative to real]
//
//go:nosplit
func (r *AtomicRefCount) DecRefWithDestructor(ctx context.Context, destroy func(context.Context)) {
switch v := atomic.AddInt64(&r.refCount, -1); {
case v < -1:
panic("Decrementing non-positive ref count")
case v == -1:
// Zap weak references. Note that at this point, all weak
// references are already invalid. That is, TryIncRef() will
// return false due to the reference count check.
r.mu.Lock()
for !r.weakRefs.Empty() {
w := r.weakRefs.Front()
// Capture the callback because w cannot be touched
// after it's zapped -- the owner is free it reuse it
// after that.
user := w.user
r.weakRefs.Remove(w)
w.zap()
if user != nil {
r.mu.Unlock()
user.WeakRefGone(ctx)
r.mu.Lock()
}
}
r.mu.Unlock()
// Call the destructor.
if destroy != nil {
destroy(ctx)
}
}
}
// DecRef decrements this object's reference count.
//
//go:nosplit
func (r *AtomicRefCount) DecRef(ctx context.Context) {
r.DecRefWithDestructor(ctx, nil)
}
// OnExit is called on sandbox exit. It runs GC to enqueue refcount finalizers,
// which check for reference leaks. There is no way to guarantee that every
// finalizer will run before exiting, but this at least ensures that they will
// be discovered/enqueued by GC.
func OnExit() {
if LeakMode(atomic.LoadUint32(&leakMode)) != NoLeakChecking {
runtime.GC()
}
}
+35
View File
@@ -0,0 +1,35 @@
// 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 refs
// +stateify savable
type savedReference struct {
obj interface{}
}
func (w *WeakRef) saveObj() savedReference {
// We load the object directly, because it is typed. This will be
// serialized and loaded as a typed value.
return savedReference{w.obj.Load()}
}
func (w *WeakRef) loadObj(v savedReference) {
// See note above. This will be serialized and loaded typed. So we're okay
// as long as refs aren't changing during save and load (which they should
// not be).
//
// w.user is loaded before loadObj is called.
w.init(v.obj.(RefCounter), w.user)
}
+157
View File
@@ -0,0 +1,157 @@
// automatically generated by stateify.
package refs
import (
"inet.af/netstack/state"
)
func (w *WeakRef) StateTypeName() string {
return "pkg/refs.WeakRef"
}
func (w *WeakRef) StateFields() []string {
return []string{
"obj",
"user",
}
}
func (w *WeakRef) beforeSave() {}
// +checklocksignore
func (w *WeakRef) StateSave(stateSinkObject state.Sink) {
w.beforeSave()
var objValue savedReference
objValue = w.saveObj()
stateSinkObject.SaveValue(0, objValue)
stateSinkObject.Save(1, &w.user)
}
func (w *WeakRef) afterLoad() {}
// +checklocksignore
func (w *WeakRef) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(1, &w.user)
stateSourceObject.LoadValue(0, new(savedReference), func(y interface{}) { w.loadObj(y.(savedReference)) })
}
func (r *AtomicRefCount) StateTypeName() string {
return "pkg/refs.AtomicRefCount"
}
func (r *AtomicRefCount) StateFields() []string {
return []string{
"refCount",
"name",
"stack",
}
}
func (r *AtomicRefCount) beforeSave() {}
// +checklocksignore
func (r *AtomicRefCount) StateSave(stateSinkObject state.Sink) {
r.beforeSave()
stateSinkObject.Save(0, &r.refCount)
stateSinkObject.Save(1, &r.name)
stateSinkObject.Save(2, &r.stack)
}
func (r *AtomicRefCount) afterLoad() {}
// +checklocksignore
func (r *AtomicRefCount) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &r.refCount)
stateSourceObject.Load(1, &r.name)
stateSourceObject.Load(2, &r.stack)
}
func (s *savedReference) StateTypeName() string {
return "pkg/refs.savedReference"
}
func (s *savedReference) StateFields() []string {
return []string{
"obj",
}
}
func (s *savedReference) beforeSave() {}
// +checklocksignore
func (s *savedReference) StateSave(stateSinkObject state.Sink) {
s.beforeSave()
stateSinkObject.Save(0, &s.obj)
}
func (s *savedReference) afterLoad() {}
// +checklocksignore
func (s *savedReference) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &s.obj)
}
func (l *weakRefList) StateTypeName() string {
return "pkg/refs.weakRefList"
}
func (l *weakRefList) StateFields() []string {
return []string{
"head",
"tail",
}
}
func (l *weakRefList) beforeSave() {}
// +checklocksignore
func (l *weakRefList) StateSave(stateSinkObject state.Sink) {
l.beforeSave()
stateSinkObject.Save(0, &l.head)
stateSinkObject.Save(1, &l.tail)
}
func (l *weakRefList) afterLoad() {}
// +checklocksignore
func (l *weakRefList) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &l.head)
stateSourceObject.Load(1, &l.tail)
}
func (e *weakRefEntry) StateTypeName() string {
return "pkg/refs.weakRefEntry"
}
func (e *weakRefEntry) StateFields() []string {
return []string{
"next",
"prev",
}
}
func (e *weakRefEntry) beforeSave() {}
// +checklocksignore
func (e *weakRefEntry) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
stateSinkObject.Save(0, &e.next)
stateSinkObject.Save(1, &e.prev)
}
func (e *weakRefEntry) afterLoad() {}
// +checklocksignore
func (e *weakRefEntry) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &e.next)
stateSourceObject.Load(1, &e.prev)
}
func init() {
state.Register((*WeakRef)(nil))
state.Register((*AtomicRefCount)(nil))
state.Register((*savedReference)(nil))
state.Register((*weakRefList)(nil))
state.Register((*weakRefEntry)(nil))
}
+221
View File
@@ -0,0 +1,221 @@
package refs
// ElementMapper provides an identity mapping by default.
//
// This can be replaced to provide a struct that maps elements to linker
// objects, if they are not the same. An ElementMapper is not typically
// required if: Linker is left as is, Element is left as is, or Linker and
// Element are the same type.
type weakRefElementMapper struct{}
// linkerFor maps an Element to a Linker.
//
// This default implementation should be inlined.
//
//go:nosplit
func (weakRefElementMapper) linkerFor(elem *WeakRef) *WeakRef { return elem }
// List is an intrusive list. Entries can be added to or removed from the list
// in O(1) time and with no additional memory allocations.
//
// The zero value for List is an empty list ready to use.
//
// To iterate over a list (where l is a List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.
// }
//
// +stateify savable
type weakRefList struct {
head *WeakRef
tail *WeakRef
}
// Reset resets list l to the empty state.
func (l *weakRefList) Reset() {
l.head = nil
l.tail = nil
}
// Empty returns true iff the list is empty.
//
//go:nosplit
func (l *weakRefList) Empty() bool {
return l.head == nil
}
// Front returns the first element of list l or nil.
//
//go:nosplit
func (l *weakRefList) Front() *WeakRef {
return l.head
}
// Back returns the last element of list l or nil.
//
//go:nosplit
func (l *weakRefList) Back() *WeakRef {
return l.tail
}
// Len returns the number of elements in the list.
//
// NOTE: This is an O(n) operation.
//
//go:nosplit
func (l *weakRefList) Len() (count int) {
for e := l.Front(); e != nil; e = (weakRefElementMapper{}.linkerFor(e)).Next() {
count++
}
return count
}
// PushFront inserts the element e at the front of list l.
//
//go:nosplit
func (l *weakRefList) PushFront(e *WeakRef) {
linker := weakRefElementMapper{}.linkerFor(e)
linker.SetNext(l.head)
linker.SetPrev(nil)
if l.head != nil {
weakRefElementMapper{}.linkerFor(l.head).SetPrev(e)
} else {
l.tail = e
}
l.head = e
}
// PushBack inserts the element e at the back of list l.
//
//go:nosplit
func (l *weakRefList) PushBack(e *WeakRef) {
linker := weakRefElementMapper{}.linkerFor(e)
linker.SetNext(nil)
linker.SetPrev(l.tail)
if l.tail != nil {
weakRefElementMapper{}.linkerFor(l.tail).SetNext(e)
} else {
l.head = e
}
l.tail = e
}
// PushBackList inserts list m at the end of list l, emptying m.
//
//go:nosplit
func (l *weakRefList) PushBackList(m *weakRefList) {
if l.head == nil {
l.head = m.head
l.tail = m.tail
} else if m.head != nil {
weakRefElementMapper{}.linkerFor(l.tail).SetNext(m.head)
weakRefElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
l.tail = m.tail
}
m.head = nil
m.tail = nil
}
// InsertAfter inserts e after b.
//
//go:nosplit
func (l *weakRefList) InsertAfter(b, e *WeakRef) {
bLinker := weakRefElementMapper{}.linkerFor(b)
eLinker := weakRefElementMapper{}.linkerFor(e)
a := bLinker.Next()
eLinker.SetNext(a)
eLinker.SetPrev(b)
bLinker.SetNext(e)
if a != nil {
weakRefElementMapper{}.linkerFor(a).SetPrev(e)
} else {
l.tail = e
}
}
// InsertBefore inserts e before a.
//
//go:nosplit
func (l *weakRefList) InsertBefore(a, e *WeakRef) {
aLinker := weakRefElementMapper{}.linkerFor(a)
eLinker := weakRefElementMapper{}.linkerFor(e)
b := aLinker.Prev()
eLinker.SetNext(a)
eLinker.SetPrev(b)
aLinker.SetPrev(e)
if b != nil {
weakRefElementMapper{}.linkerFor(b).SetNext(e)
} else {
l.head = e
}
}
// Remove removes e from l.
//
//go:nosplit
func (l *weakRefList) Remove(e *WeakRef) {
linker := weakRefElementMapper{}.linkerFor(e)
prev := linker.Prev()
next := linker.Next()
if prev != nil {
weakRefElementMapper{}.linkerFor(prev).SetNext(next)
} else if l.head == e {
l.head = next
}
if next != nil {
weakRefElementMapper{}.linkerFor(next).SetPrev(prev)
} else if l.tail == e {
l.tail = prev
}
linker.SetNext(nil)
linker.SetPrev(nil)
}
// Entry is a default implementation of Linker. Users can add anonymous fields
// of this type to their structs to make them automatically implement the
// methods needed by List.
//
// +stateify savable
type weakRefEntry struct {
next *WeakRef
prev *WeakRef
}
// Next returns the entry that follows e in the list.
//
//go:nosplit
func (e *weakRefEntry) Next() *WeakRef {
return e.next
}
// Prev returns the entry that precedes e in the list.
//
//go:nosplit
func (e *weakRefEntry) Prev() *WeakRef {
return e.prev
}
// SetNext assigns 'entry' as the entry that follows e in the list.
//
//go:nosplit
func (e *weakRefEntry) SetNext(elem *WeakRef) {
e.next = elem
}
// SetPrev assigns 'entry' as the entry that precedes e in the list.
//
//go:nosplit
func (e *weakRefEntry) SetPrev(elem *WeakRef) {
e.prev = elem
}
+41
View File
@@ -0,0 +1,41 @@
// 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 refsvfs2 defines an interface for a reference-counted object.
package refsvfs2
import (
"inet.af/netstack/context"
)
// RefCounter is the interface to be implemented by objects that are reference
// counted.
type RefCounter interface {
// IncRef increments the reference counter on the object.
IncRef()
// DecRef decrements the object's reference count. Users of refs_template.Refs
// may specify a destructor to be called once the reference count reaches zero.
DecRef(ctx context.Context)
}
// TryRefCounter is like RefCounter but allow the ref increment to be tried.
type TryRefCounter interface {
RefCounter
// TryIncRef attempts to increment the reference count, but may fail if all
// references have already been dropped, in which case it returns false. If
// true is returned, then a valid reference is now held on the object.
TryIncRef() bool
}
+138
View File
@@ -0,0 +1,138 @@
// 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 refsvfs2
import (
"fmt"
"inet.af/netstack/log"
refs_vfs1 "inet.af/netstack/refs"
"inet.af/netstack/sync"
)
var (
// liveObjects is a global map of reference-counted objects. Objects are
// inserted when leak check is enabled, and they are removed when they are
// destroyed. It is protected by liveObjectsMu.
liveObjects map[CheckedObject]struct{}
liveObjectsMu sync.Mutex
)
// CheckedObject represents a reference-counted object with an informative
// leak detection message.
type CheckedObject interface {
// RefType is the type of the reference-counted object.
RefType() string
// LeakMessage supplies a warning to be printed upon leak detection.
LeakMessage() string
// LogRefs indicates whether reference-related events should be logged.
LogRefs() bool
}
func init() {
liveObjects = make(map[CheckedObject]struct{})
}
// leakCheckEnabled returns whether leak checking is enabled. The following
// functions should only be called if it returns true.
func leakCheckEnabled() bool {
return refs_vfs1.GetLeakMode() != refs_vfs1.NoLeakChecking
}
// Register adds obj to the live object map.
func Register(obj CheckedObject) {
if leakCheckEnabled() {
liveObjectsMu.Lock()
if _, ok := liveObjects[obj]; ok {
panic(fmt.Sprintf("Unexpected entry in leak checking map: reference %p already added", obj))
}
liveObjects[obj] = struct{}{}
liveObjectsMu.Unlock()
if leakCheckEnabled() && obj.LogRefs() {
logEvent(obj, "registered")
}
}
}
// Unregister removes obj from the live object map.
func Unregister(obj CheckedObject) {
if leakCheckEnabled() {
liveObjectsMu.Lock()
defer liveObjectsMu.Unlock()
if _, ok := liveObjects[obj]; !ok {
panic(fmt.Sprintf("Expected to find entry in leak checking map for reference %p", obj))
}
delete(liveObjects, obj)
if leakCheckEnabled() && obj.LogRefs() {
logEvent(obj, "unregistered")
}
}
}
// LogIncRef logs a reference increment.
func LogIncRef(obj CheckedObject, refs int64) {
if leakCheckEnabled() && obj.LogRefs() {
logEvent(obj, fmt.Sprintf("IncRef to %d", refs))
}
}
// LogTryIncRef logs a successful TryIncRef call.
func LogTryIncRef(obj CheckedObject, refs int64) {
if leakCheckEnabled() && obj.LogRefs() {
logEvent(obj, fmt.Sprintf("TryIncRef to %d", refs))
}
}
// LogDecRef logs a reference decrement.
func LogDecRef(obj CheckedObject, refs int64) {
if leakCheckEnabled() && obj.LogRefs() {
logEvent(obj, fmt.Sprintf("DecRef to %d", refs))
}
}
// logEvent logs a message for the given reference-counted object.
//
// obj.LogRefs() should be checked before calling logEvent, in order to avoid
// calling any text processing needed to evaluate msg.
func logEvent(obj CheckedObject, msg string) {
log.Infof("[%s %p] %s:\n%s", obj.RefType(), obj, msg, refs_vfs1.FormatStack(refs_vfs1.RecordStack()))
}
// checkOnce makes sure that leak checking is only done once. DoLeakCheck is
// called from multiple places (which may overlap) to cover different sandbox
// exit scenarios.
var checkOnce sync.Once
// DoLeakCheck iterates through the live object map and logs a message for each
// object. It is called once no reference-counted objects should be reachable
// anymore, at which point anything left in the map is considered a leak.
func DoLeakCheck() {
if leakCheckEnabled() {
checkOnce.Do(func() {
liveObjectsMu.Lock()
defer liveObjectsMu.Unlock()
leaked := len(liveObjects)
if leaked > 0 {
msg := fmt.Sprintf("Leak checking detected %d leaked objects:\n", leaked)
for obj := range liveObjects {
msg += obj.LeakMessage() + "\n"
}
log.Warningf(msg)
}
})
}
}
@@ -0,0 +1,3 @@
// automatically generated by stateify.
package refsvfs2
+45 -63
View File
@@ -37,15 +37,15 @@
//
// // One time set-up.
// s := sleep.Sleeper{}
// s.AddWaker(&w1, constant1)
// s.AddWaker(&w2, constant2)
// s.AddWaker(&w1)
// s.AddWaker(&w2)
//
// // Called repeatedly.
// for {
// switch id, _ := s.Fetch(true); id {
// case constant1:
// switch s.Fetch(true) {
// case &w1:
// // Do work triggered by w1 being asserted.
// case constant2:
// case &w2:
// // Do work triggered by w2 being asserted.
// }
// }
@@ -119,13 +119,18 @@ type Sleeper struct {
waitingG uintptr
}
// AddWaker associates the given waker to the sleeper. id is the value to be
// returned when the sleeper is woken by the given waker.
func (s *Sleeper) AddWaker(w *Waker, id int) {
// AddWaker associates the given waker to the sleeper.
func (s *Sleeper) AddWaker(w *Waker) {
if w.allWakersNext != nil {
panic("waker has non-nil allWakersNext; owned by another sleeper?")
}
if w.next != nil {
panic("waker has non-nil next; queued in another sleeper?")
}
// Add the waker to the list of all wakers.
w.allWakersNext = s.allWakers
s.allWakers = w
w.id = id
// Try to associate the waker with the sleeper. If it's already
// asserted, we simply enqueue it in the "ready" list.
@@ -213,28 +218,26 @@ func commitSleep(g uintptr, waitingG unsafe.Pointer) bool {
return sync.RaceUncheckedAtomicCompareAndSwapUintptr((*uintptr)(waitingG), preparingG, g)
}
// Fetch fetches the next wake-up notification. If a notification is immediately
// available, it is returned right away. Otherwise, the behavior depends on the
// value of 'block': if true, the current goroutine blocks until a notification
// arrives, then returns it; if false, returns 'ok' as false.
//
// When 'ok' is true, the value of 'id' corresponds to the id associated with
// the waker; when 'ok' is false, 'id' is undefined.
// Fetch fetches the next wake-up notification. If a notification is
// immediately available, the asserted waker is returned immediately.
// Otherwise, the behavior depends on the value of 'block': if true, the
// current goroutine blocks until a notification arrives and returns the
// asserted waker; if false, nil will be returned.
//
// N.B. This method is *not* thread-safe. Only one goroutine at a time is
// allowed to call this method.
func (s *Sleeper) Fetch(block bool) (id int, ok bool) {
func (s *Sleeper) Fetch(block bool) *Waker {
for {
w := s.nextWaker(block)
if w == nil {
return -1, false
return nil
}
// Reassociate the waker with the sleeper. If the waker was
// still asserted we can return it, otherwise try the next one.
old := (*Sleeper)(atomic.SwapPointer(&w.s, usleeper(s)))
if old == &assertedSleeper {
return w.id, true
return w
}
}
}
@@ -243,51 +246,34 @@ func (s *Sleeper) Fetch(block bool) (id int, ok bool) {
// removes the association with all wakers so that they can be safely reused
// by another sleeper after Done() returns.
func (s *Sleeper) Done() {
// Remove all associations that we can, and build a list of the ones
// we could not. An association can be removed right away from waker w
// if w.s has a pointer to the sleeper, that is, the waker is not
// asserted yet. By atomically switching w.s to nil, we guarantee that
// subsequent calls to Assert() on the waker will not result in it being
// queued to this sleeper.
var pending *Waker
w := s.allWakers
for w != nil {
next := w.allWakersNext
for {
t := atomic.LoadPointer(&w.s)
if t != usleeper(s) {
w.allWakersNext = pending
pending = w
break
}
if atomic.CompareAndSwapPointer(&w.s, t, nil) {
break
}
// Remove all associations that we can, and build a list of the ones we
// could not. An association can be removed right away from waker w if
// w.s has a pointer to the sleeper, that is, the waker is not asserted
// yet. By atomically switching w.s to nil, we guarantee that
// subsequent calls to Assert() on the waker will not result in it
// being queued.
for w := s.allWakers; w != nil; w = s.allWakers {
next := w.allWakersNext // Before zapping.
if atomic.CompareAndSwapPointer(&w.s, usleeper(s), nil) {
w.allWakersNext = nil
w.next = nil
s.allWakers = next // Move ahead.
continue
}
w = next
}
// The associations that we could not remove are either asserted, or in
// the process of being asserted, or have been asserted and cleared
// before being pulled from the sleeper lists. We must wait for them all
// to make it to the sleeper lists, so that we know that the wakers
// won't do any more work towards waking this sleeper up.
for pending != nil {
pulled := s.nextWaker(true)
// Remove the waker we just pulled from the list of associated
// wakers.
prev := &pending
for w := *prev; w != nil; w = *prev {
if pulled == w {
*prev = w.allWakersNext
break
// Dequeue exactly one waiter from the list, it may not be
// this one but we know this one is in the process. We must
// leave it in the asserted state but drop it from our lists.
if w := s.nextWaker(true); w != nil {
prev := &s.allWakers
for *prev != w {
prev = &((*prev).allWakersNext)
}
prev = &w.allWakersNext
*prev = (*prev).allWakersNext
w.allWakersNext = nil
w.next = nil
}
}
s.allWakers = nil
}
// enqueueAssertedWaker enqueues an asserted waker to the "ready" circular list
@@ -349,10 +335,6 @@ type Waker struct {
// allWakersNext is used to form a linked list of all wakers associated
// to a given sleeper.
allWakersNext *Waker
// id is the value to be returned to sleepers when they wake up due to
// this waker being asserted.
id int
}
// Assert moves the waker to an asserted state, if it isn't asserted yet. When
+1
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !race
// +build !race
package state
+1
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build race
// +build race
package state
+1
View File
@@ -3,6 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !checklocks
// +build !checklocks
package sync
+1
View File
@@ -3,6 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build checklocks
// +build checklocks
package sync
+2 -2
View File
@@ -3,8 +3,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13
// +build !go1.14
//go:build go1.13 && !go1.14
// +build go1.13,!go1.14
package sync
+4 -2
View File
@@ -3,10 +3,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.14
// +build go1.14
// +build !go1.18
// Check go:linkname function signatures when updating Go version.
// //go:linkname directives type-checked by checklinkname. Any other
// non-linkname assumptions outside the Go 1 compatibility guarantee should
// have an accompanied vet check or version guard build tag.
package sync
+16 -2
View File
@@ -3,8 +3,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13
// +build !go1.18
//go:build go1.13 && !go1.19
// +build go1.13,!go1.19
// When updating the build constraint (above), check that syncMutex matches the
// standard library sync.Mutex definition.
@@ -32,6 +32,18 @@ func (m *CrossGoroutineMutex) state() *int32 {
return &(*syncMutex)(unsafe.Pointer(&m.Mutex)).state
}
// Lock locks the underlying Mutex.
// +checklocksignore
func (m *CrossGoroutineMutex) Lock() {
m.Mutex.Lock()
}
// Unlock unlocks the underlying Mutex.
// +checklocksignore
func (m *CrossGoroutineMutex) Unlock() {
m.Mutex.Unlock()
}
const (
mutexUnlocked = 0
mutexLocked = 1
@@ -62,6 +74,7 @@ type Mutex struct {
// Lock locks m. If the lock is already in use, the calling goroutine blocks
// until the mutex is available.
// +checklocksignore
func (m *Mutex) Lock() {
noteLock(unsafe.Pointer(m))
m.m.Lock()
@@ -80,6 +93,7 @@ func (m *Mutex) Unlock() {
// TryLock tries to acquire the mutex. It returns true if it succeeds and false
// otherwise. TryLock does not block.
// +checklocksignore
func (m *Mutex) TryLock() bool {
// Note lock first to enforce proper locking even if unsuccessful.
noteLock(unsafe.Pointer(m))
+1
View File
@@ -3,6 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !race
// +build !race
package sync
+2 -2
View File
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build race
// +build amd64
//go:build race && amd64
// +build race,amd64
#include "textflag.h"
+2 -2
View File
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build race
// +build arm64
//go:build race && arm64
// +build race,arm64
#include "textflag.h"
+1
View File
@@ -3,6 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build race
// +build race
package sync
+11 -20
View File
@@ -3,11 +3,16 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13
// +build !go1.18
//go:build go1.13 && !go1.19
// +build go1.13,!go1.19
// Check go:linkname function signatures, type definitions, and constants when
// updating Go version.
// //go:linkname directives type-checked by checklinkname. Any other
// non-linkname assumptions outside the Go 1 compatibility guarantee should
// have an accompanied vet check or version guard build tag.
// Check type definitions and constants when updating Go version.
//
// TODO(b/165820485): add these checks to checklinkname.
package sync
@@ -17,20 +22,6 @@ import (
"unsafe"
)
// Note that go:linkname silently doesn't work if the local name is exported,
// necessitating an indirection for exported functions.
// Memmove is runtime.memmove, exported for SeqAtomicLoad/SeqAtomicTryLoad<T>.
//
//go:nosplit
func Memmove(to, from unsafe.Pointer, n uintptr) {
memmove(to, from, n)
}
//go:linkname memmove runtime.memmove
//go:noescape
func memmove(to, from unsafe.Pointer, n uintptr)
// Gopark is runtime.gopark. Gopark calls unlockf(pointer to runtime.g, lock);
// if unlockf returns true, Gopark blocks until Goready(pointer to runtime.g)
// is called. unlockf and its callees must be nosplit and norace, since stack
@@ -123,10 +114,10 @@ type maptype struct {
// These functions are only used within the sync package.
//go:linkname semacquire sync.runtime_Semacquire
func semacquire(s *uint32)
func semacquire(addr *uint32)
//go:linkname semrelease sync.runtime_Semrelease
func semrelease(s *uint32, handoff bool, skipframes int)
func semrelease(addr *uint32, handoff bool, skipframes int)
//go:linkname canSpin sync.runtime_canSpin
func canSpin(i int) bool
+8
View File
@@ -37,6 +37,7 @@ const rwmutexMaxReaders = 1 << 30
// TryRLock locks rw for reading. It returns true if it succeeds and false
// otherwise. It does not block.
// +checklocksignore
func (rw *CrossGoroutineRWMutex) TryRLock() bool {
if RaceEnabled {
RaceDisable()
@@ -65,6 +66,7 @@ func (rw *CrossGoroutineRWMutex) TryRLock() bool {
// It should not be used for recursive read locking; a blocked Lock call
// excludes new readers from acquiring the lock. See the documentation on the
// RWMutex type.
// +checklocksignore
func (rw *CrossGoroutineRWMutex) RLock() {
if RaceEnabled {
RaceDisable()
@@ -83,6 +85,7 @@ func (rw *CrossGoroutineRWMutex) RLock() {
//
// Preconditions:
// * rw is locked for reading.
// +checklocksignore
func (rw *CrossGoroutineRWMutex) RUnlock() {
if RaceEnabled {
RaceReleaseMerge(unsafe.Pointer(&rw.writerSem))
@@ -134,6 +137,7 @@ func (rw *CrossGoroutineRWMutex) TryLock() bool {
// Lock locks rw for writing. If the lock is already locked for reading or
// writing, Lock blocks until the lock is available.
// +checklocksignore
func (rw *CrossGoroutineRWMutex) Lock() {
if RaceEnabled {
RaceDisable()
@@ -228,6 +232,7 @@ type RWMutex struct {
// TryRLock locks rw for reading. It returns true if it succeeds and false
// otherwise. It does not block.
// +checklocksignore
func (rw *RWMutex) TryRLock() bool {
// Note lock first to enforce proper locking even if unsuccessful.
noteLock(unsafe.Pointer(rw))
@@ -243,6 +248,7 @@ func (rw *RWMutex) TryRLock() bool {
// It should not be used for recursive read locking; a blocked Lock call
// excludes new readers from acquiring the lock. See the documentation on the
// RWMutex type.
// +checklocksignore
func (rw *RWMutex) RLock() {
noteLock(unsafe.Pointer(rw))
rw.m.RLock()
@@ -261,6 +267,7 @@ func (rw *RWMutex) RUnlock() {
// TryLock locks rw for writing. It returns true if it succeeds and false
// otherwise. It does not block.
// +checklocksignore
func (rw *RWMutex) TryLock() bool {
// Note lock first to enforce proper locking even if unsuccessful.
noteLock(unsafe.Pointer(rw))
@@ -273,6 +280,7 @@ func (rw *RWMutex) TryLock() bool {
// Lock locks rw for writing. If the lock is already locked for reading or
// writing, Lock blocks until the lock is available.
// +checklocksignore
func (rw *RWMutex) Lock() {
noteLock(unsafe.Pointer(rw))
rw.m.Lock()
+29 -16
View File
@@ -19,6 +19,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"time"
@@ -250,8 +251,8 @@ func (l *TCPListener) Accept() (net.Conn, error) {
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
// Create wait queue entry that notifies a channel.
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
l.wq.EventRegister(&waitEntry, waiter.EventIn)
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
l.wq.EventRegister(&waitEntry)
defer l.wq.EventUnregister(&waitEntry)
for {
@@ -300,8 +301,8 @@ func commonRead(b []byte, ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan s
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
// Create wait queue entry that notifies a channel.
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
wq.EventRegister(&waitEntry, waiter.EventIn)
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
wq.EventRegister(&waitEntry)
defer wq.EventUnregister(&waitEntry)
for {
res, err = ep.Read(&w, opts)
@@ -380,9 +381,8 @@ func (c *TCPConn) Write(b []byte) (int, error) {
case nil:
case *tcpip.ErrWouldBlock:
if ch == nil {
entry, ch = waiter.NewChannelEntry(nil)
c.wq.EventRegister(&entry, waiter.EventOut)
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
@@ -471,9 +471,9 @@ func DialTCP(s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtoc
return DialContextTCP(context.Background(), s, addr, network)
}
// 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) {
// 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)
@@ -484,8 +484,8 @@ func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress,
// Create wait queue entry that notifies a channel.
//
// We do this unconditionally as Connect will always return an error.
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
wq.EventRegister(&waitEntry, waiter.EventOut)
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
wq.EventRegister(&waitEntry)
defer wq.EventUnregister(&waitEntry)
select {
@@ -494,7 +494,14 @@ func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress,
default:
}
err = ep.Connect(addr)
// 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():
@@ -510,7 +517,7 @@ func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress,
return nil, &net.OpError{
Op: "connect",
Net: "tcp",
Addr: fullToTCPAddr(addr),
Addr: fullToTCPAddr(remoteAddr),
Err: errors.New(err.String()),
}
}
@@ -518,6 +525,12 @@ func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress,
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 {
@@ -651,8 +664,8 @@ func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
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(nil)
c.wq.EventRegister(&waitEntry, waiter.EventOut)
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
c.wq.EventRegister(&waitEntry)
defer c.wq.EventUnregister(&waitEntry)
for {
select {
@@ -19,6 +19,7 @@ func (vv *VectorisedView) StateFields() []string {
func (vv *VectorisedView) beforeSave() {}
// +checklocksignore
func (vv *VectorisedView) StateSave(stateSinkObject state.Sink) {
vv.beforeSave()
stateSinkObject.Save(0, &vv.views)
@@ -27,6 +28,7 @@ func (vv *VectorisedView) StateSave(stateSinkObject state.Sink) {
func (vv *VectorisedView) afterLoad() {}
// +checklocksignore
func (vv *VectorisedView) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &vv.views)
stateSourceObject.Load(1, &vv.size)
+10 -10
View File
@@ -42,26 +42,26 @@ func (s *Sum32) Reset() { *s = 0 }
// Sum32 returns the hash value
func (s *Sum32) Sum32() uint32 {
hash := *s
sCopy := *s
hash += (hash << 3)
hash ^= hash >> 11
hash += hash << 15
sCopy += sCopy << 3
sCopy ^= sCopy >> 11
sCopy += sCopy << 15
return uint32(hash)
return uint32(sCopy)
}
// Write adds more data to the running hash.
//
// It never returns an error.
func (s *Sum32) Write(data []byte) (int, error) {
hash := *s
sCopy := *s
for _, b := range data {
hash += Sum32(b)
hash += hash << 10
hash ^= hash >> 6
sCopy += Sum32(b)
sCopy += sCopy << 10
sCopy ^= sCopy >> 6
}
*s = hash
*s = sCopy
return len(data), nil
}
+62
View File
@@ -18,6 +18,7 @@ package header
import (
"encoding/binary"
"fmt"
"inet.af/netstack/tcpip"
"inet.af/netstack/tcpip/buffer"
@@ -234,3 +235,64 @@ func PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, srcAddr tcpip.
return 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)
return ChecksumCombine(xsum, ChecksumCombine(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 len(old) != len(new) {
panic(fmt.Sprintf("buffer lengths are different; old = %d, new = %d", len(old), len(new)))
}
if len(old)%uint16Bytes != 0 {
panic(fmt.Sprintf("buffer has an odd number of bytes; got = %d", len(old)))
}
// 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(old) != 0 {
// Convert the 2 byte sequences to uint16 values then apply the increment
// update.
xsum = checksumUpdate2ByteAlignedUint16(xsum, (uint16(old[0])<<8)+uint16(old[1]), (uint16(new[0])<<8)+uint16(new[1]))
old = old[uint16Bytes:]
new = new[uint16Bytes:]
}
return xsum
}
+3 -3
View File
@@ -49,9 +49,9 @@ const (
// EthernetAddressSize is the size, in bytes, of an ethernet address.
EthernetAddressSize = 6
// unspecifiedEthernetAddress is the unspecified ethernet address
// UnspecifiedEthernetAddress is the unspecified ethernet address
// (all bits set to 0).
unspecifiedEthernetAddress = tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00")
UnspecifiedEthernetAddress = tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00")
// EthernetBroadcastAddress is an ethernet address that addresses every node
// on a local link.
@@ -134,7 +134,7 @@ func IsValidUnicastEthernetAddress(addr tcpip.LinkAddress) bool {
return false
}
if addr == unspecifiedEthernetAddress {
if addr == UnspecifiedEthernetAddress {
return false
}
@@ -19,6 +19,7 @@ func (r *SACKBlock) StateFields() []string {
func (r *SACKBlock) beforeSave() {}
// +checklocksignore
func (r *SACKBlock) StateSave(stateSinkObject state.Sink) {
r.beforeSave()
stateSinkObject.Save(0, &r.Start)
@@ -27,6 +28,7 @@ func (r *SACKBlock) StateSave(stateSinkObject state.Sink) {
func (r *SACKBlock) afterLoad() {}
// +checklocksignore
func (r *SACKBlock) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &r.Start)
stateSourceObject.Load(1, &r.End)
@@ -47,6 +49,7 @@ func (t *TCPOptions) StateFields() []string {
func (t *TCPOptions) beforeSave() {}
// +checklocksignore
func (t *TCPOptions) StateSave(stateSinkObject state.Sink) {
t.beforeSave()
stateSinkObject.Save(0, &t.TS)
@@ -57,6 +60,7 @@ func (t *TCPOptions) StateSave(stateSinkObject state.Sink) {
func (t *TCPOptions) afterLoad() {}
// +checklocksignore
func (t *TCPOptions) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &t.TS)
stateSourceObject.Load(1, &t.TSVal)
+38
View File
@@ -53,6 +53,31 @@ type Transport interface {
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 {
@@ -90,3 +115,16 @@ type Network interface {
// 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)
}
+91 -8
View File
@@ -17,6 +17,7 @@ package header
import (
"encoding/binary"
"fmt"
"time"
"inet.af/netstack/tcpip"
)
@@ -178,6 +179,26 @@ const (
IPv4FlagDontFragment
)
// ipv4LinkLocalUnicastSubnet is the IPv4 link local unicast subnet as defined
// by RFC 3927 section 1.
var ipv4LinkLocalUnicastSubnet = func() tcpip.Subnet {
subnet, err := tcpip.NewSubnet("\xa9\xfe\x00\x00", "\xff\xff\x00\x00")
if err != nil {
panic(err)
}
return subnet
}()
// ipv4LinkLocalMulticastSubnet is the IPv4 link local multicast subnet as
// defined by RFC 5771 section 4.
var ipv4LinkLocalMulticastSubnet = func() tcpip.Subnet {
subnet, err := tcpip.NewSubnet("\xe0\x00\x00\x00", "\xff\xff\xff\x00")
if err != nil {
panic(err)
}
return subnet
}()
// IPv4EmptySubnet is the empty IPv4 subnet.
var IPv4EmptySubnet = func() tcpip.Subnet {
subnet, err := tcpip.NewSubnet(IPv4Any, tcpip.AddressMask(IPv4Any))
@@ -187,6 +208,15 @@ var IPv4EmptySubnet = func() tcpip.Subnet {
return subnet
}()
// IPv4LoopbackSubnet is the loopback subnet for IPv4.
var IPv4LoopbackSubnet = func() tcpip.Subnet {
subnet, err := tcpip.NewSubnet(tcpip.Address("\x7f\x00\x00\x00"), tcpip.AddressMask("\xff\x00\x00\x00"))
if err != nil {
panic(err)
}
return subnet
}()
// IPVersion returns the version of IP used in the given packet. It returns -1
// if the packet is not large enough to contain the version field.
func IPVersion(b []byte) int {
@@ -284,6 +314,18 @@ func (b IPv4) DestinationAddress() tcpip.Address {
return tcpip.Address(b[dstAddr : dstAddr+IPv4AddressSize])
}
// SetSourceAddressWithChecksumUpdate implements ChecksummableNetwork.
func (b IPv4) SetSourceAddressWithChecksumUpdate(new tcpip.Address) {
b.SetChecksum(^checksumUpdate2ByteAlignedAddress(^b.Checksum(), b.SourceAddress(), new))
b.SetSourceAddress(new)
}
// SetDestinationAddressWithChecksumUpdate implements ChecksummableNetwork.
func (b IPv4) SetDestinationAddressWithChecksumUpdate(new tcpip.Address) {
b.SetChecksum(^checksumUpdate2ByteAlignedAddress(^b.Checksum(), b.DestinationAddress(), new))
b.SetDestinationAddress(new)
}
// padIPv4OptionsLength returns the total length for IPv4 options of length l
// after applying padding according to RFC 791:
// The internet header padding is used to ensure that the internet
@@ -423,6 +465,44 @@ func (b IPv4) IsValid(pktSize int) bool {
return true
}
// IsV4LinkLocalUnicastAddress determines if the provided address is an IPv4
// link-local unicast address.
func IsV4LinkLocalUnicastAddress(addr tcpip.Address) bool {
return ipv4LinkLocalUnicastSubnet.Contains(addr)
}
// IsV4LinkLocalMulticastAddress determines if the provided address is an IPv4
// link-local multicast address.
func IsV4LinkLocalMulticastAddress(addr tcpip.Address) bool {
return ipv4LinkLocalMulticastSubnet.Contains(addr)
}
// IsChecksumValid returns true iff the IPv4 header's checksum is valid.
func (b IPv4) IsChecksumValid() bool {
// There has been some confusion regarding verifying checksums. We need
// just look for negative 0 (0xffff) as the checksum, as it's not possible to
// get positive 0 (0) for the checksum. Some bad implementations could get it
// when doing entry replacement in the early days of the Internet,
// however the lore that one needs to check for both persists.
//
// RFC 1624 section 1 describes the source of this confusion as:
// [the partial recalculation method described in RFC 1071] computes a
// result for certain cases that differs from the one obtained from
// scratch (one's complement of one's complement sum of the original
// fields).
//
// However RFC 1624 section 5 clarifies that if using the verification method
// "recommended by RFC 1071, it does not matter if an intermediate system
// generated a -0 instead of +0".
//
// RFC1071 page 1 specifies the verification method as:
// (3) To check a checksum, the 1's complement sum is computed over the
// same set of octets, including the checksum field. If the result
// is all 1 bits (-0 in 1's complement arithmetic), the check
// succeeds.
return b.CalculateChecksum() == 0xffff
}
// IsV4MulticastAddress determines if the provided address is an IPv4 multicast
// address (range 224.0.0.0 to 239.255.255.255). The four most significant bits
// will be 1110 = 0xe0.
@@ -514,7 +594,7 @@ func (o *IPv4OptionGeneric) Type() IPv4OptionType {
func (o *IPv4OptionGeneric) Size() uint8 { return uint8(len(*o)) }
// Contents implements IPv4Option.
func (o *IPv4OptionGeneric) Contents() []byte { return []byte(*o) }
func (o *IPv4OptionGeneric) Contents() []byte { return *o }
// IPv4OptionIterator is an iterator pointing to a specific IP option
// at any point of time. It also holds information as to a new options buffer
@@ -552,7 +632,7 @@ func (i *IPv4OptionIterator) InitReplacement(option IPv4Option) IPv4Options {
// RemainingBuffer returns the remaining (unused) part of the new option buffer,
// into which a new option may be written.
func (i *IPv4OptionIterator) RemainingBuffer() IPv4Options {
return IPv4Options(i.newOptions[i.writePoint:])
return i.newOptions[i.writePoint:]
}
// ConsumeBuffer marks a portion of the new buffer as used.
@@ -755,9 +835,12 @@ const (
// ipv4TimestampTime provides the current time as specified in RFC 791.
func ipv4TimestampTime(clock tcpip.Clock) uint32 {
const millisecondsPerDay = 24 * 3600 * 1000
const nanoPerMilli = 1000000
return uint32((clock.NowNanoseconds() / nanoPerMilli) % millisecondsPerDay)
// Per RFC 791 page 21:
// The Timestamp is a right-justified, 32-bit timestamp in
// milliseconds since midnight UT.
now := clock.Now().UTC()
midnight := now.Truncate(24 * time.Hour)
return uint32(now.Sub(midnight).Milliseconds())
}
// IP Timestamp option fields.
@@ -785,7 +868,7 @@ func (ts *IPv4OptionTimestamp) Type() IPv4OptionType { return IPv4OptionTimestam
func (ts *IPv4OptionTimestamp) Size() uint8 { return uint8(len(*ts)) }
// Contents implements IPv4Option.
func (ts *IPv4OptionTimestamp) Contents() []byte { return []byte(*ts) }
func (ts *IPv4OptionTimestamp) Contents() []byte { return *ts }
// Pointer returns the pointer field in the IP Timestamp option.
func (ts *IPv4OptionTimestamp) Pointer() uint8 {
@@ -889,7 +972,7 @@ func (rr *IPv4OptionRecordRoute) Type() IPv4OptionType { return IPv4OptionRecord
func (rr *IPv4OptionRecordRoute) Size() uint8 { return uint8(len(*rr)) }
// Contents implements IPv4Option.
func (rr *IPv4OptionRecordRoute) Contents() []byte { return []byte(*rr) }
func (rr *IPv4OptionRecordRoute) Contents() []byte { return *rr }
// Router Alert option specific related constants.
//
@@ -934,7 +1017,7 @@ func (*IPv4OptionRouterAlert) Type() IPv4OptionType { return IPv4OptionRouterAle
func (ra *IPv4OptionRouterAlert) Size() uint8 { return uint8(len(*ra)) }
// Contents implements IPv4Option.
func (ra *IPv4OptionRouterAlert) Contents() []byte { return []byte(*ra) }
func (ra *IPv4OptionRouterAlert) Contents() []byte { return *ra }
// Value returns the value of the IPv4OptionRouterAlert.
func (ra *IPv4OptionRouterAlert) Value() uint16 {
+70 -17
View File
@@ -98,12 +98,27 @@ const (
// The address is ff02::1.
IPv6AllNodesMulticastAddress tcpip.Address = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
// IPv6AllRoutersMulticastAddress is a link-local multicast group that
// all IPv6 routers MUST join, as per RFC 4291, section 2.8. Packets
// 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.Address = "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
// 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.
IPv6AllRoutersMulticastAddress tcpip.Address = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
IPv6AllRoutersLinkLocalMulticastAddress tcpip.Address = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
// 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.Address = "\xff\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
// IPv6MinimumMTU is the minimum MTU required by IPv6, per RFC 8200,
// section 5:
@@ -142,11 +157,6 @@ const (
// ipv6MulticastAddressScopeMask is the mask for the scope (scop) field,
// within the byte holding the field, as per RFC 4291 section 2.7.
ipv6MulticastAddressScopeMask = 0xF
// ipv6LinkLocalMulticastScope is the value of the scope (scop) field within
// a multicast IPv6 address that indicates the address has link-local scope,
// as per RFC 4291 section 2.7.
ipv6LinkLocalMulticastScope = 2
)
// IPv6EmptySubnet is the empty IPv6 subnet. It may also be known as the
@@ -381,25 +391,25 @@ func LinkLocalAddr(linkAddr tcpip.LinkAddress) tcpip.Address {
return tcpip.Address(lladdrb[:])
}
// IsV6LinkLocalAddress determines if the provided address is an IPv6
// link-local address (fe80::/10).
func IsV6LinkLocalAddress(addr tcpip.Address) bool {
// 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 len(addr) != IPv6AddressSize {
return false
}
return addr[0] == 0xfe && (addr[1]&0xc0) == 0x80
}
// IsV6LoopbackAddress determines if the provided address is an IPv6 loopback
// address.
// 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 determines if the provided address is an IPv6
// link-local multicast address.
// 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) && addr[ipv6MulticastAddressScopeByteIdx]&ipv6MulticastAddressScopeMask == ipv6LinkLocalMulticastScope
return IsV6MulticastAddress(addr) && V6MulticastScope(addr) == IPv6LinkLocalMulticastScope
}
// AppendOpaqueInterfaceIdentifier appends a 64 bit opaque interface identifier
@@ -462,7 +472,7 @@ func ScopeForIPv6Address(addr tcpip.Address) (IPv6AddressScope, tcpip.Error) {
case IsV6LinkLocalMulticastAddress(addr):
return LinkLocalScope, nil
case IsV6LinkLocalAddress(addr):
case IsV6LinkLocalUnicastAddress(addr):
return LinkLocalScope, nil
default:
@@ -520,3 +530,46 @@ func GenerateTempIPv6SLAACAddr(tempIIDHistory []byte, stableAddr tcpip.Address)
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 {
return IPv6MulticastScope(addr[ipv6MulticastAddressScopeByteIdx] & ipv6MulticastAddressScopeMask)
}
+148 -8
View File
@@ -77,12 +77,12 @@ const (
// ndpPrefixInformationOnLinkFlagMask is the mask of the On-Link Flag
// field in the flags byte within an NDPPrefixInformation.
ndpPrefixInformationOnLinkFlagMask = (1 << 7)
ndpPrefixInformationOnLinkFlagMask = 1 << 7
// ndpPrefixInformationAutoAddrConfFlagMask is the mask of the
// Autonomous Address-Configuration flag field in the flags byte within
// an NDPPrefixInformation.
ndpPrefixInformationAutoAddrConfFlagMask = (1 << 6)
ndpPrefixInformationAutoAddrConfFlagMask = 1 << 6
// ndpPrefixInformationReserved1FlagsMask is the mask of the Reserved1
// field in the flags byte within an NDPPrefixInformation.
@@ -148,15 +148,10 @@ const (
// NDP option. That is, the length field for NDP options is in units of
// 8 octets, as per RFC 4861 section 4.6.
lengthByteUnits = 8
)
var (
// NDPInfiniteLifetime is a value that represents infinity for the
// 4-byte lifetime fields found in various NDP options. Its value is
// (2^32 - 1)s = 4294967295s.
//
// This is a variable instead of a constant so that tests can change
// this value to a smaller value. It should only be modified by tests.
NDPInfiniteLifetime = time.Second * math.MaxUint32
)
@@ -238,6 +233,17 @@ func (i *NDPOptionIterator) Next() (NDPOption, bool, error) {
case ndpNonceOptionType:
return NDPNonceOption(body), false, nil
case ndpRouteInformationType:
if numBodyBytes > ndpRouteInformationMaxLength {
return nil, true, fmt.Errorf("got %d bytes for NDP Route Information option's body, expected at max %d bytes: %w", numBodyBytes, ndpRouteInformationMaxLength, ErrNDPOptMalformedBody)
}
opt := NDPRouteInformation(body)
if err := opt.hasError(); err != nil {
return nil, true, err
}
return opt, false, nil
case ndpPrefixInformationType:
// Make sure the length of a Prefix Information option
// body is ndpPrefixInformationLength, as per RFC 4861
@@ -451,7 +457,7 @@ func (o NDPNonceOption) String() string {
// Nonce returns the nonce value this option holds.
func (o NDPNonceOption) Nonce() []byte {
return []byte(o)
return o
}
// NDPSourceLinkLayerAddressOption is the NDP Source Link Layer Option
@@ -935,3 +941,137 @@ func isUpperLetter(b byte) bool {
func isDigit(b byte) bool {
return b >= '0' && b <= '9'
}
// As per RFC 4191 section 2.3,
//
// 2.3. Route Information Option
//
// 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 | Length | Prefix Length |Resvd|Prf|Resvd|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Route Lifetime |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Prefix (Variable Length) |
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Fields:
//
// Type 24
//
//
// Length 8-bit unsigned integer. The length of the option
// (including the Type and Length fields) in units of 8
// octets. The Length field is 1, 2, or 3 depending on the
// Prefix Length. If Prefix Length is greater than 64, then
// Length must be 3. If Prefix Length is greater than 0,
// then Length must be 2 or 3. If Prefix Length is zero,
// then Length must be 1, 2, or 3.
const (
ndpRouteInformationType = ndpOptionIdentifier(24)
ndpRouteInformationMaxLength = 22
ndpRouteInformationPrefixLengthIdx = 0
ndpRouteInformationFlagsIdx = 1
ndpRouteInformationPrfShift = 3
ndpRouteInformationPrfMask = 3 << ndpRouteInformationPrfShift
ndpRouteInformationRouteLifetimeIdx = 2
ndpRouteInformationRoutePrefixIdx = 6
)
// NDPRouteInformation is the NDP Router Information option, as defined by
// RFC 4191 section 2.3.
type NDPRouteInformation []byte
func (NDPRouteInformation) kind() ndpOptionIdentifier {
return ndpRouteInformationType
}
func (o NDPRouteInformation) length() int {
return len(o)
}
func (o NDPRouteInformation) serializeInto(b []byte) int {
return copy(b, o)
}
// String implements fmt.Stringer.
func (o NDPRouteInformation) String() string {
return fmt.Sprintf("%T", o)
}
// PrefixLength returns the length of the prefix.
func (o NDPRouteInformation) PrefixLength() uint8 {
return o[ndpRouteInformationPrefixLengthIdx]
}
// RoutePreference returns the preference of the route over other routes to the
// same destination but through a different router.
func (o NDPRouteInformation) RoutePreference() NDPRoutePreference {
return NDPRoutePreference((o[ndpRouteInformationFlagsIdx] & ndpRouteInformationPrfMask) >> ndpRouteInformationPrfShift)
}
// RouteLifetime returns the lifetime of the route.
//
// Note, a value of 0 implies the route is now invalid and a value of
// infinity/forever is represented by NDPInfiniteLifetime.
func (o NDPRouteInformation) RouteLifetime() time.Duration {
return time.Second * time.Duration(binary.BigEndian.Uint32(o[ndpRouteInformationRouteLifetimeIdx:]))
}
// Prefix returns the prefix of the destination subnet this route is for.
func (o NDPRouteInformation) Prefix() (tcpip.Subnet, error) {
prefixLength := int(o.PrefixLength())
if max := IPv6AddressSize * 8; prefixLength > max {
return tcpip.Subnet{}, fmt.Errorf("got prefix length = %d, want <= %d", prefixLength, max)
}
prefix := o[ndpRouteInformationRoutePrefixIdx:]
var addrBytes [IPv6AddressSize]byte
if n := copy(addrBytes[:], prefix); n != len(prefix) {
panic(fmt.Sprintf("got copy(addrBytes, prefix) = %d, want = %d", n, len(prefix)))
}
return tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes[:]),
PrefixLen: prefixLength,
}.Subnet(), nil
}
func (o NDPRouteInformation) hasError() error {
l := len(o)
if l < ndpRouteInformationRoutePrefixIdx {
return fmt.Errorf("%T too small, got = %d bytes: %w", o, l, ErrNDPOptMalformedBody)
}
prefixLength := int(o.PrefixLength())
if max := IPv6AddressSize * 8; prefixLength > max {
return fmt.Errorf("got prefix length = %d, want <= %d: %w", prefixLength, max, ErrNDPOptMalformedBody)
}
// Length 8-bit unsigned integer. The length of the option
// (including the Type and Length fields) in units of 8
// octets. The Length field is 1, 2, or 3 depending on the
// Prefix Length. If Prefix Length is greater than 64, then
// Length must be 3. If Prefix Length is greater than 0,
// then Length must be 2 or 3. If Prefix Length is zero,
// then Length must be 1, 2, or 3.
l += 2 // Add 2 bytes for the type and length bytes.
lengthField := l / lengthByteUnits
if prefixLength > 64 {
if lengthField != 3 {
return fmt.Errorf("Length field must be 3 when Prefix Length (%d) is > 64 (got = %d): %w", prefixLength, lengthField, ErrNDPOptMalformedBody)
}
} else if prefixLength > 0 {
if lengthField != 2 && lengthField != 3 {
return fmt.Errorf("Length field must be 2 or 3 when Prefix Length (%d) is between 0 and 64 (got = %d): %w", prefixLength, lengthField, ErrNDPOptMalformedBody)
}
} else if lengthField == 0 || lengthField > 3 {
return fmt.Errorf("Length field must be 1, 2, or 3 when Prefix Length is zero (got = %d): %w", lengthField, ErrNDPOptMalformedBody)
}
return nil
}
+93 -1
View File
@@ -16,15 +16,94 @@ 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 for more details.
// 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).
@@ -47,6 +126,14 @@ const (
// 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
@@ -80,6 +167,11 @@ 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
+73 -1
View File
@@ -60,9 +60,13 @@ func IPv4(pkt *stack.PacketBuffer) bool {
return false
}
ipHdr = header.IPv4(hdr)
length := int(ipHdr.TotalLength()) - len(hdr)
if length < 0 {
return false
}
pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
pkt.Data().CapLength(int(ipHdr.TotalLength()) - len(hdr))
pkt.Data().CapLength(length)
return true
}
@@ -106,6 +110,16 @@ traverseExtensions:
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()
@@ -171,3 +185,61 @@ func TCP(pkt *stack.PacketBuffer) bool {
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:
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.ICMPv6MulticastListenerQuery,
header.ICMPv6MulticastListenerReport,
header.ICMPv6MulticastListenerDone:
size := header.ICMPv6HeaderSize + header.MLDMinimumSize
if _, ok := pkt.TransportHeader().Consume(size); !ok {
return false
}
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
}
+69 -23
View File
@@ -48,6 +48,16 @@ const (
// 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("FSRPAU")
@@ -216,104 +226,104 @@ const (
TCPDefaultMSS = 536
)
// SourcePort returns the "source port" field of the tcp header.
// 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.
// 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.
// 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.
// 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
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// SetChecksum sets the checksum field of the TCP header.
func (b TCP) SetChecksum(checksum uint16) {
binary.BigEndian.PutUint16(b[TCPChecksumOffset:], checksum)
}
// SetDataOffset sets the data offset field of the tcp header. headerLen should
// 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.
// 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.
// 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.
// 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.
// SetWindowSize sets the window size field of the TCP header.
func (b TCP) SetWindowSize(rcvwnd uint16) {
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
}
// SetUrgentPoiner sets the window size field of the tcp header.
// SetUrgentPoiner sets the window size field of the TCP header.
func (b TCP) SetUrgentPoiner(urgentPointer uint16) {
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], urgentPointer)
}
// CalculateChecksum calculates the checksum of the tcp segment.
// 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 {
@@ -321,6 +331,13 @@ func (b TCP) CalculateChecksum(partialChecksum uint16) uint16 {
return Checksum(b[:b.DataOffset()], partialChecksum)
}
// 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 = ChecksumCombine(xsum, payloadChecksum)
return b.CalculateChecksum(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()]
@@ -340,7 +357,7 @@ func (b TCP) encodeSubset(seq, ack uint32, flags TCPFlags, rcvwnd uint16) {
binary.BigEndian.PutUint16(b[TCPWinSizeOffset:], rcvwnd)
}
// Encode encodes all the fields of the tcp header.
// 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)
binary.BigEndian.PutUint16(b[TCPSrcPortOffset:], t.SrcPort)
@@ -350,7 +367,7 @@ func (b TCP) Encode(t *TCPFields) {
binary.BigEndian.PutUint16(b[TCPUrgentPtrOffset:], t.UrgentPointer)
}
// EncodePartial updates a subset of the fields of the tcp header. It is useful
// 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.
@@ -373,8 +390,37 @@ func (b TCP) EncodePartial(partialChecksum, length uint16, seqnum, acknum uint32
b.SetChecksum(^checksum)
}
// 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.
// relevant ones. opts should point to the option part of the TCP header.
func ParseSynOptions(opts []byte, isAck bool) TCPSynOptions {
limit := len(opts)
+46 -10
View File
@@ -64,17 +64,17 @@ const (
UDPProtocolNumber tcpip.TransportProtocolNumber = 17
)
// SourcePort returns the "source port" field of the udp header.
// 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.
// 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.
// Length returns the "length" field of the UDP header.
func (b UDP) Length() uint16 {
return binary.BigEndian.Uint16(b[udpLength:])
}
@@ -84,42 +84,78 @@ func (b UDP) Payload() []byte {
return b[UDPMinimumSize:]
}
// Checksum returns the "checksum" field of the udp header.
// 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.
// 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.
// 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.
// SetChecksum sets the "checksum" field of the UDP header.
func (b UDP) SetChecksum(checksum uint16) {
binary.BigEndian.PutUint16(b[udpChecksum:], checksum)
}
// SetLength sets the "length" field of the udp header.
// 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
// 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(b[:UDPMinimumSize], partialChecksum)
}
// Encode encodes all the fields of the udp header.
// 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 = ChecksumCombine(xsum, payloadChecksum)
return b.CalculateChecksum(xsum) == 0xffff
}
// Encode encodes all the fields of the UDP header.
func (b UDP) Encode(u *UDPFields) {
binary.BigEndian.PutUint16(b[udpSrcPort:], u.SrcPort)
binary.BigEndian.PutUint16(b[udpDstPort:], u.DstPort)
binary.BigEndian.PutUint16(b[udpLength:], u.Length)
binary.BigEndian.PutUint16(b[udpChecksum:], 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)
}
+48
View 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"
"inet.af/netstack/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
}
@@ -0,0 +1,36 @@
// automatically generated by stateify.
package tcp
import (
"inet.af/netstack/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() {}
// +checklocksignore
func (offset *TSOffset) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &offset.milliseconds)
}
func init() {
state.Register((*TSOffset)(nil))
}
@@ -251,7 +251,7 @@ func (f *Fragmentation) releaseReassemblersLocked() {
// The list is empty.
break
}
elapsed := time.Duration(now-r.creationTime) * time.Nanosecond
elapsed := now.Sub(r.createdAt)
if f.timeout > elapsed {
// If the oldest reassembler has not expired, schedule the release
// job so that this function is called back when it has expired.
@@ -19,6 +19,7 @@ func (l *reassemblerList) StateFields() []string {
func (l *reassemblerList) beforeSave() {}
// +checklocksignore
func (l *reassemblerList) StateSave(stateSinkObject state.Sink) {
l.beforeSave()
stateSinkObject.Save(0, &l.head)
@@ -27,6 +28,7 @@ func (l *reassemblerList) StateSave(stateSinkObject state.Sink) {
func (l *reassemblerList) afterLoad() {}
// +checklocksignore
func (l *reassemblerList) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &l.head)
stateSourceObject.Load(1, &l.tail)
@@ -45,6 +47,7 @@ func (e *reassemblerEntry) StateFields() []string {
func (e *reassemblerEntry) beforeSave() {}
// +checklocksignore
func (e *reassemblerEntry) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
stateSinkObject.Save(0, &e.next)
@@ -53,6 +56,7 @@ func (e *reassemblerEntry) StateSave(stateSinkObject state.Sink) {
func (e *reassemblerEntry) afterLoad() {}
// +checklocksignore
func (e *reassemblerEntry) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &e.next)
stateSourceObject.Load(1, &e.prev)
@@ -35,21 +35,21 @@ type hole struct {
type reassembler struct {
reassemblerEntry
id FragmentID
memSize int
proto uint8
mu sync.Mutex
holes []hole
filled int
done bool
creationTime int64
pkt *stack.PacketBuffer
id FragmentID
memSize int
proto uint8
mu sync.Mutex
holes []hole
filled int
done bool
createdAt tcpip.MonotonicTime
pkt *stack.PacketBuffer
}
func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
r := &reassembler{
id: id,
creationTime: clock.NowMonotonic(),
id: id,
createdAt: clock.NowMonotonic(),
}
r.holes = append(r.holes, hole{
first: 0,
@@ -149,6 +149,7 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
r.proto = proto
}
pkt.IncRef()
break
}
if !holeFound {
@@ -166,9 +167,9 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
})
resPkt := r.holes[0].pkt
resPkt.DecRef()
for i := 1; i < len(r.holes); i++ {
fragData := r.holes[i].pkt.Data()
resPkt.Data().ReadFromData(fragData, fragData.Size())
stack.MergeFragment(resPkt, r.holes[i].pkt)
}
return resPkt, r.proto, true, memConsumed, nil
}
@@ -83,6 +83,8 @@ func (d *DAD) Init(protocolMU sync.Locker, configs stack.DADConfigurations, opts
panic(fmt.Sprintf("given a non-zero value for NonceSize (%d) but zero for ExtendDADTransmits", opts.NonceSize))
}
configs.Validate()
*d = DAD{
opts: opts,
configs: configs,
@@ -0,0 +1,85 @@
// 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 ip
import (
"fmt"
"inet.af/netstack/tcpip"
)
// ForwardingError represents an error that occured while trying to forward
// a packet.
type ForwardingError interface {
isForwardingError()
fmt.Stringer
}
// ErrTTLExceeded indicates that the received packet's TTL has been exceeded.
type ErrTTLExceeded struct{}
func (*ErrTTLExceeded) isForwardingError() {}
func (*ErrTTLExceeded) String() string { return "ttl exceeded" }
// ErrParameterProblem indicates the received packet had a problem with an IP
// parameter.
type ErrParameterProblem struct{}
func (*ErrParameterProblem) isForwardingError() {}
func (*ErrParameterProblem) String() string { return "parameter problem" }
// ErrLinkLocalSourceAddress indicates the received packet had a link-local
// source address.
type ErrLinkLocalSourceAddress struct{}
func (*ErrLinkLocalSourceAddress) isForwardingError() {}
func (*ErrLinkLocalSourceAddress) String() string { return "link local destination address" }
// ErrLinkLocalDestinationAddress indicates the received packet had a link-local
// destination address.
type ErrLinkLocalDestinationAddress struct{}
func (*ErrLinkLocalDestinationAddress) isForwardingError() {}
func (*ErrLinkLocalDestinationAddress) String() string { return "link local destination address" }
// ErrNoRoute indicates that a route for the received packet couldn't be found.
type ErrNoRoute struct{}
func (*ErrNoRoute) isForwardingError() {}
func (*ErrNoRoute) String() string { return "no route" }
// ErrMessageTooLong indicates the packet was too big for the outgoing MTU.
//
// +stateify savable
type ErrMessageTooLong struct{}
func (*ErrMessageTooLong) isForwardingError() {}
func (*ErrMessageTooLong) String() string { return "message too long" }
// ErrOther indicates the packet coould not be forwarded for a reason
// captured by the contained error.
type ErrOther struct {
Err tcpip.Error
}
func (*ErrOther) isForwardingError() {}
func (e *ErrOther) String() string { return fmt.Sprintf("other tcpip error: %s", e.Err) }
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package ip holds IPv4/IPv6 common utilities.
package ip
import (
@@ -156,14 +155,6 @@ type GenericMulticastProtocolOptions struct {
//
// Unsolicited reports are transmitted when a group is newly joined.
MaxUnsolicitedReportDelay time.Duration
// AllNodesAddress is a multicast address that all nodes on a network should
// be a member of.
//
// This address will not have the generic multicast protocol performed on it;
// it will be left in the non member/listener state, and packets will never
// be sent for it.
AllNodesAddress tcpip.Address
}
// MulticastGroupProtocol is a multicast group protocol whose core state machine
@@ -188,6 +179,10 @@ type MulticastGroupProtocol interface {
// SendLeave sends a multicast leave for the specified group address.
SendLeave(groupAddress tcpip.Address) tcpip.Error
// ShouldPerformProtocol returns true iff the protocol should be performed for
// the specified group.
ShouldPerformProtocol(tcpip.Address) bool
}
// GenericMulticastProtocolState is the per interface generic multicast protocol
@@ -455,20 +450,7 @@ func (g *GenericMulticastProtocolState) initializeNewMemberLocked(groupAddress t
info.lastToSendReport = false
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// The all-systems group (address 224.0.0.1) is handled as a special
// case. The host starts in Idle Member state for that group on every
// interface, never transitions to another state, and never sends a
// report for that group.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// The link-scope all-nodes address (FF02::1) is handled as a special
// case. The node starts in Idle Listener state for that address on
// every interface, never transitions to another state, and never sends
// a Report or Done for that address.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
info.state = idleMember
return
}
@@ -537,20 +519,7 @@ func (g *GenericMulticastProtocolState) maybeSendLeave(groupAddress tcpip.Addres
return
}
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// The all-systems group (address 224.0.0.1) is handled as a special
// case. The host starts in Idle Member state for that group on every
// interface, never transitions to another state, and never sends a
// report for that group.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// The link-scope all-nodes address (FF02::1) is handled as a special
// case. The node starts in Idle Listener state for that address on
// every interface, never transitions to another state, and never sends
// a Report or Done for that address.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
return
}
@@ -627,20 +596,7 @@ func (g *GenericMulticastProtocolState) setDelayTimerForAddressRLocked(groupAddr
return
}
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// The all-systems group (address 224.0.0.1) is handled as a special
// case. The host starts in Idle Member state for that group on every
// interface, never transitions to another state, and never sends a
// report for that group.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// The link-scope all-nodes address (FF02::1) is handled as a special
// case. The node starts in Idle Listener state for that address on
// every interface, never transitions to another state, and never sends
// a Report or Done for that address.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
return
}
@@ -655,7 +611,7 @@ func (g *GenericMulticastProtocolState) setDelayTimerForAddressRLocked(groupAddr
// If a timer for any address is already running, it is reset to the new
// random value only if the requested Maximum Response Delay is less than
// the remaining value of the running timer.
now := time.Unix(0 /* seconds */, g.opts.Clock.NowNanoseconds())
now := g.opts.Clock.Now()
if info.state == delayingMember {
if info.delayedReportJobFiresAt.IsZero() {
panic(fmt.Sprintf("delayed report unscheduled while in the delaying member state; group = %s", groupAddress))
@@ -1,3 +1,32 @@
// automatically generated by stateify.
package ip
import (
"inet.af/netstack/state"
)
func (e *ErrMessageTooLong) StateTypeName() string {
return "pkg/tcpip/network/internal/ip.ErrMessageTooLong"
}
func (e *ErrMessageTooLong) StateFields() []string {
return []string{}
}
func (e *ErrMessageTooLong) beforeSave() {}
// +checklocksignore
func (e *ErrMessageTooLong) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
}
func (e *ErrMessageTooLong) afterLoad() {}
// +checklocksignore
func (e *ErrMessageTooLong) StateLoad(stateSourceObject state.Source) {
}
func init() {
state.Register((*ErrMessageTooLong)(nil))
}
+96 -20
View File
@@ -16,77 +16,150 @@ package ip
import "inet.af/netstack/tcpip"
// LINT.IfChange(MultiCounterIPForwardingStats)
// MultiCounterIPForwardingStats holds IP forwarding statistics. Each counter
// may have several versions.
type MultiCounterIPForwardingStats struct {
// Unrouteable is the number of IP packets received which were dropped
// because the netstack could not construct a route to their
// destination.
Unrouteable tcpip.MultiCounterStat
// ExhaustedTTL is the number of IP packets received which were dropped
// because their TTL was exhausted.
ExhaustedTTL tcpip.MultiCounterStat
// LinkLocalSource is the number of IP packets which were dropped
// because they contained a link-local source address.
LinkLocalSource tcpip.MultiCounterStat
// LinkLocalDestination is the number of IP packets which were dropped
// because they contained a link-local destination address.
LinkLocalDestination tcpip.MultiCounterStat
// PacketTooBig is the number of IP packets which were dropped because they
// were too big for the outgoing MTU.
PacketTooBig tcpip.MultiCounterStat
// HostUnreachable is the number of IP packets received which could not be
// successfully forwarded due to an unresolvable next hop.
HostUnreachable tcpip.MultiCounterStat
// ExtensionHeaderProblem is the number of IP packets which were dropped
// because of a problem encountered when processing an IPv6 extension
// header.
ExtensionHeaderProblem tcpip.MultiCounterStat
// Errors is the number of IP packets received which could not be
// successfully forwarded.
Errors tcpip.MultiCounterStat
}
// Init sets internal counters to track a and b counters.
func (m *MultiCounterIPForwardingStats) Init(a, b *tcpip.IPForwardingStats) {
m.Unrouteable.Init(a.Unrouteable, b.Unrouteable)
m.Errors.Init(a.Errors, b.Errors)
m.LinkLocalSource.Init(a.LinkLocalSource, b.LinkLocalSource)
m.LinkLocalDestination.Init(a.LinkLocalDestination, b.LinkLocalDestination)
m.ExtensionHeaderProblem.Init(a.ExtensionHeaderProblem, b.ExtensionHeaderProblem)
m.PacketTooBig.Init(a.PacketTooBig, b.PacketTooBig)
m.ExhaustedTTL.Init(a.ExhaustedTTL, b.ExhaustedTTL)
m.HostUnreachable.Init(a.HostUnreachable, b.HostUnreachable)
}
// LINT.ThenChange(:MultiCounterIPForwardingStats, ../../../tcpip.go:IPForwardingStats)
// LINT.IfChange(MultiCounterIPStats)
// MultiCounterIPStats holds IP statistics, each counter may have several
// versions.
type MultiCounterIPStats struct {
// PacketsReceived is the total number of IP packets received from the link
// PacketsReceived is the number of IP packets received from the link
// layer.
PacketsReceived tcpip.MultiCounterStat
// DisabledPacketsReceived is the total number of IP packets received from the
// link layer when the IP layer is disabled.
// ValidPacketsReceived is the number of valid IP packets that reached the IP
// layer.
ValidPacketsReceived tcpip.MultiCounterStat
// DisabledPacketsReceived is the number of IP packets received from
// the link layer when the IP layer is disabled.
DisabledPacketsReceived tcpip.MultiCounterStat
// InvalidDestinationAddressesReceived is the total number of IP packets
// InvalidDestinationAddressesReceived is the number of IP packets
// received with an unknown or invalid destination address.
InvalidDestinationAddressesReceived tcpip.MultiCounterStat
// InvalidSourceAddressesReceived is the total number of IP packets received
// with a source address that should never have been received on the wire.
// InvalidSourceAddressesReceived is the number of IP packets received
// with a source address that should never have been received on the
// wire.
InvalidSourceAddressesReceived tcpip.MultiCounterStat
// PacketsDelivered is the total number of incoming IP packets that are
// successfully delivered to the transport layer.
// PacketsDelivered is the number of incoming IP packets successfully
// delivered to the transport layer.
PacketsDelivered tcpip.MultiCounterStat
// PacketsSent is the total number of IP packets sent via WritePacket.
// PacketsSent is the number of IP packets sent via WritePacket.
PacketsSent tcpip.MultiCounterStat
// OutgoingPacketErrors is the total number of IP packets which failed to
// OutgoingPacketErrors is the number of IP packets which failed to
// write to a link-layer endpoint.
OutgoingPacketErrors tcpip.MultiCounterStat
// MalformedPacketsReceived is the total number of IP Packets that were
// MalformedPacketsReceived is the number of IP Packets that were
// dropped due to the IP packet header failing validation checks.
MalformedPacketsReceived tcpip.MultiCounterStat
// MalformedFragmentsReceived is the total number of IP Fragments that were
// MalformedFragmentsReceived is the number of IP Fragments that were
// dropped due to the fragment failing validation checks.
MalformedFragmentsReceived tcpip.MultiCounterStat
// IPTablesPreroutingDropped is the total number of IP packets dropped in the
// IPTablesPreroutingDropped is the number of IP packets dropped in the
// Prerouting chain.
IPTablesPreroutingDropped tcpip.MultiCounterStat
// IPTablesInputDropped is the total number of IP packets dropped in the Input
// chain.
// IPTablesInputDropped is the number of IP packets dropped in the
// Input chain.
IPTablesInputDropped tcpip.MultiCounterStat
// IPTablesOutputDropped is the total number of IP packets dropped in the
// IPTablesForwardDropped is the number of IP packets dropped in the
// Forward chain.
IPTablesForwardDropped tcpip.MultiCounterStat
// IPTablesOutputDropped is the number of IP packets dropped in the
// Output chain.
IPTablesOutputDropped tcpip.MultiCounterStat
// TODO(https://gvisor.dev/issues/5529): Move the IPv4-only option stats out
// of IPStats.
// IPTablesPostroutingDropped is the number of IP packets dropped in
// the Postrouting chain.
IPTablesPostroutingDropped tcpip.MultiCounterStat
// TODO(https://gvisor.dev/issues/5529): Move the IPv4-only option
// stats out of IPStats.
// OptionTimestampReceived is the number of Timestamp options seen.
OptionTimestampReceived tcpip.MultiCounterStat
// OptionRecordRouteReceived is the number of Record Route options seen.
// OptionRecordRouteReceived is the number of Record Route options
// seen.
OptionRecordRouteReceived tcpip.MultiCounterStat
// OptionRouterAlertReceived is the number of Router Alert options seen.
// OptionRouterAlertReceived is the number of Router Alert options
// seen.
OptionRouterAlertReceived tcpip.MultiCounterStat
// OptionUnknownReceived is the number of unknown IP options seen.
OptionUnknownReceived tcpip.MultiCounterStat
// Forwarding collects stats related to IP forwarding.
Forwarding MultiCounterIPForwardingStats
}
// Init sets internal counters to track a and b counters.
func (m *MultiCounterIPStats) Init(a, b *tcpip.IPStats) {
m.PacketsReceived.Init(a.PacketsReceived, b.PacketsReceived)
m.ValidPacketsReceived.Init(a.ValidPacketsReceived, b.ValidPacketsReceived)
m.DisabledPacketsReceived.Init(a.DisabledPacketsReceived, b.DisabledPacketsReceived)
m.InvalidDestinationAddressesReceived.Init(a.InvalidDestinationAddressesReceived, b.InvalidDestinationAddressesReceived)
m.InvalidSourceAddressesReceived.Init(a.InvalidSourceAddressesReceived, b.InvalidSourceAddressesReceived)
@@ -97,11 +170,14 @@ func (m *MultiCounterIPStats) Init(a, b *tcpip.IPStats) {
m.MalformedFragmentsReceived.Init(a.MalformedFragmentsReceived, b.MalformedFragmentsReceived)
m.IPTablesPreroutingDropped.Init(a.IPTablesPreroutingDropped, b.IPTablesPreroutingDropped)
m.IPTablesInputDropped.Init(a.IPTablesInputDropped, b.IPTablesInputDropped)
m.IPTablesForwardDropped.Init(a.IPTablesForwardDropped, b.IPTablesForwardDropped)
m.IPTablesOutputDropped.Init(a.IPTablesOutputDropped, b.IPTablesOutputDropped)
m.IPTablesPostroutingDropped.Init(a.IPTablesPostroutingDropped, b.IPTablesPostroutingDropped)
m.OptionTimestampReceived.Init(a.OptionTimestampReceived, b.OptionTimestampReceived)
m.OptionRecordRouteReceived.Init(a.OptionRecordRouteReceived, b.OptionRecordRouteReceived)
m.OptionRouterAlertReceived.Init(a.OptionRouterAlertReceived, b.OptionRouterAlertReceived)
m.OptionUnknownReceived.Init(a.OptionUnknownReceived, b.OptionUnknownReceived)
m.Forwarding.Init(&a.Forwarding, &b.Forwarding)
}
// LINT.ThenChange(:MultiCounterIPStats, ../../../tcpip.go:IPStats)
+123 -74
View File
@@ -163,26 +163,26 @@ func (e *endpoint) handleControl(errInfo stack.TransportError, pkt *stack.Packet
return
}
// Skip the ip header, then deliver the error.
pkt.Data().TrimFront(hlen)
// Keep needed information before trimming header.
p := hdr.TransportProtocol()
e.dispatcher.DeliverTransportError(srcAddr, hdr.DestinationAddress(), ProtocolNumber, p, errInfo, pkt)
dstAddr := hdr.DestinationAddress()
// Skip the ip header, then deliver the error.
if _, ok := pkt.Data().Consume(hlen); !ok {
panic(fmt.Sprintf("could not consume the IP header of %d bytes", hlen))
}
e.dispatcher.DeliverTransportError(srcAddr, dstAddr, ProtocolNumber, p, errInfo, pkt)
}
func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
received := e.stats.icmp.packetsReceived
// TODO(gvisor.dev/issue/170): ICMP packets don't have their
// TransportHeader fields set. See icmp/protocol.go:protocol.Parse for a
// full explanation.
v, ok := pkt.Data().PullUp(header.ICMPv4MinimumSize)
if !ok {
h := header.ICMPv4(pkt.TransportHeader().View())
if len(h) < header.ICMPv4MinimumSize {
received.invalid.Increment()
return
}
h := header.ICMPv4(v)
// Only do in-stack processing if the checksum is correct.
if pkt.Data().AsRange().Checksum() != 0xffff {
if header.Checksum(h, pkt.Data().AsRange().Checksum()) != 0xffff {
received.invalid.Increment()
// It's possible that a raw socket expects to receive this regardless
// of checksum errors. If it's an echo request we know it's safe because
@@ -220,7 +220,6 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
_ = e.protocol.returnError(&icmpReasonParamProblem{
pointer: optProblem.Pointer,
}, pkt)
e.protocol.stack.Stats().MalformedRcvdPackets.Increment()
e.stats.ip.MalformedPacketsReceived.Increment()
}
return
@@ -240,20 +239,15 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
case header.ICMPv4Echo:
received.echoRequest.Increment()
sent := e.stats.icmp.packetsSent
if !e.protocol.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return
}
// DeliverTransportPacket will take ownership of pkt so don't use it beyond
// DeliverTransportPacket may modify pkt so don't use it beyond
// this point. Make a deep copy of the data before pkt gets sent as we will
// be modifying fields.
// be modifying fields. Both the ICMP header (with its type modified to
// EchoReply) and payload are reused in the reply packet.
//
// TODO(gvisor.dev/issue/4399): The copy may not be needed if there are no
// waiting endpoints. Consider moving responsibility for doing the copy to
// DeliverTransportPacket so that is is only done when needed.
replyData := pkt.Data().AsRange().ToOwnedView()
replyData := stack.PayloadSince(pkt.TransportHeader())
ipHdr := header.IPv4(pkt.NetworkHeader().View())
localAddressBroadcast := pkt.NetworkPacketInfo.LocalAddressBroadcast
@@ -281,6 +275,12 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
}
defer r.Release()
sent := e.stats.icmp.packetsSent
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
sent.rateLimited.Increment()
return
}
// TODO(gvisor.dev/issue/3810:) When adding protocol numbers into the
// header information, we may have to change this code to handle the
// ICMP header no longer being in the data buffer.
@@ -305,8 +305,8 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
// We need to produce the entire packet in the data segment in order to
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
// total length and the header checksum so we don't need to set those here.
replyIPHdr.SetSourceAddress(r.LocalAddress)
replyIPHdr.SetDestinationAddress(r.RemoteAddress)
replyIPHdr.SetSourceAddress(r.LocalAddress())
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
replyIPHdr.SetTTL(r.DefaultTTL())
replyICMPHdr := header.ICMPv4(replyData)
@@ -320,6 +320,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Data: replyVV,
})
defer replyPkt.DecRef()
replyPkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
if err := r.WriteHeaderIncludedPacket(replyPkt); err != nil {
@@ -331,19 +332,22 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
case header.ICMPv4EchoReply:
received.echoReply.Increment()
// ICMP sockets expect the ICMP header to be present, so we don't consume
// the ICMP header.
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
case header.ICMPv4DstUnreachable:
received.dstUnreachable.Increment()
pkt.Data().TrimFront(header.ICMPv4MinimumSize)
switch h.Code() {
mtu := h.MTU()
code := h.Code()
switch code {
case header.ICMPv4HostUnreachable:
e.handleControl(&icmpv4DestinationHostUnreachableSockError{}, pkt)
case header.ICMPv4PortUnreachable:
e.handleControl(&icmpv4DestinationPortUnreachableSockError{}, pkt)
case header.ICMPv4FragmentationNeeded:
networkMTU, err := calculateNetworkMTU(uint32(h.MTU()), header.IPv4MinimumSize)
networkMTU, err := calculateNetworkMTU(uint32(mtu), header.IPv4MinimumSize)
if err != nil {
networkMTU = 0
}
@@ -383,6 +387,8 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
// icmpReason is a marker interface for IPv4 specific ICMP errors.
type icmpReason interface {
isICMPReason()
// isForwarding indicates whether or not the error arose while attempting to
// forward a packet.
isForwarding() bool
}
@@ -442,6 +448,55 @@ func (r *icmpReasonParamProblem) isForwarding() bool {
return r.forwarding
}
// icmpReasonNetworkUnreachable is an error in which the network specified in
// the internet destination field of the datagram is unreachable.
type icmpReasonNetworkUnreachable struct{}
func (*icmpReasonNetworkUnreachable) isICMPReason() {}
func (*icmpReasonNetworkUnreachable) isForwarding() bool {
// If we hit a Net Unreachable error, then we know we are operating as
// a router. As per RFC 792 page 5, Destination Unreachable Message,
//
// If, according to the information in the gateway's routing tables,
// the network specified in the internet destination field of a
// datagram is unreachable, e.g., the distance to the network is
// infinity, the gateway may send a destination unreachable message to
// the internet source host of the datagram.
return true
}
// icmpReasonFragmentationNeeded is an error where a packet requires
// fragmentation while also having the Don't Fragment flag set, as per RFC 792
// page 3, Destination Unreachable Message.
type icmpReasonFragmentationNeeded struct{}
func (*icmpReasonFragmentationNeeded) isICMPReason() {}
func (*icmpReasonFragmentationNeeded) isForwarding() bool {
// If we hit a Don't Fragment error, then we know we are operating as a router.
// As per RFC 792 page 4, Destination Unreachable Message,
//
// Another case is when a datagram must be fragmented to be forwarded by a
// gateway yet the Don't Fragment flag is on. In this case the gateway must
// discard the datagram and may return a destination unreachable message.
return true
}
// icmpReasonHostUnreachable is an error in which the host specified in the
// internet destination field of the datagram is unreachable.
type icmpReasonHostUnreachable struct{}
func (*icmpReasonHostUnreachable) isICMPReason() {}
func (*icmpReasonHostUnreachable) isForwarding() bool {
// If we hit a Host Unreachable error, then we know we are operating as a
// router. As per RFC 792 page 5, Destination Unreachable Message,
//
// In addition, in some networks, the gateway may be able to determine
// if the internet destination host is unreachable. Gateways in these
// networks may send destination unreachable messages to the source host
// when the destination host is unreachable.
return true
}
// returnError takes an error descriptor and generates the appropriate ICMP
// error packet for IPv4 and sends it back to the remote device that sent
// the problematic packet. It incorporates as much of that packet as
@@ -498,42 +553,30 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
defer route.Release()
p.mu.Lock()
netEP, ok := p.mu.eps[pkt.NICID]
// We retrieve an endpoint using the newly constructed route's NICID rather
// than the packet's NICID. The packet's NICID corresponds to the NIC on
// which it arrived, which isn't necessarily the same as the NIC on which it
// will be transmitted. On the other hand, the route's NIC *is* guaranteed
// to be the NIC on which the packet will be transmitted.
netEP, ok := p.mu.eps[route.NICID()]
p.mu.Unlock()
if !ok {
return &tcpip.ErrNotConnected{}
}
sent := netEP.stats.icmp.packetsSent
if !p.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return nil
}
transportHeader := pkt.TransportHeader().View()
// Don't respond to icmp error packets.
if origIPHdr.Protocol() == uint8(header.ICMPv4ProtocolNumber) {
// TODO(gvisor.dev/issue/3810):
// Unfortunately the current stack pretty much always has ICMPv4 headers
// in the Data section of the packet but there is no guarantee that is the
// case. If this is the case grab the header to make it like all other
// packet types. When this is cleaned up the Consume should be removed.
if transportHeader.IsEmpty() {
var ok bool
transportHeader, ok = pkt.TransportHeader().Consume(header.ICMPv4MinimumSize)
if !ok {
return nil
}
} else if transportHeader.Size() < header.ICMPv4MinimumSize {
return nil
}
// We need to decide to explicitly name the packets we can respond to or
// the ones we can not respond to. The decision is somewhat arbitrary and
// if problems arise this could be reversed. It was judged less of a breach
// of protocol to not respond to unknown non-error packets than to respond
// to unknown error packets so we take the first approach.
if len(transportHeader) < header.ICMPv4MinimumSize {
// The packet is malformed.
return nil
}
switch header.ICMPv4(transportHeader).Type() {
case
header.ICMPv4EchoReply,
@@ -548,6 +591,35 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
}
}
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
switch reason := reason.(type) {
case *icmpReasonPortUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
case *icmpReasonProtoUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
case *icmpReasonNetworkUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
case *icmpReasonHostUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
case *icmpReasonFragmentationNeeded:
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
case *icmpReasonTTLExceeded:
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
case *icmpReasonParamProblem:
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
}()
if !p.allowICMPReply(icmpType, icmpCode) {
sent.rateLimited.Increment()
return nil
}
// Now work out how much of the triggering packet we should return.
// As per RFC 1812 Section 4.3.2.3
//
@@ -596,40 +668,17 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
ReserveHeaderBytes: int(route.MaxHeaderLength()) + header.ICMPv4MinimumSize,
Data: payload,
})
defer icmpPkt.DecRef()
icmpPkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
icmpHdr := header.ICMPv4(icmpPkt.TransportHeader().Push(header.ICMPv4MinimumSize))
var counter tcpip.MultiCounterStat
switch reason := reason.(type) {
case *icmpReasonPortUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4PortUnreachable)
counter = sent.dstUnreachable
case *icmpReasonProtoUnreachable:
icmpHdr.SetType(header.ICMPv4DstUnreachable)
icmpHdr.SetCode(header.ICMPv4ProtoUnreachable)
counter = sent.dstUnreachable
case *icmpReasonTTLExceeded:
icmpHdr.SetType(header.ICMPv4TimeExceeded)
icmpHdr.SetCode(header.ICMPv4TTLExceeded)
counter = sent.timeExceeded
case *icmpReasonReassemblyTimeout:
icmpHdr.SetType(header.ICMPv4TimeExceeded)
icmpHdr.SetCode(header.ICMPv4ReassemblyTimeout)
counter = sent.timeExceeded
case *icmpReasonParamProblem:
icmpHdr.SetType(header.ICMPv4ParamProblem)
icmpHdr.SetCode(header.ICMPv4UnusedCode)
icmpHdr.SetPointer(reason.pointer)
counter = sent.paramProblem
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
icmpHdr.SetCode(icmpCode)
icmpHdr.SetType(icmpType)
icmpHdr.SetPointer(pointer)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().AsRange().Checksum()))
if err := route.WritePacket(
nil, /* gso */
stack.NetworkHeaderParams{
Protocol: header.ICMPv4ProtocolNumber,
TTL: route.DefaultTTL(),
+14 -3
View File
@@ -126,6 +126,17 @@ func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
return err
}
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
// As per RFC 2236 section 6 page 10,
//
// The all-systems group (address 224.0.0.1) is handled as a special
// case. The host starts in Idle Member state for that group on every
// interface, never transitions to another state, and never sends a
// report for that group.
return groupAddress != header.IPv4AllSystems
}
// init sets up an igmpState struct, and is required to be called before using
// a new igmpState.
//
@@ -137,10 +148,9 @@ func (igmp *igmpState) init(ep *endpoint) {
Clock: ep.protocol.stack.Clock(),
Protocol: igmp,
MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
AllNodesAddress: header.IPv4AllSystems,
})
igmp.igmpV1Present = igmpV1PresentDefault
igmp.igmpV1Job = ep.protocol.stack.NewJob(&ep.mu, func() {
igmp.igmpV1Job = tcpip.NewJob(ep.protocol.stack.Clock(), &ep.mu, func() {
igmp.setV1Present(false)
})
}
@@ -312,6 +322,7 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()),
Data: buffer.View(igmpData).ToVectorisedView(),
})
defer pkt.DecRef()
addressEndpoint := igmp.ep.acquireOutgoingPrimaryAddressRLocked(destAddress, false /* allowExpired */)
if addressEndpoint == nil {
@@ -331,7 +342,7 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
}
sentStats := igmp.ep.stats.igmp.packetsSent
if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), nil /* gso */, ProtocolNumber, pkt); err != nil {
if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), ProtocolNumber, pkt); err != nil {
sentStats.dropped.Increment()
return false, err
}
File diff suppressed because it is too large. Load diff
@@ -16,12 +16,14 @@ func (i *icmpv4DestinationUnreachableSockError) StateFields() []string {
func (i *icmpv4DestinationUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv4DestinationUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
}
func (i *icmpv4DestinationUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv4DestinationUnreachableSockError) StateLoad(stateSourceObject state.Source) {
}
@@ -37,6 +39,7 @@ func (i *icmpv4DestinationHostUnreachableSockError) StateFields() []string {
func (i *icmpv4DestinationHostUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv4DestinationHostUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
stateSinkObject.Save(0, &i.icmpv4DestinationUnreachableSockError)
@@ -44,6 +47,7 @@ func (i *icmpv4DestinationHostUnreachableSockError) StateSave(stateSinkObject st
func (i *icmpv4DestinationHostUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv4DestinationHostUnreachableSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &i.icmpv4DestinationUnreachableSockError)
}
@@ -60,6 +64,7 @@ func (i *icmpv4DestinationPortUnreachableSockError) StateFields() []string {
func (i *icmpv4DestinationPortUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv4DestinationPortUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
stateSinkObject.Save(0, &i.icmpv4DestinationUnreachableSockError)
@@ -67,6 +72,7 @@ func (i *icmpv4DestinationPortUnreachableSockError) StateSave(stateSinkObject st
func (i *icmpv4DestinationPortUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv4DestinationPortUnreachableSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &i.icmpv4DestinationUnreachableSockError)
}
@@ -84,6 +90,7 @@ func (e *icmpv4FragmentationNeededSockError) StateFields() []string {
func (e *icmpv4FragmentationNeededSockError) beforeSave() {}
// +checklocksignore
func (e *icmpv4FragmentationNeededSockError) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
stateSinkObject.Save(0, &e.icmpv4DestinationUnreachableSockError)
@@ -92,6 +99,7 @@ func (e *icmpv4FragmentationNeededSockError) StateSave(stateSinkObject state.Sin
func (e *icmpv4FragmentationNeededSockError) afterLoad() {}
// +checklocksignore
func (e *icmpv4FragmentationNeededSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &e.icmpv4DestinationUnreachableSockError)
stateSourceObject.Load(1, &e.mtu)
+205 -141
View File
@@ -181,10 +181,15 @@ func (e *endpoint) handleControl(transErr stack.TransportError, pkt *stack.Packe
return
}
// Keep needed information before trimming header.
p := hdr.TransportProtocol()
dstAddr := hdr.DestinationAddress()
// Skip the IP header, then handle the fragmentation header if there
// is one.
pkt.Data().TrimFront(header.IPv6MinimumSize)
p := hdr.TransportProtocol()
if _, ok := pkt.Data().Consume(header.IPv6MinimumSize); !ok {
panic("could not consume IPv6MinimumSize bytes")
}
if p == header.IPv6FragmentHeader {
f, ok := pkt.Data().PullUp(header.IPv6FragmentHeaderSize)
if !ok {
@@ -196,14 +201,16 @@ func (e *endpoint) handleControl(transErr stack.TransportError, pkt *stack.Packe
// because they don't have the transport headers.
return
}
p = fragHdr.TransportProtocol()
// Skip fragmentation header and find out the actual protocol
// number.
pkt.Data().TrimFront(header.IPv6FragmentHeaderSize)
p = fragHdr.TransportProtocol()
if _, ok := pkt.Data().Consume(header.IPv6FragmentHeaderSize); !ok {
panic("could not consume IPv6FragmentHeaderSize bytes")
}
}
e.dispatcher.DeliverTransportError(srcAddr, hdr.DestinationAddress(), ProtocolNumber, p, transErr, pkt)
e.dispatcher.DeliverTransportError(srcAddr, dstAddr, ProtocolNumber, p, transErr, pkt)
}
// getLinkAddrOption searches NDP options for a given link address option using
@@ -267,13 +274,13 @@ func isMLDValid(pkt *stack.PacketBuffer, iph header.IPv6, routerAlert *header.IP
if routerAlert == nil || routerAlert.Value != header.IPv6RouterAlertMLD {
return false
}
if pkt.Data().Size() < header.ICMPv6HeaderSize+header.MLDMinimumSize {
if pkt.TransportHeader().View().Size() < header.ICMPv6HeaderSize+header.MLDMinimumSize {
return false
}
if iph.HopLimit() != header.MLDHopLimit {
return false
}
if !header.IsV6LinkLocalAddress(iph.SourceAddress()) {
if !header.IsV6LinkLocalUnicastAddress(iph.SourceAddress()) {
return false
}
return true
@@ -282,20 +289,17 @@ func isMLDValid(pkt *stack.PacketBuffer, iph header.IPv6, routerAlert *header.IP
func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, routerAlert *header.IPv6RouterAlertOption) {
sent := e.stats.icmp.packetsSent
received := e.stats.icmp.packetsReceived
// TODO(gvisor.dev/issue/170): ICMP packets don't have their TransportHeader
// fields set. See icmp/protocol.go:protocol.Parse for a full explanation.
v, ok := pkt.Data().PullUp(header.ICMPv6HeaderSize)
if !ok {
h := header.ICMPv6(pkt.TransportHeader().View())
if len(h) < header.ICMPv6MinimumSize {
received.invalid.Increment()
return
}
h := header.ICMPv6(v)
iph := header.IPv6(pkt.NetworkHeader().View())
srcAddr := iph.SourceAddress()
dstAddr := iph.DestinationAddress()
// Validate ICMPv6 checksum before processing the packet.
payload := pkt.Data().AsRange().SubRange(len(h))
payload := pkt.Data().AsRange()
if got, want := h.Checksum(), header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: h,
Src: srcAddr,
@@ -322,13 +326,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
switch icmpType := h.Type(); icmpType {
case header.ICMPv6PacketTooBig:
received.packetTooBig.Increment()
hdr, ok := pkt.Data().PullUp(header.ICMPv6PacketTooBigMinimumSize)
if !ok {
received.invalid.Increment()
return
}
pkt.Data().TrimFront(header.ICMPv6PacketTooBigMinimumSize)
networkMTU, err := calculateNetworkMTU(header.ICMPv6(hdr).MTU(), header.IPv6MinimumSize)
networkMTU, err := calculateNetworkMTU(h.MTU(), header.IPv6MinimumSize)
if err != nil {
networkMTU = 0
}
@@ -336,13 +334,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6DstUnreachable:
received.dstUnreachable.Increment()
hdr, ok := pkt.Data().PullUp(header.ICMPv6DstUnreachableMinimumSize)
if !ok {
received.invalid.Increment()
return
}
pkt.Data().TrimFront(header.ICMPv6DstUnreachableMinimumSize)
switch header.ICMPv6(hdr).Code() {
switch h.Code() {
case header.ICMPv6NetworkUnreachable:
e.handleControl(&icmpv6DestinationNetworkUnreachableSockError{}, pkt)
case header.ICMPv6PortUnreachable:
@@ -350,16 +342,12 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
}
case header.ICMPv6NeighborSolicit:
received.neighborSolicit.Increment()
if !isNDPValid() || pkt.Data().Size() < header.ICMPv6NeighborSolicitMinimumSize {
if !isNDPValid() || len(h) < header.ICMPv6NeighborSolicitMinimumSize {
received.invalid.Increment()
return
}
// The remainder of payload must be only the neighbor solicitation, so
// payload.AsView() always returns the solicitation. Per RFC 6980 section 5,
// NDP messages cannot be fragmented. Also note that in the common case NDP
// datagrams are very small and AsView() will not incur allocations.
ns := header.NDPNeighborSolicit(payload.AsView())
ns := header.NDPNeighborSolicit(h.MessageBody())
targetAddr := ns.TargetAddress()
// As per RFC 4861 section 4.3, the Target Address MUST NOT be a multicast
@@ -537,6 +525,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + neighborAdvertSize,
})
defer pkt.DecRef()
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
packet := header.ICMPv6(pkt.TransportHeader().Push(neighborAdvertSize))
packet.SetType(header.ICMPv6NeighborAdvert)
@@ -554,8 +543,8 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
na.Options().Serialize(optsSerializer)
packet.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: packet,
Src: r.LocalAddress,
Dst: r.RemoteAddress,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
}))
// RFC 4861 Neighbor Discovery for IP version 6 (IPv6)
@@ -564,7 +553,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
//
// The IP Hop Limit field has a value of 255, i.e., the packet
// could not possibly have been forwarded by a router.
if err := r.WritePacket(nil /* gso */, stack.NetworkHeaderParams{Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: stack.DefaultTOS}, pkt); err != nil {
if err := r.WritePacket(stack.NetworkHeaderParams{Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: stack.DefaultTOS}, pkt); err != nil {
sent.dropped.Increment()
return
}
@@ -572,16 +561,12 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6NeighborAdvert:
received.neighborAdvert.Increment()
if !isNDPValid() || pkt.Data().Size() < header.ICMPv6NeighborAdvertMinimumSize {
if !isNDPValid() || len(h) < header.ICMPv6NeighborAdvertMinimumSize {
received.invalid.Increment()
return
}
// The remainder of payload must be only the neighbor advertisement, so
// payload.AsView() always returns the advertisement. Per RFC 6980 section
// 5, NDP messages cannot be fragmented. Also note that in the common case
// NDP datagrams are very small and AsView() will not incur allocations.
na := header.NDPNeighborAdvert(payload.AsView())
na := header.NDPNeighborAdvert(h.MessageBody())
it, err := na.Options().Iter(false /* check */)
if err != nil {
@@ -668,12 +653,6 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6EchoRequest:
received.echoRequest.Increment()
icmpHdr, ok := pkt.TransportHeader().Consume(header.ICMPv6EchoMinimumSize)
if !ok {
received.invalid.Increment()
return
}
// As per RFC 4291 section 2.7, multicast addresses must not be used as
// source addresses in IPv6 packets.
localAddr := dstAddr
@@ -688,23 +667,29 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
}
defer r.Release()
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
sent.rateLimited.Increment()
return
}
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
Data: pkt.Data().ExtractVV(),
})
defer replyPkt.DecRef()
icmp := header.ICMPv6(replyPkt.TransportHeader().Push(header.ICMPv6EchoMinimumSize))
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
copy(icmp, icmpHdr)
copy(icmp, h)
icmp.SetType(header.ICMPv6EchoReply)
dataRange := replyPkt.Data().AsRange()
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: r.LocalAddress,
Dst: r.RemoteAddress,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
PayloadCsum: dataRange.Checksum(),
PayloadLen: dataRange.Size(),
}))
if err := r.WritePacket(nil /* gso */, stack.NetworkHeaderParams{
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: r.DefaultTTL(),
TOS: stack.DefaultTOS,
@@ -716,7 +701,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6EchoReply:
received.echoReply.Increment()
if pkt.Data().Size() < header.ICMPv6EchoMinimumSize {
if len(h) < header.ICMPv6EchoMinimumSize {
received.invalid.Increment()
return
}
@@ -736,23 +721,17 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
//
// Is the NDP payload of sufficient size to hold a Router Solictation?
if !isNDPValid() || pkt.Data().Size()-header.ICMPv6HeaderSize < header.NDPRSMinimumSize {
if !isNDPValid() || len(h)-header.ICMPv6HeaderSize < header.NDPRSMinimumSize {
received.invalid.Increment()
return
}
stack := e.protocol.stack
// Is the networking stack operating as a router?
if !stack.Forwarding(ProtocolNumber) {
// ... No, silently drop the packet.
if !e.Forwarding() {
received.routerOnlyPacketsDroppedByHost.Increment()
return
}
// Note that in the common case NDP datagrams are very small and AsView()
// will not incur allocations.
rs := header.NDPRouterSolicit(payload.AsView())
rs := header.NDPRouterSolicit(h.MessageBody())
it, err := rs.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
@@ -796,7 +775,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
//
// Is the NDP payload of sufficient size to hold a Router Advertisement?
if !isNDPValid() || pkt.Data().Size()-header.ICMPv6HeaderSize < header.NDPRAMinimumSize {
if !isNDPValid() || len(h)-header.ICMPv6HeaderSize < header.NDPRAMinimumSize {
received.invalid.Increment()
return
}
@@ -804,15 +783,13 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
routerAddr := srcAddr
// Is the IP Source Address a link-local address?
if !header.IsV6LinkLocalAddress(routerAddr) {
if !header.IsV6LinkLocalUnicastAddress(routerAddr) {
// ...No, silently drop the packet.
received.invalid.Increment()
return
}
// Note that in the common case NDP datagrams are very small and AsView()
// will not incur allocations.
ra := header.NDPRouterAdvert(payload.AsView())
ra := header.NDPRouterAdvert(h.MessageBody())
it, err := ra.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
@@ -890,11 +867,11 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
switch icmpType {
case header.ICMPv6MulticastListenerQuery:
e.mu.Lock()
e.mu.mld.handleMulticastListenerQuery(header.MLD(payload.AsView()))
e.mu.mld.handleMulticastListenerQuery(header.MLD(h.MessageBody()))
e.mu.Unlock()
case header.ICMPv6MulticastListenerReport:
e.mu.Lock()
e.mu.mld.handleMulticastListenerReport(header.MLD(payload.AsView()))
e.mu.mld.handleMulticastListenerReport(header.MLD(h.MessageBody()))
e.mu.Unlock()
case header.ICMPv6MulticastListenerDone:
default:
@@ -951,6 +928,19 @@ func (*endpoint) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bo
// icmpReason is a marker interface for IPv6 specific ICMP errors.
type icmpReason interface {
isICMPReason()
// isForwarding indicates whether or not the error arose while attempting to
// forward a packet.
isForwarding() bool
// respondToMulticast indicates whether this error falls under the exception
// outlined by RFC 4443 section 2.4 point e.3 exception 2:
//
// (e.3) A packet destined to an IPv6 multicast address. (There are two
// exceptions to this rule: (1) the Packet Too Big Message (Section 3.2) to
// allow Path MTU discovery to work for IPv6 multicast, and (2) the Parameter
// Problem Message, Code 2 (Section 3.4) reporting an unrecognized IPv6
// option (see Section 4.2 of [IPv6]) that has the Option Type highest-
// order two bits set to 10).
respondsToMulticast() bool
}
// icmpReasonParameterProblem is an error during processing of extension headers
@@ -958,18 +948,6 @@ type icmpReason interface {
type icmpReasonParameterProblem struct {
code header.ICMPv6Code
// respondToMulticast indicates that we are sending a packet that falls under
// the exception outlined by RFC 4443 section 2.4 point e.3 exception 2:
//
// (e.3) A packet destined to an IPv6 multicast address. (There are
// two exceptions to this rule: (1) the Packet Too Big Message
// (Section 3.2) to allow Path MTU discovery to work for IPv6
// multicast, and (2) the Parameter Problem Message, Code 2
// (Section 3.4) reporting an unrecognized IPv6 option (see
// Section 4.2 of [IPv6]) that has the Option Type highest-
// order two bits set to 10).
respondToMulticast bool
// pointer is defined in the RFC 4443 setion 3.4 which reads:
//
// Pointer Identifies the octet offset within the invoking packet
@@ -979,9 +957,20 @@ type icmpReasonParameterProblem struct {
// packet if the field in error is beyond what can fit
// in the maximum size of an ICMPv6 error message.
pointer uint32
forwarding bool
respondToMulticast bool
}
func (*icmpReasonParameterProblem) isICMPReason() {}
func (p *icmpReasonParameterProblem) isForwarding() bool {
return p.forwarding
}
func (p *icmpReasonParameterProblem) respondsToMulticast() bool {
return p.respondToMulticast
}
// icmpReasonPortUnreachable is an error where the transport protocol has no
// listener and no alternative means to inform the sender.
@@ -989,12 +978,96 @@ type icmpReasonPortUnreachable struct{}
func (*icmpReasonPortUnreachable) isICMPReason() {}
func (*icmpReasonPortUnreachable) isForwarding() bool {
return false
}
func (*icmpReasonPortUnreachable) respondsToMulticast() bool {
return false
}
// icmpReasonNetUnreachable is an error where no route can be found to the
// network of the final destination.
type icmpReasonNetUnreachable struct{}
func (*icmpReasonNetUnreachable) isICMPReason() {}
func (*icmpReasonNetUnreachable) isForwarding() bool {
// If we hit a Network Unreachable error, then we also know we are
// operating as a router. As per RFC 4443 section 3.1:
//
// If the reason for the failure to deliver is lack of a matching
// entry in the forwarding node's routing table, the Code field is
// set to 0 (Network Unreachable).
return true
}
func (*icmpReasonNetUnreachable) respondsToMulticast() bool {
return false
}
// icmpReasonHostUnreachable is an error in which the host specified in the
// internet destination field of the datagram is unreachable.
type icmpReasonHostUnreachable struct{}
func (*icmpReasonHostUnreachable) isICMPReason() {}
func (*icmpReasonHostUnreachable) isForwarding() bool {
// If we hit a Host Unreachable error, then we know we are operating as a
// router. As per RFC 4443 page 8, Destination Unreachable Message,
//
// If the reason for the failure to deliver cannot be mapped to any of
// other codes, the Code field is set to 3. Example of such cases are
// an inability to resolve the IPv6 destination address into a
// corresponding link address, or a link-specific problem of some sort.
return true
}
func (*icmpReasonHostUnreachable) respondsToMulticast() bool {
return false
}
// icmpReasonFragmentationNeeded is an error where a packet is to big to be sent
// out through the outgoing MTU, as per RFC 4443 page 9, Packet Too Big Message.
type icmpReasonPacketTooBig struct{}
func (*icmpReasonPacketTooBig) isICMPReason() {}
func (*icmpReasonPacketTooBig) isForwarding() bool {
// If we hit a Packet Too Big error, then we know we are operating as a router.
// As per RFC 4443 section 3.2:
//
// A Packet Too Big MUST be sent by a router in response to a packet that it
// cannot forward because the packet is larger than the MTU of the outgoing
// link.
return true
}
func (*icmpReasonPacketTooBig) respondsToMulticast() bool {
return true
}
// icmpReasonHopLimitExceeded is an error where a packet's hop limit exceeded in
// transit to its final destination, as per RFC 4443 section 3.3.
type icmpReasonHopLimitExceeded struct{}
func (*icmpReasonHopLimitExceeded) isICMPReason() {}
func (*icmpReasonHopLimitExceeded) isForwarding() bool {
// If we hit a Hop Limit Exceeded error, then we know we are operating
// as a router. As per RFC 4443 section 3.3:
//
// If a router receives a packet with a Hop Limit of zero, or if a
// router decrements a packet's Hop Limit to zero, it MUST discard
// the packet and originate an ICMPv6 Time Exceeded message with Code
// 0 to the source of the packet. This indicates either a routing
// loop or too small an initial Hop Limit value.
return true
}
func (*icmpReasonHopLimitExceeded) respondsToMulticast() bool {
return false
}
// icmpReasonReassemblyTimeout is an error where insufficient fragments are
// received to complete reassembly of a packet within a configured time after
// the reception of the first-arriving fragment of that packet.
@@ -1002,6 +1075,14 @@ type icmpReasonReassemblyTimeout struct{}
func (*icmpReasonReassemblyTimeout) isICMPReason() {}
func (*icmpReasonReassemblyTimeout) isForwarding() bool {
return false
}
func (*icmpReasonReassemblyTimeout) respondsToMulticast() bool {
return false
}
// returnError takes an error descriptor and generates the appropriate ICMP
// error packet for IPv6 and sends it.
func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip.Error {
@@ -1030,25 +1111,12 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
// Section 4.2 of [IPv6]) that has the Option Type highest-
// order two bits set to 10).
//
var allowResponseToMulticast bool
if reason, ok := reason.(*icmpReasonParameterProblem); ok {
allowResponseToMulticast = reason.respondToMulticast
}
allowResponseToMulticast := reason.respondsToMulticast()
isOrigDstMulticast := header.IsV6MulticastAddress(origIPHdrDst)
if (!allowResponseToMulticast && isOrigDstMulticast) || origIPHdrSrc == header.IPv6Any {
return nil
}
// If we hit a Hop Limit Exceeded error, then we know we are operating as a
// router. As per RFC 4443 section 3.3:
//
// If a router receives a packet with a Hop Limit of zero, or if a
// router decrements a packet's Hop Limit to zero, it MUST discard the
// packet and originate an ICMPv6 Time Exceeded message with Code 0 to
// the source of the packet. This indicates either a routing loop or
// too small an initial Hop Limit value.
//
// If we are operating as a router, do not use the packet's destination
// address as the response's source address as we should not own the
// destination address of a packet we are forwarding.
@@ -1058,7 +1126,7 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
// packet as "multicast addresses must not be used as source addresses in IPv6
// packets", as per RFC 4291 section 2.7.
localAddr := origIPHdrDst
if _, ok := reason.(*icmpReasonHopLimitExceeded); ok || isOrigDstMulticast {
if reason.isForwarding() || isOrigDstMulticast {
localAddr = ""
}
// Even if we were able to receive a packet from some remote, we may not have
@@ -1072,34 +1140,48 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
defer route.Release()
p.mu.Lock()
netEP, ok := p.mu.eps[pkt.NICID]
// We retrieve an endpoint using the newly constructed route's NICID rather
// than the packet's NICID. The packet's NICID corresponds to the NIC on
// which it arrived, which isn't necessarily the same as the NIC on which it
// will be transmitted. On the other hand, the route's NIC *is* guaranteed
// to be the NIC on which the packet will be transmitted.
netEP, ok := p.mu.eps[route.NICID()]
p.mu.Unlock()
if !ok {
return &tcpip.ErrNotConnected{}
}
sent := netEP.stats.icmp.packetsSent
if !p.stack.AllowICMPMessage() {
sent.rateLimited.Increment()
return nil
if pkt.TransportProtocolNumber == header.ICMPv6ProtocolNumber {
if typ := header.ICMPv6(pkt.TransportHeader().View()).Type(); typ.IsErrorType() || typ == header.ICMPv6RedirectMsg {
return nil
}
}
if pkt.TransportProtocolNumber == header.ICMPv6ProtocolNumber {
// TODO(gvisor.dev/issues/3810): Sort this out when ICMP headers are stored.
// Unfortunately at this time ICMP Packets do not have a transport
// header separated out. It is in the Data part so we need to
// separate it out now. We will just pretend it is a minimal length
// ICMP packet as we don't really care if any later bits of a
// larger ICMP packet are in the header view or in the Data view.
transport, ok := pkt.TransportHeader().Consume(header.ICMPv6MinimumSize)
if !ok {
return nil
}
typ := header.ICMPv6(transport).Type()
if typ.IsErrorType() || typ == header.ICMPv6RedirectMsg {
return nil
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, typeSpecific := func() (header.ICMPv6Type, header.ICMPv6Code, tcpip.MultiCounterStat, uint32) {
switch reason := reason.(type) {
case *icmpReasonParameterProblem:
return header.ICMPv6ParamProblem, reason.code, sent.paramProblem, reason.pointer
case *icmpReasonPortUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, sent.dstUnreachable, 0
case *icmpReasonNetUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6NetworkUnreachable, sent.dstUnreachable, 0
case *icmpReasonHostUnreachable:
return header.ICMPv6DstUnreachable, header.ICMPv6AddressUnreachable, sent.dstUnreachable, 0
case *icmpReasonPacketTooBig:
return header.ICMPv6PacketTooBig, header.ICMPv6UnusedCode, sent.packetTooBig, 0
case *icmpReasonHopLimitExceeded:
return header.ICMPv6TimeExceeded, header.ICMPv6HopLimitExceeded, sent.timeExceeded, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv6TimeExceeded, header.ICMPv6ReassemblyTimeout, sent.timeExceeded, 0
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
}()
if !p.allowICMPReply(icmpType) {
sent.rateLimited.Increment()
return nil
}
network, transport := pkt.NetworkHeader().View(), pkt.TransportHeader().View()
@@ -1133,41 +1215,23 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer) tcpip
ReserveHeaderBytes: int(route.MaxHeaderLength()) + header.ICMPv6ErrorHeaderSize,
Data: payload,
})
defer newPkt.DecRef()
newPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
icmpHdr := header.ICMPv6(newPkt.TransportHeader().Push(header.ICMPv6DstUnreachableMinimumSize))
var counter tcpip.MultiCounterStat
switch reason := reason.(type) {
case *icmpReasonParameterProblem:
icmpHdr.SetType(header.ICMPv6ParamProblem)
icmpHdr.SetCode(reason.code)
icmpHdr.SetTypeSpecific(reason.pointer)
counter = sent.paramProblem
case *icmpReasonPortUnreachable:
icmpHdr.SetType(header.ICMPv6DstUnreachable)
icmpHdr.SetCode(header.ICMPv6PortUnreachable)
counter = sent.dstUnreachable
case *icmpReasonHopLimitExceeded:
icmpHdr.SetType(header.ICMPv6TimeExceeded)
icmpHdr.SetCode(header.ICMPv6HopLimitExceeded)
counter = sent.timeExceeded
case *icmpReasonReassemblyTimeout:
icmpHdr.SetType(header.ICMPv6TimeExceeded)
icmpHdr.SetCode(header.ICMPv6ReassemblyTimeout)
counter = sent.timeExceeded
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
icmpHdr.SetType(icmpType)
icmpHdr.SetCode(icmpCode)
icmpHdr.SetTypeSpecific(typeSpecific)
dataRange := newPkt.Data().AsRange()
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr,
Src: route.LocalAddress,
Dst: route.RemoteAddress,
Src: route.LocalAddress(),
Dst: route.RemoteAddress(),
PayloadCsum: dataRange.Checksum(),
PayloadLen: dataRange.Size(),
}))
if err := route.WritePacket(
nil, /* gso */
stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: route.DefaultTTL(),
File diff suppressed because it is too large. Load diff
@@ -16,12 +16,14 @@ func (i *icmpv6DestinationUnreachableSockError) StateFields() []string {
func (i *icmpv6DestinationUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv6DestinationUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
}
func (i *icmpv6DestinationUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv6DestinationUnreachableSockError) StateLoad(stateSourceObject state.Source) {
}
@@ -37,6 +39,7 @@ func (i *icmpv6DestinationNetworkUnreachableSockError) StateFields() []string {
func (i *icmpv6DestinationNetworkUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv6DestinationNetworkUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
@@ -44,6 +47,7 @@ func (i *icmpv6DestinationNetworkUnreachableSockError) StateSave(stateSinkObject
func (i *icmpv6DestinationNetworkUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv6DestinationNetworkUnreachableSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
}
@@ -60,6 +64,7 @@ func (i *icmpv6DestinationPortUnreachableSockError) StateFields() []string {
func (i *icmpv6DestinationPortUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv6DestinationPortUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
@@ -67,6 +72,7 @@ func (i *icmpv6DestinationPortUnreachableSockError) StateSave(stateSinkObject st
func (i *icmpv6DestinationPortUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv6DestinationPortUnreachableSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
}
@@ -83,6 +89,7 @@ func (i *icmpv6DestinationAddressUnreachableSockError) StateFields() []string {
func (i *icmpv6DestinationAddressUnreachableSockError) beforeSave() {}
// +checklocksignore
func (i *icmpv6DestinationAddressUnreachableSockError) StateSave(stateSinkObject state.Sink) {
i.beforeSave()
stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
@@ -90,6 +97,7 @@ func (i *icmpv6DestinationAddressUnreachableSockError) StateSave(stateSinkObject
func (i *icmpv6DestinationAddressUnreachableSockError) afterLoad() {}
// +checklocksignore
func (i *icmpv6DestinationAddressUnreachableSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
}
@@ -106,6 +114,7 @@ func (e *icmpv6PacketTooBigSockError) StateFields() []string {
func (e *icmpv6PacketTooBigSockError) beforeSave() {}
// +checklocksignore
func (e *icmpv6PacketTooBigSockError) StateSave(stateSinkObject state.Sink) {
e.beforeSave()
stateSinkObject.Save(0, &e.mtu)
@@ -113,6 +122,7 @@ func (e *icmpv6PacketTooBigSockError) StateSave(stateSinkObject state.Sink) {
func (e *icmpv6PacketTooBigSockError) afterLoad() {}
// +checklocksignore
func (e *icmpv6PacketTooBigSockError) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &e.mtu)
}
+22 -3
View File
@@ -76,10 +76,29 @@ func (mld *mldState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error)
//
// Precondition: mld.ep.mu must be read locked.
func (mld *mldState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
_, err := mld.writePacket(header.IPv6AllRoutersMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
_, err := mld.writePacket(header.IPv6AllRoutersLinkLocalMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
return err
}
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
func (mld *mldState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
// As per RFC 2710 section 5 page 10,
//
// The link-scope all-nodes address (FF02::1) is handled as a special
// case. The node starts in Idle Listener state for that address on
// every interface, never transitions to another state, and never sends
// a Report or Done for that address.
//
// MLD messages are never sent for multicast addresses whose scope is 0
// (reserved) or 1 (node-local).
if groupAddress == header.IPv6AllNodesMulticastAddress {
return false
}
scope := header.V6MulticastScope(groupAddress)
return scope != header.IPv6Reserved0MulticastScope && scope != header.IPv6InterfaceLocalMulticastScope
}
// init sets up an mldState struct, and is required to be called before using
// a new mldState.
//
@@ -91,7 +110,6 @@ func (mld *mldState) init(ep *endpoint) {
Clock: ep.protocol.stack.Clock(),
Protocol: mld,
MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
AllNodesAddress: header.IPv6AllNodesMulticastAddress,
})
}
@@ -252,6 +270,7 @@ func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldTyp
ReserveHeaderBytes: int(mld.ep.MaxHeaderLength()) + extensionHeaders.Length(),
Data: buffer.View(icmp).ToVectorisedView(),
})
defer pkt.DecRef()
if err := addIPHeader(localAddress, destAddress, pkt, stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
@@ -259,7 +278,7 @@ func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldTyp
}, extensionHeaders); err != nil {
panic(fmt.Sprintf("failed to add IP header: %s", err))
}
if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), nil /* gso */, ProtocolNumber, pkt); err != nil {
if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), ProtocolNumber, pkt); err != nil {
sentStats.dropped.Increment()
return false, err
}
+282 -186
View File
@@ -16,7 +16,6 @@ package ipv6
import (
"fmt"
"math/rand"
"time"
"inet.af/netstack/sync"
@@ -48,13 +47,18 @@ const (
// defaultHandleRAs is the default configuration for whether or not to
// handle incoming Router Advertisements as a host.
defaultHandleRAs = true
defaultHandleRAs = HandlingRAsEnabledWhenForwardingDisabled
// defaultDiscoverDefaultRouters is the default configuration for
// whether or not to discover default routers from incoming Router
// Advertisements, as a host.
defaultDiscoverDefaultRouters = true
// defaultDiscoverMoreSpecificRoutes is the default configuration for
// whether or not to discover more-specific routes from incoming Router
// Advertisements, as a host.
defaultDiscoverMoreSpecificRoutes = true
// defaultDiscoverOnLinkPrefixes is the default configuration for
// whether or not to discover on-link prefixes from incoming Router
// Advertisements' Prefix Information option, as a host.
@@ -79,13 +83,13 @@ const (
// we cannot have a negative delay.
minimumMaxRtrSolicitationDelay = 0
// MaxDiscoveredDefaultRouters is the maximum number of discovered
// default routers. The stack should stop discovering new routers after
// discovering MaxDiscoveredDefaultRouters routers.
// MaxDiscoveredOffLinkRoutes is the maximum number of discovered off-link
// routes. The stack should stop discovering new off-link routes after
// this limit is reached.
//
// This value MUST be at minimum 2 as per RFC 4861 section 6.3.4, and
// SHOULD be more.
MaxDiscoveredDefaultRouters = 10
MaxDiscoveredOffLinkRoutes = 10
// MaxDiscoveredOnLinkPrefixes is the maximum number of discovered
// on-link prefixes. The stack should stop discovering new on-link
@@ -128,25 +132,17 @@ const (
// maxSLAACAddrLocalRegenAttempts is the maximum number of times to attempt
// SLAAC address regenerations in response to an IPv6 endpoint-local conflict.
maxSLAACAddrLocalRegenAttempts = 10
)
var (
// MinPrefixInformationValidLifetimeForUpdate is the minimum Valid
// Lifetime to update the valid lifetime of a generated address by
// SLAAC.
//
// This is exported as a variable (instead of a constant) so tests
// can update it to a smaller value.
//
// Min = 2hrs.
MinPrefixInformationValidLifetimeForUpdate = 2 * time.Hour
// MaxDesyncFactor is the upper bound for the preferred lifetime's desync
// factor for temporary SLAAC addresses.
//
// This is exported as a variable (instead of a constant) so tests
// can update it to a smaller value.
//
// Must be greater than 0.
//
// Max = 10m (from RFC 4941 section 5).
@@ -155,9 +151,6 @@ var (
// MinMaxTempAddrPreferredLifetime is the minimum value allowed for the
// maximum preferred lifetime for temporary SLAAC addresses.
//
// This is exported as a variable (instead of a constant) so tests
// can update it to a smaller value.
//
// This value guarantees that a temporary address is preferred for at
// least 1hr if the SLAAC prefix is valid for at least that time.
MinMaxTempAddrPreferredLifetime = defaultRegenAdvanceDuration + MaxDesyncFactor + time.Hour
@@ -165,9 +158,6 @@ var (
// MinMaxTempAddrValidLifetime is the minimum value allowed for the
// maximum valid lifetime for temporary SLAAC addresses.
//
// This is exported as a variable (instead of a constant) so tests
// can update it to a smaller value.
//
// This value guarantees that a temporary address is valid for at least
// 2hrs if the SLAAC prefix is valid for at least that time.
MinMaxTempAddrValidLifetime = 2 * time.Hour
@@ -215,28 +205,23 @@ type NDPDispatcher interface {
// is also not permitted to call into the stack.
OnDuplicateAddressDetectionResult(tcpip.NICID, tcpip.Address, stack.DADResult)
// OnDefaultRouterDiscovered is called when a new default router is
// discovered. Implementations must return true if the newly discovered
// router should be remembered.
// OnOffLinkRouteUpdated is called when an off-link route is updated.
//
// This function is not permitted to block indefinitely. This function
// is also not permitted to call into the stack.
OnDefaultRouterDiscovered(tcpip.NICID, tcpip.Address) bool
OnOffLinkRouteUpdated(tcpip.NICID, tcpip.Subnet, tcpip.Address, header.NDPRoutePreference)
// OnDefaultRouterInvalidated is called when a discovered default router that
// was remembered is invalidated.
// OnOffLinkRouteInvalidated is called when an off-link route is invalidated.
//
// This function is not permitted to block indefinitely. This function
// is also not permitted to call into the stack.
OnDefaultRouterInvalidated(tcpip.NICID, tcpip.Address)
OnOffLinkRouteInvalidated(tcpip.NICID, tcpip.Subnet, tcpip.Address)
// OnOnLinkPrefixDiscovered is called when a new on-link prefix is discovered.
// Implementations must return true if the newly discovered on-link prefix
// should be remembered.
//
// This function is not permitted to block indefinitely. This function
// is also not permitted to call into the stack.
OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) bool
OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet)
// OnOnLinkPrefixInvalidated is called when a discovered on-link prefix that
// was remembered is invalidated.
@@ -246,13 +231,11 @@ type NDPDispatcher interface {
OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet)
// OnAutoGenAddress is called when a new prefix with its autonomous address-
// configuration flag set is received and SLAAC was performed. Implementations
// may prevent the stack from assigning the address to the NIC by returning
// false.
// configuration flag set is received and SLAAC was performed.
//
// This function is not permitted to block indefinitely. It must not
// call functions on the stack itself.
OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool
OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix)
// OnAutoGenAddressDeprecated is called when an auto-generated address (SLAAC)
// is deprecated, but is still considered valid. Note, if an address is
@@ -301,10 +284,60 @@ type NDPDispatcher interface {
OnDHCPv6Configuration(tcpip.NICID, DHCPv6ConfigurationFromNDPRA)
}
var _ fmt.Stringer = HandleRAsConfiguration(0)
// HandleRAsConfiguration enumerates when RAs may be handled.
type HandleRAsConfiguration int
const (
// HandlingRAsDisabled indicates that Router Advertisements will not be
// handled.
HandlingRAsDisabled HandleRAsConfiguration = iota
// HandlingRAsEnabledWhenForwardingDisabled indicates that router
// advertisements will only be handled when forwarding is disabled.
HandlingRAsEnabledWhenForwardingDisabled
// HandlingRAsAlwaysEnabled indicates that Router Advertisements will always
// be handled, even when forwarding is enabled.
HandlingRAsAlwaysEnabled
)
// String implements fmt.Stringer.
func (c HandleRAsConfiguration) String() string {
switch c {
case HandlingRAsDisabled:
return "HandlingRAsDisabled"
case HandlingRAsEnabledWhenForwardingDisabled:
return "HandlingRAsEnabledWhenForwardingDisabled"
case HandlingRAsAlwaysEnabled:
return "HandlingRAsAlwaysEnabled"
default:
return fmt.Sprintf("HandleRAsConfiguration(%d)", c)
}
}
// enabled returns true iff Router Advertisements may be handled given the
// specified forwarding status.
func (c HandleRAsConfiguration) enabled(forwarding bool) bool {
switch c {
case HandlingRAsDisabled:
return false
case HandlingRAsEnabledWhenForwardingDisabled:
return !forwarding
case HandlingRAsAlwaysEnabled:
return true
default:
panic(fmt.Sprintf("unhandled HandleRAsConfiguration = %d", c))
}
}
// NDPConfigurations is the NDP configurations for the netstack.
type NDPConfigurations struct {
// The number of Router Solicitation messages to send when the IPv6 endpoint
// becomes enabled.
//
// Ignored unless configured to handle Router Advertisements.
MaxRtrSolicitations uint8
// The amount of time between transmitting Router Solicitation messages.
@@ -318,17 +351,24 @@ type NDPConfigurations struct {
// Must be greater than or equal to 0s.
MaxRtrSolicitationDelay time.Duration
// HandleRAs determines whether or not Router Advertisements are processed.
HandleRAs bool
// HandleRAs is the configuration for when Router Advertisements should be
// handled.
HandleRAs HandleRAsConfiguration
// DiscoverDefaultRouters determines whether or not default routers are
// discovered from Router Advertisements, as per RFC 4861 section 6. This
// configuration is ignored if HandleRAs is false.
// configuration is ignored if RAs will not be processed (see HandleRAs).
DiscoverDefaultRouters bool
// DiscoverMoreSpecificRoutes determines whether or not more specific routes
// are discovered from Router Advertisements, as per RFC 4191. This
// configuration is ignored if RAs will not be processed (see HandleRAs).
DiscoverMoreSpecificRoutes bool
// DiscoverOnLinkPrefixes determines whether or not on-link prefixes are
// discovered from Router Advertisements' Prefix Information option, as per
// RFC 4861 section 6. This configuration is ignored if HandleRAs is false.
// RFC 4861 section 6. This configuration is ignored if RAs will not be
// processed (see HandleRAs).
DiscoverOnLinkPrefixes bool
// AutoGenGlobalAddresses determines whether or not an IPv6 endpoint performs
@@ -379,6 +419,7 @@ func DefaultNDPConfigurations() NDPConfigurations {
MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay,
HandleRAs: defaultHandleRAs,
DiscoverDefaultRouters: defaultDiscoverDefaultRouters,
DiscoverMoreSpecificRoutes: defaultDiscoverMoreSpecificRoutes,
DiscoverOnLinkPrefixes: defaultDiscoverOnLinkPrefixes,
AutoGenGlobalAddresses: defaultAutoGenGlobalAddresses,
AutoGenTempGlobalAddresses: defaultAutoGenTempGlobalAddresses,
@@ -419,6 +460,11 @@ type timer struct {
timer tcpip.Timer
}
type offLinkRoute struct {
dest tcpip.Subnet
router tcpip.Address
}
// ndpState is the per-Interface NDP state.
type ndpState struct {
// Do not allow overwriting this state.
@@ -433,8 +479,8 @@ type ndpState struct {
// The DAD timers to send the next NS message, or resolve the address.
dad ip.DAD
// The default routers discovered through Router Advertisements.
defaultRouters map[tcpip.Address]defaultRouterState
// The off-link routes discovered through Router Advertisements.
offLinkRoutes map[offLinkRoute]offLinkRouteState
// rtrSolicitTimer is the timer used to send the next router solicitation
// message.
@@ -462,10 +508,12 @@ type ndpState struct {
temporaryAddressDesyncFactor time.Duration
}
// defaultRouterState holds data associated with a default router discovered by
// offLinkRouteState holds data associated with an off-link route discovered by
// a Router Advertisement (RA).
type defaultRouterState struct {
// Job to invalidate the default router.
type offLinkRouteState struct {
prf header.NDPRoutePreference
// Job to invalidate the route.
//
// Must not be nil.
invalidationJob *tcpip.Job
@@ -498,7 +546,7 @@ type tempSLAACAddrState struct {
// Must not be nil.
regenJob *tcpip.Job
createdAt time.Time
createdAt tcpip.MonotonicTime
// The address's endpoint.
//
@@ -521,11 +569,11 @@ type slaacPrefixState struct {
// Must not be nil.
invalidationJob *tcpip.Job
// Nonzero only when the address is not valid forever.
validUntil time.Time
// nil iff the address is valid forever.
validUntil *tcpip.MonotonicTime
// Nonzero only when the address is not preferred forever.
preferredUntil time.Time
// nil iff the address is preferred forever.
preferredUntil *tcpip.MonotonicTime
// State associated with the stable address generated for the prefix.
stableAddr struct {
@@ -654,7 +702,8 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
// per-interface basis; it is a protocol-wide configuration, so we check the
// protocol's forwarding flag to determine if the IPv6 endpoint is forwarding
// packets.
if !ndp.configs.HandleRAs || ndp.ep.protocol.Forwarding() {
if !ndp.configs.HandleRAs.enabled(ndp.ep.Forwarding()) {
ndp.ep.stats.localStats.UnhandledRouterAdvertisements.Increment()
return
}
@@ -682,30 +731,22 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
// Is the IPv6 endpoint configured to discover default routers?
if ndp.configs.DiscoverDefaultRouters {
rtr, ok := ndp.defaultRouters[ip]
rl := ra.RouterLifetime()
switch {
case !ok && rl != 0:
// This is a new default router we are discovering.
prf := ra.DefaultRouterPreference()
if prf == header.ReservedRoutePreference {
// As per RFC 4191 section 2.2,
//
// Only remember it if we currently know about less than
// MaxDiscoveredDefaultRouters routers.
if len(ndp.defaultRouters) < MaxDiscoveredDefaultRouters {
ndp.rememberDefaultRouter(ip, rl)
}
case ok && rl != 0:
// This is an already discovered default router. Update
// the invalidation job.
rtr.invalidationJob.Cancel()
rtr.invalidationJob.Schedule(rl)
ndp.defaultRouters[ip] = rtr
case ok && rl == 0:
// We know about the router but it is no longer to be
// used as a default router so invalidate it.
ndp.invalidateDefaultRouter(ip)
// Prf (Default Router Preference)
//
// If the Reserved (10) value is received, the receiver MUST treat the
// value as if it were (00).
//
// Note that the value 00 is the medium (default) router preference value.
prf = header.MediumRoutePreference
}
// We represent default routers with a default (off-link) route through the
// router.
ndp.handleOffLinkRouteDiscovery(offLinkRoute{dest: header.IPv6EmptySubnet, router: ip}, ra.RouterLifetime(), prf)
}
// TODO(b/141556115): Do (RetransTimer, ReachableTime)) Parameter
@@ -737,7 +778,7 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
prefix := opt.Subnet()
// Is the prefix a link-local?
if header.IsV6LinkLocalAddress(prefix.ID()) {
if header.IsV6LinkLocalUnicastAddress(prefix.ID()) {
// ...Yes, skip as per RFC 4861 section 6.3.4,
// and RFC 4862 section 5.5.3.b (for SLAAC).
continue
@@ -757,61 +798,107 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
if opt.AutonomousAddressConfigurationFlag() {
ndp.handleAutonomousPrefixInformation(opt)
}
case header.NDPRouteInformation:
if !ndp.configs.DiscoverMoreSpecificRoutes {
continue
}
dest, err := opt.Prefix()
if err != nil {
panic(fmt.Sprintf("%T.Prefix(): %s", opt, err))
}
prf := opt.RoutePreference()
if prf == header.ReservedRoutePreference {
// As per RFC 4191 section 2.3,
//
// Prf (Route Preference)
// 2-bit signed integer. The Route Preference indicates
// whether to prefer the router associated with this prefix
// over others, when multiple identical prefixes (for
// different routers) have been received. If the Reserved
// (10) value is received, the Route Information Option MUST
// be ignored.
continue
}
ndp.handleOffLinkRouteDiscovery(offLinkRoute{dest: dest, router: ip}, opt.RouteLifetime(), prf)
}
// TODO(b/141556115): Do (MTU) Parameter Discovery.
}
}
// invalidateDefaultRouter invalidates a discovered default router.
// invalidateOffLinkRoute invalidates a discovered off-link route.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) invalidateDefaultRouter(ip tcpip.Address) {
rtr, ok := ndp.defaultRouters[ip]
// Is the router still discovered?
func (ndp *ndpState) invalidateOffLinkRoute(route offLinkRoute) {
state, ok := ndp.offLinkRoutes[route]
if !ok {
// ...Nope, do nothing further.
return
}
rtr.invalidationJob.Cancel()
delete(ndp.defaultRouters, ip)
state.invalidationJob.Cancel()
delete(ndp.offLinkRoutes, route)
// Let the integrator know a discovered default router is invalidated.
// Let the integrator know a discovered off-link route is invalidated.
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
ndpDisp.OnDefaultRouterInvalidated(ndp.ep.nic.ID(), ip)
ndpDisp.OnOffLinkRouteInvalidated(ndp.ep.nic.ID(), route.dest, route.router)
}
}
// rememberDefaultRouter remembers a newly discovered default router with IPv6
// link-local address ip with lifetime rl.
// handleOffLinkRouteDiscovery handles the discovery of an off-link route.
//
// The router identified by ip MUST NOT already be known by the IPv6 endpoint.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) rememberDefaultRouter(ip tcpip.Address, rl time.Duration) {
// Precondition: ndp.ep.mu must be locked.
func (ndp *ndpState) handleOffLinkRouteDiscovery(route offLinkRoute, lifetime time.Duration, prf header.NDPRoutePreference) {
ndpDisp := ndp.ep.protocol.options.NDPDisp
if ndpDisp == nil {
return
}
// Inform the integrator when we discovered a default router.
if !ndpDisp.OnDefaultRouterDiscovered(ndp.ep.nic.ID(), ip) {
// Informed by the integrator to not remember the router, do
// nothing further.
return
state, ok := ndp.offLinkRoutes[route]
switch {
case !ok && lifetime != 0:
// This is a new route we are discovering.
//
// Only remember it if we currently know about less than
// MaxDiscoveredOffLinkRoutes routers.
if len(ndp.offLinkRoutes) < MaxDiscoveredOffLinkRoutes {
// Inform the integrator when we discovered an off-link route.
ndpDisp.OnOffLinkRouteUpdated(ndp.ep.nic.ID(), route.dest, route.router, prf)
state := offLinkRouteState{
prf: prf,
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
ndp.invalidateOffLinkRoute(route)
}),
}
state.invalidationJob.Schedule(lifetime)
ndp.offLinkRoutes[route] = state
}
case ok && lifetime != 0:
// This is an already discovered off-link route. Update the lifetime.
state.invalidationJob.Cancel()
state.invalidationJob.Schedule(lifetime)
if prf != state.prf {
state.prf = prf
// Inform the integrator about route preference updates.
ndpDisp.OnOffLinkRouteUpdated(ndp.ep.nic.ID(), route.dest, route.router, prf)
}
ndp.offLinkRoutes[route] = state
case ok && lifetime == 0:
// The already discovered off-link route is no longer considered valid so we
// invalidate it immediately.
ndp.invalidateOffLinkRoute(route)
}
state := defaultRouterState{
invalidationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
ndp.invalidateDefaultRouter(ip)
}),
}
state.invalidationJob.Schedule(rl)
ndp.defaultRouters[ip] = state
}
// rememberOnLinkPrefix remembers a newly discovered on-link prefix with IPv6
@@ -827,14 +914,10 @@ func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration)
}
// Inform the integrator when we discovered an on-link prefix.
if !ndpDisp.OnOnLinkPrefixDiscovered(ndp.ep.nic.ID(), prefix) {
// Informed by the integrator to not remember the prefix, do
// nothing further.
return
}
ndpDisp.OnOnLinkPrefixDiscovered(ndp.ep.nic.ID(), prefix)
state := onLinkPrefixState{
invalidationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
ndp.invalidateOnLinkPrefix(prefix)
}),
}
@@ -979,7 +1062,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
}
state := slaacPrefixState{
deprecationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
deprecationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix))
@@ -987,7 +1070,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
ndp.deprecateSLAACAddress(state.stableAddr.addressEndpoint)
}),
invalidationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the invalidated SLAAC prefix %s", prefix))
@@ -999,12 +1082,13 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
maxGenerationAttempts: ndp.configs.AutoGenAddressConflictRetries + 1,
}
now := time.Now()
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
// The time an address is preferred until is needed to properly generate the
// address.
if pl < header.NDPInfiniteLifetime {
state.preferredUntil = now.Add(pl)
t := now.Add(pl)
state.preferredUntil = &t
}
if !ndp.generateSLAACAddr(prefix, &state) {
@@ -1022,7 +1106,8 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
if vl < header.NDPInfiniteLifetime {
state.invalidationJob.Schedule(vl)
state.validUntil = now.Add(vl)
t := now.Add(vl)
state.validUntil = &t
}
// If the address is assigned (DAD resolved), generate a temporary address.
@@ -1045,16 +1130,17 @@ func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, config
return nil
}
if !ndpDisp.OnAutoGenAddress(ndp.ep.nic.ID(), addr) {
// Informed by the integrator not to add the address.
return nil
}
addressEndpoint, err := ndp.ep.addAndAcquirePermanentAddressLocked(addr, stack.FirstPrimaryEndpoint, configType, deprecated)
addressEndpoint, err := ndp.ep.addAndAcquirePermanentAddressLocked(addr, stack.AddressProperties{
PEB: stack.FirstPrimaryEndpoint,
ConfigType: configType,
Deprecated: deprecated,
})
if err != nil {
panic(fmt.Sprintf("ndp: error when adding SLAAC address %+v: %s", addr, err))
}
ndpDisp.OnAutoGenAddress(ndp.ep.nic.ID(), addr)
return addressEndpoint
}
@@ -1130,7 +1216,8 @@ func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixSt
state.stableAddr.localGenerationFailures++
}
if addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, stack.AddressConfigSlaac, time.Since(state.preferredUntil) >= 0 /* deprecated */); addressEndpoint != nil {
deprecated := state.preferredUntil != nil && !state.preferredUntil.After(ndp.ep.protocol.stack.Clock().NowMonotonic())
if addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, stack.AddressConfigSlaac, deprecated); addressEndpoint != nil {
state.stableAddr.addressEndpoint = addressEndpoint
state.generationAttempts++
return true
@@ -1185,13 +1272,13 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
}
stableAddr := prefixState.stableAddr.addressEndpoint.AddressWithPrefix().Address
now := time.Now()
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
// As per RFC 4941 section 3.3 step 4, the valid lifetime of a temporary
// address is the lower of the valid lifetime of the stable address or the
// maximum temporary address valid lifetime.
vl := ndp.configs.MaxTempAddrValidLifetime
if prefixState.validUntil != (time.Time{}) {
if prefixState.validUntil != nil {
if prefixVL := prefixState.validUntil.Sub(now); vl > prefixVL {
vl = prefixVL
}
@@ -1207,7 +1294,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
// maximum temporary address preferred lifetime - the temporary address desync
// factor.
pl := ndp.configs.MaxTempAddrPreferredLifetime - ndp.temporaryAddressDesyncFactor
if prefixState.preferredUntil != (time.Time{}) {
if prefixState.preferredUntil != nil {
if prefixPL := prefixState.preferredUntil.Sub(now); pl > prefixPL {
// Respect the preferred lifetime of the prefix, as per RFC 4941 section
// 3.3 step 4.
@@ -1248,7 +1335,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
}
state := tempSLAACAddrState{
deprecationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
deprecationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to deprecate temporary address %s", prefix, generatedAddr))
@@ -1261,7 +1348,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
ndp.deprecateSLAACAddress(tempAddrState.addressEndpoint)
}),
invalidationJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to invalidate temporary address %s", prefix, generatedAddr))
@@ -1274,7 +1361,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
ndp.invalidateTempSLAACAddr(prefixState.tempAddrs, generatedAddr.Address, tempAddrState)
}),
regenJob: ndp.ep.protocol.stack.NewJob(&ndp.ep.mu, func() {
regenJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to regenerate temporary address after %s", prefix, generatedAddr))
@@ -1342,16 +1429,17 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// deprecation job so it can be reset.
prefixState.deprecationJob.Cancel()
now := time.Now()
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
// Schedule the deprecation job if prefix has a finite preferred lifetime.
if pl < header.NDPInfiniteLifetime {
if !deprecated {
prefixState.deprecationJob.Schedule(pl)
}
prefixState.preferredUntil = now.Add(pl)
t := now.Add(pl)
prefixState.preferredUntil = &t
} else {
prefixState.preferredUntil = time.Time{}
prefixState.preferredUntil = nil
}
// As per RFC 4862 section 5.5.3.e, update the valid lifetime for prefix:
@@ -1369,17 +1457,17 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// Handle the infinite valid lifetime separately as we do not schedule a
// job in this case.
prefixState.invalidationJob.Cancel()
prefixState.validUntil = time.Time{}
prefixState.validUntil = nil
} else {
var effectiveVl time.Duration
var rl time.Duration
// If the prefix was originally set to be valid forever, assume the
// remaining time to be the maximum possible value.
if prefixState.validUntil == (time.Time{}) {
if prefixState.validUntil == nil {
rl = header.NDPInfiniteLifetime
} else {
rl = time.Until(prefixState.validUntil)
rl = prefixState.validUntil.Sub(now)
}
if vl > MinPrefixInformationValidLifetimeForUpdate || vl > rl {
@@ -1391,7 +1479,8 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
if effectiveVl != 0 {
prefixState.invalidationJob.Cancel()
prefixState.invalidationJob.Schedule(effectiveVl)
prefixState.validUntil = now.Add(effectiveVl)
t := now.Add(effectiveVl)
prefixState.validUntil = &t
}
}
@@ -1411,8 +1500,8 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// maximum temporary address valid lifetime. Note, the valid lifetime of a
// temporary address is relative to the address's creation time.
validUntil := tempAddrState.createdAt.Add(ndp.configs.MaxTempAddrValidLifetime)
if prefixState.validUntil != (time.Time{}) && validUntil.Sub(prefixState.validUntil) > 0 {
validUntil = prefixState.validUntil
if prefixState.validUntil != nil && prefixState.validUntil.Before(validUntil) {
validUntil = *prefixState.validUntil
}
// If the address is no longer valid, invalidate it immediately. Otherwise,
@@ -1431,14 +1520,15 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// desync factor. Note, the preferred lifetime of a temporary address is
// relative to the address's creation time.
preferredUntil := tempAddrState.createdAt.Add(ndp.configs.MaxTempAddrPreferredLifetime - ndp.temporaryAddressDesyncFactor)
if prefixState.preferredUntil != (time.Time{}) && preferredUntil.Sub(prefixState.preferredUntil) > 0 {
preferredUntil = prefixState.preferredUntil
if prefixState.preferredUntil != nil && prefixState.preferredUntil.Before(preferredUntil) {
preferredUntil = *prefixState.preferredUntil
}
// If the address is no longer preferred, deprecate it immediately.
// Otherwise, schedule the deprecation job again.
newPreferredLifetime := preferredUntil.Sub(now)
tempAddrState.deprecationJob.Cancel()
if newPreferredLifetime <= 0 {
ndp.deprecateSLAACAddress(tempAddrState.addressEndpoint)
} else {
@@ -1609,44 +1699,16 @@ func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs map[t
delete(tempAddrs, tempAddr)
}
// removeSLAACAddresses removes all SLAAC addresses.
//
// If keepLinkLocal is false, the SLAAC generated link-local address is removed.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) removeSLAACAddresses(keepLinkLocal bool) {
linkLocalSubnet := header.IPv6LinkLocalPrefix.Subnet()
var linkLocalPrefixes int
for prefix, state := range ndp.slaacPrefixes {
// RFC 4862 section 5 states that routers are also expected to generate a
// link-local address so we do not invalidate them if we are cleaning up
// host-only state.
if keepLinkLocal && prefix == linkLocalSubnet {
linkLocalPrefixes++
continue
}
ndp.invalidateSLAACPrefix(prefix, state)
}
if got := len(ndp.slaacPrefixes); got != linkLocalPrefixes {
panic(fmt.Sprintf("ndp: still have non-linklocal SLAAC prefixes after cleaning up; found = %d prefixes, of which %d are link-local", got, linkLocalPrefixes))
}
}
// cleanupState cleans up ndp's state.
//
// If hostOnly is true, then only host-specific state is cleaned up.
//
// This function invalidates all discovered on-link prefixes, discovered
// routers, and auto-generated addresses.
//
// If hostOnly is true, then the link-local auto-generated address aren't
// invalidated as routers are also expected to generate a link-local address.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupState(hostOnly bool) {
ndp.removeSLAACAddresses(hostOnly /* keepLinkLocal */)
func (ndp *ndpState) cleanupState() {
for prefix, state := range ndp.slaacPrefixes {
ndp.invalidateSLAACPrefix(prefix, state)
}
for prefix := range ndp.onLinkPrefixes {
ndp.invalidateOnLinkPrefix(prefix)
@@ -1656,12 +1718,12 @@ func (ndp *ndpState) cleanupState(hostOnly bool) {
panic(fmt.Sprintf("ndp: still have discovered on-link prefixes after cleaning up; found = %d", got))
}
for router := range ndp.defaultRouters {
ndp.invalidateDefaultRouter(router)
for route := range ndp.offLinkRoutes {
ndp.invalidateOffLinkRoute(route)
}
if got := len(ndp.defaultRouters); got != 0 {
panic(fmt.Sprintf("ndp: still have discovered default routers after cleaning up; found = %d", got))
if got := len(ndp.offLinkRoutes); got != 0 {
panic(fmt.Sprintf("ndp: still have discovered off-link routes after cleaning up; found = %d", got))
}
ndp.dhcpv6Configuration = 0
@@ -1670,6 +1732,10 @@ func (ndp *ndpState) cleanupState(hostOnly bool) {
// startSolicitingRouters starts soliciting routers, as per RFC 4861 section
// 6.3.7. If routers are already being solicited, this function does nothing.
//
// If ndp is not configured to handle Router Advertisements, routers will not
// be solicited as there is no point soliciting routers if we don't handle their
// advertisements.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) startSolicitingRouters() {
if ndp.rtrSolicitTimer.timer != nil {
@@ -1682,11 +1748,15 @@ func (ndp *ndpState) startSolicitingRouters() {
return
}
if !ndp.configs.HandleRAs.enabled(ndp.ep.Forwarding()) {
return
}
// Calculate the random delay before sending our first RS, as per RFC
// 4861 section 6.3.7.
var delay time.Duration
if ndp.configs.MaxRtrSolicitationDelay > 0 {
delay = time.Duration(rand.Int63n(int64(ndp.configs.MaxRtrSolicitationDelay)))
delay = time.Duration(ndp.ep.protocol.stack.Rand().Int63n(int64(ndp.configs.MaxRtrSolicitationDelay)))
}
// Protected by ndp.ep.mu.
@@ -1703,7 +1773,7 @@ func (ndp *ndpState) startSolicitingRouters() {
// the unspecified address if no address is assigned
// to the sending interface.
localAddr := header.IPv6Any
if addressEndpoint := ndp.ep.AcquireOutgoingPrimaryAddress(header.IPv6AllRoutersMulticastAddress, false); addressEndpoint != nil {
if addressEndpoint := ndp.ep.AcquireOutgoingPrimaryAddress(header.IPv6AllRoutersLinkLocalMulticastAddress, false); addressEndpoint != nil {
localAddr = addressEndpoint.AddressWithPrefix().Address
addressEndpoint.DecRef()
}
@@ -1722,7 +1792,7 @@ func (ndp *ndpState) startSolicitingRouters() {
header.NDPSourceLinkLayerAddressOption(linkAddress),
}
}
payloadSize := header.ICMPv6HeaderSize + header.NDPRSMinimumSize + int(optsSerializer.Length())
payloadSize := header.ICMPv6HeaderSize + header.NDPRSMinimumSize + optsSerializer.Length()
icmpData := header.ICMPv6(buffer.NewView(payloadSize))
icmpData.SetType(header.ICMPv6RouterSolicit)
rs := header.NDPRouterSolicit(icmpData.MessageBody())
@@ -1730,23 +1800,24 @@ func (ndp *ndpState) startSolicitingRouters() {
icmpData.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpData,
Src: localAddr,
Dst: header.IPv6AllRoutersMulticastAddress,
Dst: header.IPv6AllRoutersLinkLocalMulticastAddress,
}))
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(ndp.ep.MaxHeaderLength()),
Data: buffer.View(icmpData).ToVectorisedView(),
})
defer pkt.DecRef()
sent := ndp.ep.stats.icmp.packetsSent
if err := addIPHeader(localAddr, header.IPv6AllRoutersMulticastAddress, pkt, stack.NetworkHeaderParams{
if err := addIPHeader(localAddr, header.IPv6AllRoutersLinkLocalMulticastAddress, pkt, stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: header.NDPHopLimit,
}, nil /* extensionHeaders */); err != nil {
panic(fmt.Sprintf("failed to add IP header: %s", err))
}
if err := ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersMulticastAddress), nil /* gso */, ProtocolNumber, pkt); err != nil {
if err := ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersLinkLocalMulticastAddress), ProtocolNumber, pkt); err != nil {
sent.dropped.Increment()
// Don't send any more messages if we had an error.
remaining = 0
@@ -1774,6 +1845,32 @@ func (ndp *ndpState) startSolicitingRouters() {
}
}
// forwardingChanged handles a change in forwarding configuration.
//
// If transitioning to a host, router solicitation will be started. Otherwise,
// router solicitation will be stopped if NDP is not configured to handle RAs
// as a router.
//
// Precondition: ndp.ep.mu must be locked.
func (ndp *ndpState) forwardingChanged(forwarding bool) {
if forwarding {
if ndp.configs.HandleRAs.enabled(forwarding) {
return
}
ndp.stopSolicitingRouters()
return
}
// Solicit routers when transitioning to a host.
//
// If the endpoint is not currently enabled, routers will be solicited when
// the endpoint becomes enabled (if it is still a host).
if ndp.ep.Enabled() {
ndp.startSolicitingRouters()
}
}
// stopSolicitingRouters stops soliciting routers. If routers are not currently
// being solicited, this function does nothing.
//
@@ -1790,21 +1887,19 @@ func (ndp *ndpState) stopSolicitingRouters() {
}
func (ndp *ndpState) init(ep *endpoint, dadOptions ip.DADOptions) {
if ndp.defaultRouters != nil {
if ndp.offLinkRoutes != nil {
panic("attempted to initialize NDP state twice")
}
ndp.ep = ep
ndp.configs = ep.protocol.options.NDPConfigs
ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, dadOptions)
ndp.defaultRouters = make(map[tcpip.Address]defaultRouterState)
ndp.offLinkRoutes = make(map[offLinkRoute]offLinkRouteState)
ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState)
ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState)
header.InitialTempIID(ndp.temporaryIIDHistory[:], ndp.ep.protocol.options.TempIIDSeed, ndp.ep.nic.ID())
if MaxDesyncFactor != 0 {
ndp.temporaryAddressDesyncFactor = time.Duration(rand.Int63n(int64(MaxDesyncFactor)))
}
ndp.temporaryAddressDesyncFactor = time.Duration(ep.protocol.stack.Rand().Int63n(int64(MaxDesyncFactor)))
}
func (ndp *ndpState) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
@@ -1830,6 +1925,7 @@ func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteL
ReserveHeaderBytes: int(e.MaxHeaderLength()),
Data: buffer.View(icmp).ToVectorisedView(),
})
defer pkt.DecRef()
if err := addIPHeader(srcAddr, dstAddr, pkt, stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
@@ -1839,7 +1935,7 @@ func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteL
}
sent := e.stats.icmp.packetsSent
err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt)
err := e.nic.WritePacketToRemote(remoteLinkAddr, ProtocolNumber, pkt)
if err != nil {
sent.dropped.Increment()
} else {
+4
View File
@@ -29,6 +29,10 @@ type Stats struct {
// ICMP holds ICMPv6 statistics.
ICMP tcpip.ICMPv6Stats
// UnhandledRouterAdvertisements is the number of Router Advertisements that
// were observed but not handled.
UnhandledRouterAdvertisements *tcpip.StatCounter
}
// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
+45 -29
View File
@@ -17,14 +17,19 @@
package ports
import (
"math"
"math/rand"
"sync/atomic"
"inet.af/netstack/sync"
"inet.af/netstack/tcpip"
"inet.af/netstack/tcpip/header"
)
const anyIPAddress tcpip.Address = ""
const (
firstEphemeral = 16000
anyIPAddress tcpip.Address = ""
)
// Reservation describes a port reservation.
type Reservation struct {
@@ -97,7 +102,7 @@ func (dc destToCounter) intersectionFlags(res Reservation) (BitFlags, int) {
// Wildcard destinations affect all destinations for TupleOnly.
if dest.addr == anyIPAddress || res.Dest.Addr == anyIPAddress {
// Only bitwise and the TupleOnlyFlag.
intersection &= ((^TupleOnlyFlag) | counter.SharedFlags())
intersection &= (^TupleOnlyFlag) | counter.SharedFlags()
count++
}
}
@@ -118,7 +123,7 @@ type deviceToDest map[tcpip.NICID]destToCounter
// If either of the port reuse flags is enabled on any of the nodes, all nodes
// sharing a port must share at least one reuse flag. This matches Linux's
// behavior.
func (dd deviceToDest) isAvailable(res Reservation) bool {
func (dd deviceToDest) isAvailable(res Reservation, portSpecified bool) bool {
flagBits := res.Flags.Bits()
if res.BindToDevice == 0 {
intersection := FlagMask
@@ -134,6 +139,9 @@ func (dd deviceToDest) isAvailable(res Reservation) bool {
return false
}
}
if !portSpecified && res.Transport == header.TCPProtocolNumber {
return false
}
return true
}
@@ -142,16 +150,26 @@ func (dd deviceToDest) isAvailable(res Reservation) bool {
if dests, ok := dd[0]; ok {
var count int
intersection, count = dests.intersectionFlags(res)
if count > 0 && intersection&flagBits == 0 {
return false
if count > 0 {
if intersection&flagBits == 0 {
return false
}
if !portSpecified && res.Transport == header.TCPProtocolNumber {
return false
}
}
}
if dests, ok := dd[res.BindToDevice]; ok {
flags, count := dests.intersectionFlags(res)
intersection &= flags
if count > 0 && intersection&flagBits == 0 {
return false
if count > 0 {
if intersection&flagBits == 0 {
return false
}
if !portSpecified && res.Transport == header.TCPProtocolNumber {
return false
}
}
}
@@ -164,12 +182,12 @@ type addrToDevice map[tcpip.Address]deviceToDest
// isAvailable checks whether an IP address is available to bind to. If the
// address is the "any" address, check all other addresses. Otherwise, just
// check against the "any" address and the provided address.
func (ad addrToDevice) isAvailable(res Reservation) bool {
func (ad addrToDevice) isAvailable(res Reservation, portSpecified bool) bool {
if res.Addr == anyIPAddress {
// If binding to the "any" address then check that there are no
// conflicts with all addresses.
for _, devices := range ad {
if !devices.isAvailable(res) {
if !devices.isAvailable(res, portSpecified) {
return false
}
}
@@ -178,14 +196,14 @@ func (ad addrToDevice) isAvailable(res Reservation) bool {
// Check that there is no conflict with the "any" address.
if devices, ok := ad[anyIPAddress]; ok {
if !devices.isAvailable(res) {
if !devices.isAvailable(res, portSpecified) {
return false
}
}
// Check that this is no conflict with the provided address.
if devices, ok := ad[res.Addr]; ok {
if !devices.isAvailable(res) {
if !devices.isAvailable(res, portSpecified) {
return false
}
}
@@ -220,10 +238,8 @@ type PortManager struct {
func NewPortManager() *PortManager {
return &PortManager{
allocatedPorts: make(map[portDescriptor]addrToDevice),
// Match Linux's default ephemeral range. See:
// https://github.com/torvalds/linux/blob/e54937963fa249595824439dc839c948188dea83/net/ipv4/af_inet.c#L1842
firstEphemeral: 32768,
numEphemeral: 28232,
firstEphemeral: firstEphemeral,
numEphemeral: math.MaxUint16 - firstEphemeral + 1,
}
}
@@ -236,19 +252,19 @@ type PortTester func(port uint16) (good bool, err tcpip.Error)
// possible ephemeral ports, allowing the caller to decide whether a given port
// is suitable for its needs, and stopping when a port is found or an error
// occurs.
func (pm *PortManager) PickEphemeralPort(testPort PortTester) (port uint16, err tcpip.Error) {
func (pm *PortManager) PickEphemeralPort(rng *rand.Rand, testPort PortTester) (port uint16, err tcpip.Error) {
pm.ephemeralMu.RLock()
firstEphemeral := pm.firstEphemeral
numEphemeral := pm.numEphemeral
pm.ephemeralMu.RUnlock()
offset := uint16(rand.Int31n(int32(numEphemeral)))
offset := uint32(rng.Int31n(int32(numEphemeral)))
return pickEphemeralPort(offset, firstEphemeral, numEphemeral, testPort)
}
// portHint atomically reads and returns the pm.hint value.
func (pm *PortManager) portHint() uint16 {
return uint16(atomic.LoadUint32(&pm.hint))
func (pm *PortManager) portHint() uint32 {
return atomic.LoadUint32(&pm.hint)
}
// incPortHint atomically increments pm.hint by 1.
@@ -260,7 +276,7 @@ func (pm *PortManager) incPortHint() {
// iterates over all ephemeral ports, allowing the caller to decide whether a
// given port is suitable for its needs and stopping when a port is found or an
// error occurs.
func (pm *PortManager) PickEphemeralPortStable(offset uint16, testPort PortTester) (port uint16, err tcpip.Error) {
func (pm *PortManager) PickEphemeralPortStable(offset uint32, testPort PortTester) (port uint16, err tcpip.Error) {
pm.ephemeralMu.RLock()
firstEphemeral := pm.firstEphemeral
numEphemeral := pm.numEphemeral
@@ -277,9 +293,9 @@ func (pm *PortManager) PickEphemeralPortStable(offset uint16, testPort PortTeste
// and iterates over the number of ports specified by count and allows the
// caller to decide whether a given port is suitable for its needs, and stopping
// when a port is found or an error occurs.
func pickEphemeralPort(offset, first, count uint16, testPort PortTester) (port uint16, err tcpip.Error) {
for i := uint16(0); i < count; i++ {
port = first + (offset+i)%count
func pickEphemeralPort(offset uint32, first, count uint16, testPort PortTester) (port uint16, err tcpip.Error) {
for i := uint32(0); i < uint32(count); i++ {
port := uint16(uint32(first) + (offset+i)%uint32(count))
ok, err := testPort(port)
if err != nil {
return 0, err
@@ -301,14 +317,14 @@ func pickEphemeralPort(offset, first, count uint16, testPort PortTester) (port u
// An optional PortTester can be passed in which if provided will be used to
// test if the picked port can be used. The function should return true if the
// port is safe to use, false otherwise.
func (pm *PortManager) ReservePort(res Reservation, testPort PortTester) (reservedPort uint16, err tcpip.Error) {
func (pm *PortManager) ReservePort(rng *rand.Rand, res Reservation, testPort PortTester) (reservedPort uint16, err tcpip.Error) {
pm.mu.Lock()
defer pm.mu.Unlock()
// If a port is specified, just try to reserve it for all network
// protocols.
if res.Port != 0 {
if !pm.reserveSpecificPortLocked(res) {
if !pm.reserveSpecificPortLocked(res, true /* portSpecified */) {
return 0, &tcpip.ErrPortInUse{}
}
if testPort != nil {
@@ -326,9 +342,9 @@ func (pm *PortManager) ReservePort(res Reservation, testPort PortTester) (reserv
}
// A port wasn't specified, so try to find one.
return pm.PickEphemeralPort(func(p uint16) (bool, tcpip.Error) {
return pm.PickEphemeralPort(rng, func(p uint16) (bool, tcpip.Error) {
res.Port = p
if !pm.reserveSpecificPortLocked(res) {
if !pm.reserveSpecificPortLocked(res, false /* portSpecified */) {
return false, nil
}
if testPort != nil {
@@ -348,12 +364,12 @@ func (pm *PortManager) ReservePort(res Reservation, testPort PortTester) (reserv
// reserveSpecificPortLocked tries to reserve the given port on all given
// protocols.
func (pm *PortManager) reserveSpecificPortLocked(res Reservation) bool {
func (pm *PortManager) reserveSpecificPortLocked(res Reservation, portSpecified bool) bool {
// Make sure the port is available.
for _, network := range res.Networks {
desc := portDescriptor{network, res.Transport, res.Port}
if addrs, ok := pm.allocatedPorts[desc]; ok {
if !addrs.isAvailable(res) {
if !addrs.isAvailable(res, portSpecified) {
return false
}
}
@@ -20,6 +20,7 @@ func (f *Flags) StateFields() []string {
func (f *Flags) beforeSave() {}
// +checklocksignore
func (f *Flags) StateSave(stateSinkObject state.Sink) {
f.beforeSave()
stateSinkObject.Save(0, &f.MostRecent)
@@ -29,6 +30,7 @@ func (f *Flags) StateSave(stateSinkObject state.Sink) {
func (f *Flags) afterLoad() {}
// +checklocksignore
func (f *Flags) StateLoad(stateSourceObject state.Source) {
stateSourceObject.Load(0, &f.MostRecent)
stateSourceObject.Load(1, &f.LoadBalanced)
+80 -38
View File
@@ -15,16 +15,12 @@
package tcpip
import (
"math"
"sync/atomic"
"inet.af/netstack/atomicbitops"
"inet.af/netstack/sync"
)
// PacketOverheadFactor is used to multiply the value provided by the user on a
// SetSockOpt for setting the send/receive buffer sizes sockets.
const PacketOverheadFactor = 2
// SocketOptionsHandler holds methods that help define endpoint specific
// behavior for socket level socket options. These must be implemented by
// endpoints to get notified when socket level options are set.
@@ -58,6 +54,14 @@ type SocketOptionsHandler interface {
// changed. The handler is invoked with the new value for the socket send
// buffer size. It also returns the newly set value.
OnSetSendBufferSize(v int64) (newSz int64)
// OnSetReceiveBufferSize is invoked by SO_RCVBUF and SO_RCVBUFFORCE.
OnSetReceiveBufferSize(v, oldSz int64) (newSz int64)
// WakeupWriters is invoked when the send buffer size for an endpoint is
// changed. The handler notifies the writers if the send buffer size is
// increased with setsockopt(2) for TCP endpoints.
WakeupWriters()
}
// DefaultSocketOptionsHandler is an embeddable type that implements no-op
@@ -99,6 +103,14 @@ func (*DefaultSocketOptionsHandler) OnSetSendBufferSize(v int64) (newSz int64) {
return v
}
// WakeupWriters implements SocketOptionsHandler.WakeupWriters.
func (*DefaultSocketOptionsHandler) WakeupWriters() {}
// OnSetReceiveBufferSize implements SocketOptionsHandler.OnSetReceiveBufferSize.
func (*DefaultSocketOptionsHandler) OnSetReceiveBufferSize(v, oldSz int64) (newSz int64) {
return v
}
// StackHandler holds methods to access the stack options. These must be
// implemented by the stack.
type StackHandler interface {
@@ -158,10 +170,14 @@ type SocketOptions struct {
// message is passed with incoming packets.
receiveTClassEnabled uint32
// receivePacketInfoEnabled is used to specify if more inforamtion is
// provided with incoming packets such as interface index and address.
// receivePacketInfoEnabled is used to specify if more information is
// provided with incoming IPv4 packets.
receivePacketInfoEnabled uint32
// receivePacketInfoEnabled is used to specify if more information is
// provided with incoming IPv6 packets.
receiveIPv6PacketInfoEnabled uint32
// hdrIncludeEnabled is used to indicate for a raw endpoint that all packets
// being written have an IP header and the endpoint should not attach an IP
// header.
@@ -205,7 +221,15 @@ type SocketOptions struct {
getSendBufferLimits GetSendBufferLimits `state:"manual"`
// sendBufferSize determines the send buffer size for this socket.
sendBufferSize int64
sendBufferSize atomicbitops.AlignedAtomicInt64
// getReceiveBufferLimits provides the handler to get the min, default and
// max size for receive buffer. It is initialized at the creation time and
// will not change.
getReceiveBufferLimits GetReceiveBufferLimits `state:"manual"`
// receiveBufferSize determines the receive buffer size for this socket.
receiveBufferSize atomicbitops.AlignedAtomicInt64
// mu protects the access to the below fields.
mu sync.Mutex `state:"nosave"`
@@ -217,10 +241,11 @@ type SocketOptions struct {
// InitHandler initializes the handler. This must be called before using the
// socket options utility.
func (so *SocketOptions) InitHandler(handler SocketOptionsHandler, stack StackHandler, getSendBufferLimits GetSendBufferLimits) {
func (so *SocketOptions) InitHandler(handler SocketOptionsHandler, stack StackHandler, getSendBufferLimits GetSendBufferLimits, getReceiveBufferLimits GetReceiveBufferLimits) {
so.handler = handler
so.stackHandler = stack
so.getSendBufferLimits = getSendBufferLimits
so.getReceiveBufferLimits = getReceiveBufferLimits
}
func storeAtomicBool(addr *uint32, v bool) {
@@ -339,6 +364,16 @@ func (so *SocketOptions) SetReceivePacketInfo(v bool) {
storeAtomicBool(&so.receivePacketInfoEnabled, v)
}
// GetIPv6ReceivePacketInfo gets value for IPV6_RECVPKTINFO option.
func (so *SocketOptions) GetIPv6ReceivePacketInfo() bool {
return atomic.LoadUint32(&so.receiveIPv6PacketInfoEnabled) != 0
}
// SetIPv6ReceivePacketInfo sets value for IPV6_RECVPKTINFO option.
func (so *SocketOptions) SetIPv6ReceivePacketInfo(v bool) {
storeAtomicBool(&so.receiveIPv6PacketInfoEnabled, v)
}
// GetHeaderIncluded gets value for IP_HDRINCL option.
func (so *SocketOptions) GetHeaderIncluded() bool {
return atomic.LoadUint32(&so.hdrIncludedEnabled) != 0
@@ -583,9 +618,10 @@ func (so *SocketOptions) GetBindToDevice() int32 {
return atomic.LoadInt32(&so.bindToDevice)
}
// SetBindToDevice sets value for SO_BINDTODEVICE option.
// SetBindToDevice sets value for SO_BINDTODEVICE option. If bindToDevice is
// zero, the socket device binding is removed.
func (so *SocketOptions) SetBindToDevice(bindToDevice int32) Error {
if !so.handler.HasNIC(bindToDevice) {
if bindToDevice != 0 && !so.handler.HasNIC(bindToDevice) {
return &ErrUnknownDevice{}
}
@@ -595,40 +631,46 @@ func (so *SocketOptions) SetBindToDevice(bindToDevice int32) Error {
// GetSendBufferSize gets value for SO_SNDBUF option.
func (so *SocketOptions) GetSendBufferSize() int64 {
return atomic.LoadInt64(&so.sendBufferSize)
return so.sendBufferSize.Load()
}
// SendBufferLimits returns the [min, max) range of allowable send buffer
// sizes.
func (so *SocketOptions) SendBufferLimits() (min, max int64) {
limits := so.getSendBufferLimits(so.stackHandler)
return int64(limits.Min), int64(limits.Max)
}
// SetSendBufferSize sets value for SO_SNDBUF option. notify indicates if the
// stack handler should be invoked to set the send buffer size.
func (so *SocketOptions) SetSendBufferSize(sendBufferSize int64, notify bool) {
v := sendBufferSize
if !notify {
atomic.StoreInt64(&so.sendBufferSize, v)
return
if notify {
sendBufferSize = so.handler.OnSetSendBufferSize(sendBufferSize)
}
// Make sure the send buffer size is within the min and max
// allowed.
ss := so.getSendBufferLimits(so.stackHandler)
min := int64(ss.Min)
max := int64(ss.Max)
// Validate the send buffer size with min and max values.
// Multiply it by factor of 2.
if v > max {
v = max
so.sendBufferSize.Store(sendBufferSize)
if notify {
so.handler.WakeupWriters()
}
}
if v < math.MaxInt32/PacketOverheadFactor {
v *= PacketOverheadFactor
if v < min {
v = min
}
} else {
v = math.MaxInt32
// GetReceiveBufferSize gets value for SO_RCVBUF option.
func (so *SocketOptions) GetReceiveBufferSize() int64 {
return so.receiveBufferSize.Load()
}
// ReceiveBufferLimits returns the [min, max) range of allowable receive buffer
// sizes.
func (so *SocketOptions) ReceiveBufferLimits() (min, max int64) {
limits := so.getReceiveBufferLimits(so.stackHandler)
return int64(limits.Min), int64(limits.Max)
}
// SetReceiveBufferSize sets the value of the SO_RCVBUF option, optionally
// notifying the owning endpoint.
func (so *SocketOptions) SetReceiveBufferSize(receiveBufferSize int64, notify bool) {
if notify {
oldSz := so.receiveBufferSize.Load()
receiveBufferSize = so.handler.OnSetReceiveBufferSize(receiveBufferSize, oldSz)
}
// Notify endpoint about change in buffer size.
newSz := so.handler.OnSetSendBufferSize(v)
atomic.StoreInt64(&so.sendBufferSize, newSz)
so.receiveBufferSize.Store(receiveBufferSize)
}
@@ -117,10 +117,10 @@ func (a *AddressableEndpointState) releaseAddressStateLocked(addrState *addressS
}
// AddAndAcquirePermanentAddress implements AddressableEndpoint.
func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior, configType AddressConfigType, deprecated bool) (AddressEndpoint, tcpip.Error) {
func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, properties AddressProperties) (AddressEndpoint, tcpip.Error) {
a.mu.Lock()
defer a.mu.Unlock()
ep, err := a.addAndAcquireAddressLocked(addr, peb, configType, deprecated, true /* permanent */)
ep, err := a.addAndAcquireAddressLocked(addr, properties, true /* permanent */)
// From https://golang.org/doc/faq#nil_error:
//
// Under the covers, interfaces are implemented as two elements, a type T and
@@ -149,7 +149,7 @@ func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.Addr
func (a *AddressableEndpointState) AddAndAcquireTemporaryAddress(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior) (AddressEndpoint, tcpip.Error) {
a.mu.Lock()
defer a.mu.Unlock()
ep, err := a.addAndAcquireAddressLocked(addr, peb, AddressConfigStatic, false /* deprecated */, false /* permanent */)
ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: peb}, false /* permanent */)
// From https://golang.org/doc/faq#nil_error:
//
// Under the covers, interfaces are implemented as two elements, a type T and
@@ -180,7 +180,7 @@ func (a *AddressableEndpointState) AddAndAcquireTemporaryAddress(addr tcpip.Addr
// returned, regardless the kind of address that is being added.
//
// Precondition: a.mu must be write locked.
func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior, configType AddressConfigType, deprecated, permanent bool) (*addressState, tcpip.Error) {
func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.AddressWithPrefix, properties AddressProperties, permanent bool) (*addressState, tcpip.Error) {
// attemptAddToPrimary is false when the address is already in the primary
// address list.
attemptAddToPrimary := true
@@ -208,7 +208,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address
// We now promote the address.
for i, s := range a.mu.primary {
if s == addrState {
switch peb {
switch properties.PEB {
case CanBePrimaryEndpoint:
// The address is already in the primary address list.
attemptAddToPrimary = false
@@ -222,7 +222,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address
case NeverPrimaryEndpoint:
a.mu.primary = append(a.mu.primary[:i], a.mu.primary[i+1:]...)
default:
panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", peb))
panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", properties.PEB))
}
break
}
@@ -249,7 +249,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address
// or we are adding a new temporary or permanent address.
//
// The address MUST be write locked at this point.
defer addrState.mu.Unlock()
defer addrState.mu.Unlock() // +checklocksforce
if permanent {
if addrState.mu.kind.IsPermanent() {
@@ -262,11 +262,11 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address
}
// Acquire the address before returning it.
addrState.mu.refs++
addrState.mu.deprecated = deprecated
addrState.mu.configType = configType
addrState.mu.deprecated = properties.Deprecated
addrState.mu.configType = properties.ConfigType
if attemptAddToPrimary {
switch peb {
switch properties.PEB {
case NeverPrimaryEndpoint:
case CanBePrimaryEndpoint:
a.mu.primary = append(a.mu.primary, addrState)
@@ -285,7 +285,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address
a.mu.primary[0] = addrState
}
default:
panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", peb))
panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", properties.PEB))
}
}
@@ -440,41 +440,63 @@ func (a *AddressableEndpointState) acquirePrimaryAddressRLocked(isValid func(*ad
// Regardless how the address was obtained, it will be acquired before it is
// returned.
func (a *AddressableEndpointState) AcquireAssignedAddressOrMatching(localAddr tcpip.Address, f func(AddressEndpoint) bool, allowTemp bool, tempPEB PrimaryEndpointBehavior) AddressEndpoint {
a.mu.Lock()
defer a.mu.Unlock()
lookup := func() *addressState {
if addrState, ok := a.mu.endpoints[localAddr]; ok {
if !addrState.IsAssigned(allowTemp) {
return nil
}
if addrState, ok := a.mu.endpoints[localAddr]; ok {
if !addrState.IsAssigned(allowTemp) {
return nil
if !addrState.IncRef() {
panic(fmt.Sprintf("failed to increase the reference count for address = %s", addrState.addr))
}
return addrState
}
if !addrState.IncRef() {
panic(fmt.Sprintf("failed to increase the reference count for address = %s", addrState.addr))
}
return addrState
}
if f != nil {
for _, addrState := range a.mu.endpoints {
if addrState.IsAssigned(allowTemp) && f(addrState) && addrState.IncRef() {
return addrState
if f != nil {
for _, addrState := range a.mu.endpoints {
if addrState.IsAssigned(allowTemp) && f(addrState) && addrState.IncRef() {
return addrState
}
}
}
return nil
}
// Avoid exclusive lock on mu unless we need to add a new address.
a.mu.RLock()
ep := lookup()
a.mu.RUnlock()
if ep != nil {
return ep
}
if !allowTemp {
return nil
}
// Acquire state lock in exclusive mode as we need to add a new temporary
// endpoint.
a.mu.Lock()
defer a.mu.Unlock()
// Do the lookup again in case another goroutine added the address in the time
// we released and acquired the lock.
ep = lookup()
if ep != nil {
return ep
}
// Proceed to add a new temporary endpoint.
addr := localAddr.WithPrefix()
ep, err := a.addAndAcquireAddressLocked(addr, tempPEB, AddressConfigStatic, false /* deprecated */, false /* permanent */)
ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: tempPEB}, false /* permanent */)
if err != nil {
// addAndAcquireAddressLocked only returns an error if the address is
// already assigned but we just checked above if the address exists so we
// expect no error.
panic(fmt.Sprintf("a.addAndAcquireAddressLocked(%s, %d, %d, false, false): %s", addr, tempPEB, AddressConfigStatic, err))
panic(fmt.Sprintf("a.addAndAcquireAddressLocked(%s, AddressProperties{PEB: %s}, false): %s", addr, tempPEB, err))
}
// From https://golang.org/doc/faq#nil_error:
//
// Under the covers, interfaces are implemented as two elements, a type T and
File diff suppressed because it is too large. Load diff
+41
View File
@@ -0,0 +1,41 @@
// 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.
// Code generated by "stringer -type Hook ."; DO NOT EDIT.
package stack
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[Prerouting-0]
_ = x[Input-1]
_ = x[Forward-2]
_ = x[Output-3]
_ = x[Postrouting-4]
_ = x[NumHooks-5]
}
const _Hook_name = "PreroutingInputForwardOutputPostroutingNumHooks"
var _Hook_index = [...]uint8{0, 10, 15, 22, 28, 39, 47}
func (i Hook) String() string {
if i >= Hook(len(_Hook_index)-1) {
return "Hook(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Hook_name[_Hook_index[i]:_Hook_index[i+1]]
}
+35 -4
View File
@@ -16,6 +16,7 @@ package stack
import (
"golang.org/x/time/rate"
"inet.af/netstack/tcpip"
)
const (
@@ -31,11 +32,41 @@ const (
// ICMPRateLimiter is a global rate limiter that controls the generation of
// ICMP messages generated by the stack.
type ICMPRateLimiter struct {
*rate.Limiter
limiter *rate.Limiter
clock tcpip.Clock
}
// NewICMPRateLimiter returns a global rate limiter for controlling the rate
// at which ICMP messages are generated by the stack.
func NewICMPRateLimiter() *ICMPRateLimiter {
return &ICMPRateLimiter{Limiter: rate.NewLimiter(icmpLimit, icmpBurst)}
// at which ICMP messages are generated by the stack. The returned limiter
// does not apply limits to any ICMP types by default.
func NewICMPRateLimiter(clock tcpip.Clock) *ICMPRateLimiter {
return &ICMPRateLimiter{
clock: clock,
limiter: rate.NewLimiter(icmpLimit, icmpBurst),
}
}
// SetLimit sets a new Limit for the limiter.
func (l *ICMPRateLimiter) SetLimit(limit rate.Limit) {
l.limiter.SetLimitAt(l.clock.Now(), limit)
}
// Limit returns the maximum overall event rate.
func (l *ICMPRateLimiter) Limit() rate.Limit {
return l.limiter.Limit()
}
// SetBurst sets a new burst size for the limiter.
func (l *ICMPRateLimiter) SetBurst(burst int) {
l.limiter.SetBurstAt(l.clock.Now(), burst)
}
// Burst returns the maximum burst size.
func (l *ICMPRateLimiter) Burst() int {
return l.limiter.Burst()
}
// Allow reports whether one ICMP message may be sent now.
func (l *ICMPRateLimiter) Allow() bool {
return l.limiter.AllowN(l.clock.Now(), 1)
}
+286 -102
View File
@@ -16,6 +16,7 @@ package stack
import (
"fmt"
"math/rand"
"time"
"inet.af/netstack/tcpip"
@@ -42,7 +43,7 @@ const reaperDelay = 5 * time.Second
// DefaultTables returns a default set of tables. Each chain is set to accept
// all packets.
func DefaultTables() *IPTables {
func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
return &IPTables{
v4Tables: [NumTables]Table{
NATID: {
@@ -174,13 +175,10 @@ func DefaultTables() *IPTables {
},
},
},
priorities: [NumHooks][]TableID{
Prerouting: {MangleID, NATID},
Input: {NATID, FilterID},
Output: {MangleID, NATID, FilterID},
},
connections: ConnTrack{
seed: generateRandUint32(),
seed: rand.Uint32(),
clock: clock,
rand: rand,
},
reaperDone: make(chan struct{}, 1),
}
@@ -254,7 +252,7 @@ const (
// chainAccept indicates the packet should continue through netstack.
chainAccept chainVerdict = iota
// chainAccept indicates the packet should be dropped.
// chainDrop indicates the packet should be dropped.
chainDrop
// chainReturn indicates the packet should return to the calling chain
@@ -262,91 +260,260 @@ const (
chainReturn
)
// Check runs pkt through the rules for hook. It returns true when the packet
// should continue traversing the network stack and false when it should be
// dropped.
// CheckPrerouting performs the prerouting hook on the packet.
//
// TODO(gvisor.dev/issue/170): PacketBuffer should hold the GSO and route, from
// which address can be gathered. Currently, address is only needed for
// prerouting.
// Returns true iff the packet may continue traversing the stack; the packet
// must be dropped if false is returned.
//
// Precondition: pkt.NetworkHeader is set.
func (it *IPTables) Check(hook Hook, pkt *PacketBuffer, gso *GSO, r *Route, preroutingAddr tcpip.Address, inNicName, outNicName string) bool {
if pkt.NetworkProtocolNumber != header.IPv4ProtocolNumber && pkt.NetworkProtocolNumber != header.IPv6ProtocolNumber {
return true
}
// Many users never configure iptables. Spare them the cost of rule
// traversal if rules have never been set.
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckPrerouting(pkt *PacketBuffer, addressEP AddressableEndpoint, inNicName string) bool {
it.mu.RLock()
defer it.mu.RUnlock()
if !it.modified {
if it.shouldSkipRLocked(pkt.NetworkProtocolNumber) {
return true
}
// Packets are manipulated only if connection and matching
// NAT rule exists.
shouldTrack := it.connections.handlePacket(pkt, hook, gso, r)
pkt.tuple = it.connections.getConnAndUpdate(pkt)
// Go through each table containing the hook.
priorities := it.priorities[hook]
for _, tableID := range priorities {
// If handlePacket already NATed the packet, we don't need to
// check the NAT table.
if tableID == NATID && pkt.NatDone {
continue
}
var table Table
if pkt.NetworkProtocolNumber == header.IPv6ProtocolNumber {
table = it.v6Tables[tableID]
} else {
table = it.v4Tables[tableID]
}
ruleIdx := table.BuiltinChains[hook]
switch verdict := it.checkChain(hook, pkt, table, ruleIdx, gso, r, preroutingAddr, inNicName, outNicName); verdict {
// If the table returns Accept, move on to the next table.
case chainAccept:
continue
// The Drop verdict is final.
case chainDrop:
for _, check := range [...]checkTableFn{
it.checkMangleRLocked,
it.checkNATRLocked,
} {
if !check(Prerouting, pkt, nil /* route */, addressEP, inNicName, "" /* outNicName */) {
return false
case chainReturn:
// Any Return from a built-in chain means we have to
// call the underflow.
underflow := table.Rules[table.Underflows[hook]]
switch v, _ := underflow.Target.Action(pkt, &it.connections, hook, gso, r, preroutingAddr); v {
case RuleAccept:
continue
case RuleDrop:
return false
case RuleJump, RuleReturn:
panic("Underflows should only return RuleAccept or RuleDrop.")
default:
panic(fmt.Sprintf("Unknown verdict: %d", v))
}
default:
panic(fmt.Sprintf("Unknown verdict %v.", verdict))
}
}
// If this connection should be tracked, try to add an entry for it. If
// traversing the nat table didn't end in adding an entry,
// maybeInsertNoop will add a no-op entry for the connection. This is
// needeed when establishing connections so that the SYN/ACK reply to an
// outgoing SYN is delivered to the correct endpoint rather than being
// redirected by a prerouting rule.
//
// From the iptables documentation: "If there is no rule, a `null'
// binding is created: this usually does not map the packet, but exists
// to ensure we don't map another stream over an existing one."
if shouldTrack {
it.connections.maybeInsertNoop(pkt, hook)
return true
}
// CheckInput performs the input hook on the packet.
//
// Returns true iff the packet may continue traversing the stack; the packet
// must be dropped if false is returned.
//
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckInput(pkt *PacketBuffer, inNicName string) bool {
it.mu.RLock()
defer it.mu.RUnlock()
if it.shouldSkipRLocked(pkt.NetworkProtocolNumber) {
return true
}
// Every table returned Accept.
for _, check := range [...]checkTableFn{
it.checkNATRLocked,
it.checkFilterRLocked,
} {
if !check(Input, pkt, nil /* route */, nil /* addressEP */, inNicName, "" /* outNicName */) {
return false
}
}
if t := pkt.tuple; t != nil {
pkt.tuple = nil
return t.conn.finalize()
}
return true
}
// CheckForward performs the forward hook on the packet.
//
// Returns true iff the packet may continue traversing the stack; the packet
// must be dropped if false is returned.
//
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckForward(pkt *PacketBuffer, inNicName, outNicName string) bool {
it.mu.RLock()
defer it.mu.RUnlock()
if it.shouldSkipRLocked(pkt.NetworkProtocolNumber) {
return true
}
return it.checkFilterRLocked(Forward, pkt, nil /* route */, nil /* addressEP */, inNicName, outNicName)
}
// CheckOutput performs the output hook on the packet.
//
// Returns true iff the packet may continue traversing the stack; the packet
// must be dropped if false is returned.
//
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string) bool {
it.mu.RLock()
defer it.mu.RUnlock()
if it.shouldSkipRLocked(pkt.NetworkProtocolNumber) {
return true
}
pkt.tuple = it.connections.getConnAndUpdate(pkt)
for _, check := range [...]checkTableFn{
it.checkMangleRLocked,
it.checkNATRLocked,
it.checkFilterRLocked,
} {
if !check(Output, pkt, r, nil /* addressEP */, "" /* inNicName */, outNicName) {
return false
}
}
return true
}
// CheckPostrouting performs the postrouting hook on the packet.
//
// Returns true iff the packet may continue traversing the stack; the packet
// must be dropped if false is returned.
//
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckPostrouting(pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, outNicName string) bool {
it.mu.RLock()
defer it.mu.RUnlock()
if it.shouldSkipRLocked(pkt.NetworkProtocolNumber) {
return true
}
for _, check := range [...]checkTableFn{
it.checkMangleRLocked,
it.checkNATRLocked,
} {
if !check(Postrouting, pkt, r, addressEP, "" /* inNicName */, outNicName) {
return false
}
}
if t := pkt.tuple; t != nil {
pkt.tuple = nil
return t.conn.finalize()
}
return true
}
// +checklocksread:it.mu
func (it *IPTables) shouldSkipRLocked(netProto tcpip.NetworkProtocolNumber) bool {
switch netProto {
case header.IPv4ProtocolNumber, header.IPv6ProtocolNumber:
default:
// IPTables only supports IPv4/IPv6.
return true
}
// Many users never configure iptables. Spare them the cost of rule
// traversal if rules have never been set.
return !it.modified
}
type checkTableFn func(hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool
// checkMangleRLocked runs the packet through the mangle table.
//
// See checkRLocked.
//
// +checklocksread:it.mu
func (it *IPTables) checkMangleRLocked(hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
return it.checkRLocked(MangleID, hook, pkt, r, addressEP, inNicName, outNicName)
}
// checkNATRLocked runs the packet through the NAT table.
//
// See checkRLocked.
//
// +checklocksread:it.mu
func (it *IPTables) checkNATRLocked(hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
t := pkt.tuple
if t != nil && t.conn.handlePacket(pkt, hook, r) {
return true
}
if !it.checkRLocked(NATID, hook, pkt, r, addressEP, inNicName, outNicName) {
return false
}
if t == nil {
return true
}
var dnat bool
var natDone *bool
switch hook {
case Prerouting, Output:
dnat = true
natDone = &pkt.DNATDone
case Input, Postrouting:
dnat = false
natDone = &pkt.SNATDone
case Forward:
panic("should not attempt NAT in forwarding")
default:
panic(fmt.Sprintf("unhandled hook = %d", hook))
}
// Make sure the connection is NATed.
//
// If the packet was already NATed, the connection must be NATed.
if !*natDone {
t.conn.maybePerformNoopNAT(dnat)
_ = t.conn.handlePacket(pkt, hook, r)
}
return true
}
// checkFilterRLocked runs the packet through the filter table.
//
// See checkRLocked.
//
// +checklocksread:it.mu
func (it *IPTables) checkFilterRLocked(hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
return it.checkRLocked(FilterID, hook, pkt, r, addressEP, inNicName, outNicName)
}
// checkRLocked runs the packet through the rules in the specified table for the
// hook. It returns true if the packet should continue to traverse through the
// network stack or tables, or false when it must be dropped.
//
// Precondition: The packet's network and transport header must be set.
//
// +checklocksread:it.mu
func (it *IPTables) checkRLocked(tableID TableID, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
var table Table
if pkt.NetworkProtocolNumber == header.IPv6ProtocolNumber {
table = it.v6Tables[tableID]
} else {
table = it.v4Tables[tableID]
}
ruleIdx := table.BuiltinChains[hook]
switch verdict := it.checkChain(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName); verdict {
// If the table returns Accept, move on to the next table.
case chainAccept:
return true
// The Drop verdict is final.
case chainDrop:
return false
case chainReturn:
// Any Return from a built-in chain means we have to
// call the underflow.
underflow := table.Rules[table.Underflows[hook]]
switch v, _ := underflow.Target.Action(pkt, hook, r, addressEP); v {
case RuleAccept:
return true
case RuleDrop:
return false
case RuleJump, RuleReturn:
panic("Underflows should only return RuleAccept or RuleDrop.")
default:
panic(fmt.Sprintf("Unknown verdict: %d", v))
}
default:
panic(fmt.Sprintf("Unknown verdict %v.", verdict))
}
}
// beforeSave is invoked by stateify.
func (it *IPTables) beforeSave() {
// Ensure the reaper exits cleanly.
@@ -369,6 +536,7 @@ func (it *IPTables) startReaper(interval time.Duration) {
select {
case <-it.reaperDone:
return
// TODO(gvisor.dev/issue/5939): do not use the ambient clock.
case <-time.After(interval):
bucket, interval = it.connections.reapUnused(bucket, interval)
}
@@ -376,30 +544,46 @@ func (it *IPTables) startReaper(interval time.Duration) {
}()
}
// CheckPackets runs pkts through the rules for hook and returns a map of packets that
// should not go forward.
// CheckOutputPackets performs the output hook on the packets.
//
// Preconditions:
// * pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
// * pkt.NetworkHeader is not nil.
// Returns a map of packets that must be dropped.
//
// NOTE: unlike the Check API the returned map contains packets that should be
// dropped.
func (it *IPTables) CheckPackets(hook Hook, pkts PacketBufferList, gso *GSO, r *Route, inNicName, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
// Precondition: The packets' network and transport header must be set.
func (it *IPTables) CheckOutputPackets(pkts PacketBufferList, r *Route, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
return checkPackets(pkts, func(pkt *PacketBuffer) bool {
return it.CheckOutput(pkt, r, outNicName)
}, true /* dnat */)
}
// CheckPostroutingPackets performs the postrouting hook on the packets.
//
// Returns a map of packets that must be dropped.
//
// Precondition: The packets' network and transport header must be set.
func (it *IPTables) CheckPostroutingPackets(pkts PacketBufferList, r *Route, addressEP AddressableEndpoint, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
return checkPackets(pkts, func(pkt *PacketBuffer) bool {
return it.CheckPostrouting(pkt, r, addressEP, outNicName)
}, false /* dnat */)
}
func checkPackets(pkts PacketBufferList, f func(*PacketBuffer) bool, dnat bool) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
if !pkt.NatDone {
if ok := it.Check(hook, pkt, gso, r, "", inNicName, outNicName); !ok {
if drop == nil {
drop = make(map[*PacketBuffer]struct{})
}
drop[pkt] = struct{}{}
natDone := &pkt.SNATDone
if dnat {
natDone = &pkt.DNATDone
}
if ok := f(pkt); !ok {
if drop == nil {
drop = make(map[*PacketBuffer]struct{})
}
if pkt.NatDone {
if natPkts == nil {
natPkts = make(map[*PacketBuffer]struct{})
}
natPkts[pkt] = struct{}{}
drop[pkt] = struct{}{}
}
if *natDone {
if natPkts == nil {
natPkts = make(map[*PacketBuffer]struct{})
}
natPkts[pkt] = struct{}{}
}
}
return drop, natPkts
@@ -408,11 +592,11 @@ func (it *IPTables) CheckPackets(hook Hook, pkts PacketBufferList, gso *GSO, r *
// Preconditions:
// * pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
// * pkt.NetworkHeader is not nil.
func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, gso *GSO, r *Route, preroutingAddr tcpip.Address, inNicName, outNicName string) chainVerdict {
func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) chainVerdict {
// Start from ruleIdx and walk the list of rules until a rule gives us
// a verdict.
for ruleIdx < len(table.Rules) {
switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx, gso, r, preroutingAddr, inNicName, outNicName); verdict {
switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName); verdict {
case RuleAccept:
return chainAccept
@@ -429,7 +613,7 @@ func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleId
ruleIdx++
continue
}
switch verdict := it.checkChain(hook, pkt, table, jumpTo, gso, r, preroutingAddr, inNicName, outNicName); verdict {
switch verdict := it.checkChain(hook, pkt, table, jumpTo, r, addressEP, inNicName, outNicName); verdict {
case chainAccept:
return chainAccept
case chainDrop:
@@ -455,7 +639,7 @@ func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleId
// Preconditions:
// * pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
// * pkt.NetworkHeader is not nil.
func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, gso *GSO, r *Route, preroutingAddr tcpip.Address, inNicName, outNicName string) (RuleVerdict, int) {
func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) (RuleVerdict, int) {
rule := table.Rules[ruleIdx]
// Check whether the packet matches the IP header filter.
@@ -478,16 +662,16 @@ func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx
}
// All the matchers matched, so run the target.
return rule.Target.Action(pkt, &it.connections, hook, gso, r, preroutingAddr)
return rule.Target.Action(pkt, hook, r, addressEP)
}
// OriginalDst returns the original destination of redirected connections. It
// returns an error if the connection doesn't exist or isn't redirected.
func (it *IPTables) OriginalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
func (it *IPTables) OriginalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
it.mu.RLock()
defer it.mu.RUnlock()
if !it.modified {
return "", 0, &tcpip.ErrNotConnected{}
}
return it.connections.originalDst(epID, netProto)
return it.connections.originalDst(epID, netProto, transProto)
}
+195 -59
View File
@@ -16,6 +16,7 @@ package stack
import (
"fmt"
"math"
"inet.af/netstack/log"
"inet.af/netstack/tcpip"
@@ -29,7 +30,7 @@ type AcceptTarget struct {
}
// Action implements Target.Action.
func (*AcceptTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
func (*AcceptTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleAccept, 0
}
@@ -40,7 +41,7 @@ type DropTarget struct {
}
// Action implements Target.Action.
func (*DropTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
func (*DropTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleDrop, 0
}
@@ -52,7 +53,7 @@ type ErrorTarget struct {
}
// Action implements Target.Action.
func (*ErrorTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
func (*ErrorTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
log.Debugf("ErrorTarget triggered.")
return RuleDrop, 0
}
@@ -67,7 +68,7 @@ type UserChainTarget struct {
}
// Action implements Target.Action.
func (*UserChainTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
func (*UserChainTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
panic("UserChainTarget should never be called.")
}
@@ -79,17 +80,53 @@ type ReturnTarget struct {
}
// Action implements Target.Action.
func (*ReturnTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
func (*ReturnTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleReturn, 0
}
// DNATTarget modifies the destination port/IP of packets.
type DNATTarget struct {
// The new destination address for packets.
//
// Immutable.
Addr tcpip.Address
// The new destination port for packets.
//
// Immutable.
Port uint16
// NetworkProtocol is the network protocol the target is used with.
//
// Immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (rt *DNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"DNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
rt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Prerouting, Output:
case Input, Forward, Postrouting:
panic(fmt.Sprintf("%s not supported for DNAT", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
return dnatAction(pkt, hook, r, rt.Port, rt.Addr)
}
// RedirectTarget redirects the packet to this machine by modifying the
// destination port/IP. Outgoing packets are redirected to the loopback device,
// and incoming packets are redirected to the incoming interface (rather than
// forwarded).
//
// TODO(gvisor.dev/issue/170): Other flags need to be added after we support
// them.
type RedirectTarget struct {
// Port indicates port used to redirect. It is immutable.
Port uint16
@@ -100,10 +137,7 @@ type RedirectTarget struct {
}
// Action implements Target.Action.
// TODO(gvisor.dev/issue/170): Parse headers without copying. The current
// implementation only works for Prerouting and calls pkt.Clone(), neither
// of which should be the case.
func (rt *RedirectTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, gso *GSO, r *Route, address tcpip.Address) (RuleVerdict, int) {
func (rt *RedirectTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
@@ -111,18 +145,9 @@ func (rt *RedirectTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, gs
rt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
// Packet is already manipulated.
if pkt.NatDone {
return RuleAccept, 0
}
// Drop the packet if network and transport header are not set.
if pkt.NetworkHeader().View().IsEmpty() || pkt.TransportHeader().View().IsEmpty() {
return RuleDrop, 0
}
// Change the address to loopback (127.0.0.1 or ::1) in Output and to
// the primary address of the incoming interface in Prerouting.
var address tcpip.Address
switch hook {
case Output:
if pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
@@ -131,54 +156,165 @@ func (rt *RedirectTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, gs
address = header.IPv6Loopback
}
case Prerouting:
// No-op, as address is already set correctly.
// addressEP is expected to be set for the prerouting hook.
address = addressEP.MainAddress().Address
default:
panic("redirect target is supported only on output and prerouting hooks")
}
// TODO(gvisor.dev/issue/170): Check Flags in RedirectTarget if
// we need to change dest address (for OUTPUT chain) or ports.
switch protocol := pkt.TransportProtocolNumber; protocol {
case header.UDPProtocolNumber:
udpHeader := header.UDP(pkt.TransportHeader().View())
udpHeader.SetDestinationPort(rt.Port)
return dnatAction(pkt, hook, r, rt.Port, address)
}
// Calculate UDP checksum and set it.
if hook == Output {
udpHeader.SetChecksum(0)
netHeader := pkt.Network()
netHeader.SetDestinationAddress(address)
// SNATTarget modifies the source port/IP in the outgoing packets.
type SNATTarget struct {
Addr tcpip.Address
Port uint16
// Only calculate the checksum if offloading isn't supported.
if r.RequiresTXTransportChecksum() {
length := uint16(pkt.Size()) - uint16(len(pkt.NetworkHeader().View()))
xsum := header.PseudoHeaderChecksum(protocol, netHeader.SourceAddress(), netHeader.DestinationAddress(), length)
xsum = header.ChecksumCombine(xsum, pkt.Data().AsRange().Checksum())
udpHeader.SetChecksum(^udpHeader.CalculateChecksum(xsum))
}
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
func dnatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
return natAction(pkt, hook, r, portRange{start: port, size: 1}, address, true /* dnat */)
}
func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
ports := portRange{start: port, size: 1}
if port == 0 {
// As per iptables(8),
//
// If no port range is specified, then source ports below 512 will be
// mapped to other ports below 512: those between 512 and 1023 inclusive
// will be mapped to ports below 1024, and other ports will be mapped to
// 1024 or above.
switch protocol := pkt.TransportProtocolNumber; protocol {
case header.UDPProtocolNumber:
port = header.UDP(pkt.TransportHeader().View()).SourcePort()
case header.TCPProtocolNumber:
port = header.TCP(pkt.TransportHeader().View()).SourcePort()
default:
panic(fmt.Sprintf("unsupported transport protocol = %d", pkt.TransportProtocolNumber))
}
// After modification, IPv4 packets need a valid checksum.
if pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
netHeader := header.IPv4(pkt.NetworkHeader().View())
netHeader.SetChecksum(0)
netHeader.SetChecksum(^netHeader.CalculateChecksum())
}
pkt.NatDone = true
case header.TCPProtocolNumber:
if ct == nil {
return RuleAccept, 0
switch {
case port < 512:
ports = portRange{start: 1, size: 511}
case port < 1024:
ports = portRange{start: 1, size: 1023}
default:
ports = portRange{start: 1024, size: math.MaxUint16 - 1023}
}
}
// Set up conection for matching NAT rule. Only the first
// packet of the connection comes here. Other packets will be
// manipulated in connection tracking.
if conn := ct.insertRedirectConn(pkt, hook, rt.Port, address); conn != nil {
ct.handlePacket(pkt, hook, gso, r)
}
default:
return natAction(pkt, hook, r, ports, address, false /* dnat */)
}
func natAction(pkt *PacketBuffer, hook Hook, r *Route, ports portRange, address tcpip.Address, dnat bool) (RuleVerdict, int) {
// Drop the packet if network and transport header are not set.
if pkt.NetworkHeader().View().IsEmpty() || pkt.TransportHeader().View().IsEmpty() {
return RuleDrop, 0
}
return RuleAccept, 0
if t := pkt.tuple; t != nil {
t.conn.performNAT(pkt, hook, r, ports, address, dnat)
return RuleAccept, 0
}
return RuleDrop, 0
}
// Action implements Target.Action.
func (st *SNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if st.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"SNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
st.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting, Input:
case Prerouting, Output, Forward:
panic(fmt.Sprintf("%s not supported", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
return snatAction(pkt, hook, r, st.Port, st.Addr)
}
// MasqueradeTarget modifies the source port/IP in the outgoing packets.
type MasqueradeTarget struct {
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if mt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"MasqueradeTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
mt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting:
case Prerouting, Input, Forward, Output:
panic(fmt.Sprintf("masquerade target is supported only on postrouting hook; hook = %d", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
// addressEP is expected to be set for the postrouting hook.
ep := addressEP.AcquireOutgoingPrimaryAddress(pkt.Network().DestinationAddress(), false /* allowExpired */)
if ep == nil {
// No address exists that we can use as a source address.
return RuleDrop, 0
}
address := ep.AddressWithPrefix().Address
ep.DecRef()
return snatAction(pkt, hook, r, 0 /* port */, address)
}
func rewritePacket(n header.Network, t header.ChecksummableTransport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
if updateSRCFields {
if fullChecksum {
t.SetSourcePortWithChecksumUpdate(newPort)
} else {
t.SetSourcePort(newPort)
}
} else {
if fullChecksum {
t.SetDestinationPortWithChecksumUpdate(newPort)
} else {
t.SetDestinationPort(newPort)
}
}
if updatePseudoHeader {
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
}
if checksummableNetHeader, ok := n.(header.ChecksummableNetwork); ok {
if updateSRCFields {
checksummableNetHeader.SetSourceAddressWithChecksumUpdate(newAddr)
} else {
checksummableNetHeader.SetDestinationAddressWithChecksumUpdate(newAddr)
}
} else if updateSRCFields {
n.SetSourceAddress(newAddr)
} else {
n.SetDestinationAddress(newAddr)
}
}
+27 -21
View File
@@ -81,26 +81,25 @@ const (
//
// +stateify savable
type IPTables struct {
// mu protects v4Tables, v6Tables, and modified.
mu sync.RWMutex
// v4Tables and v6tables map tableIDs to tables. They hold builtin
// tables only, not user tables. mu must be locked for accessing.
v4Tables [NumTables]Table
v6Tables [NumTables]Table
// modified is whether tables have been modified at least once. It is
// used to elide the iptables performance overhead for workloads that
// don't utilize iptables.
modified bool
// priorities maps each hook to a list of table names. The order of the
// list is the order in which each table should be visited for that
// hook. It is immutable.
priorities [NumHooks][]TableID
connections ConnTrack
// reaperDone can be signaled to stop the reaper goroutine.
reaperDone chan struct{}
mu sync.RWMutex
// v4Tables and v6tables map tableIDs to tables. They hold builtin
// tables only, not user tables.
//
// +checklocks:mu
v4Tables [NumTables]Table
// +checklocks:mu
v6Tables [NumTables]Table
// modified is whether tables have been modified at least once. It is
// used to elide the iptables performance overhead for workloads that
// don't utilize iptables.
//
// +checklocks:mu
modified bool
}
// VisitTargets traverses all the targets of all tables and replaces each with
@@ -242,7 +241,6 @@ type IPHeaderFilter struct {
func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicName string) bool {
// Extract header fields.
var (
// TODO(gvisor.dev/issue/170): Support other filter fields.
transProto tcpip.TransportProtocolNumber
dstAddr tcpip.Address
srcAddr tcpip.Address
@@ -280,9 +278,17 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicNa
return matchIfName(inNicName, fl.InputInterface, fl.InputInterfaceInvert)
case Output:
return matchIfName(outNicName, fl.OutputInterface, fl.OutputInterfaceInvert)
case Forward, Postrouting:
// TODO(gvisor.dev/issue/170): Add the check for FORWARD and POSTROUTING
// hooks after supported.
case Forward:
if !matchIfName(inNicName, fl.InputInterface, fl.InputInterfaceInvert) {
return false
}
if !matchIfName(outNicName, fl.OutputInterface, fl.OutputInterfaceInvert) {
return false
}
return true
case Postrouting:
return true
default:
panic(fmt.Sprintf("unknown hook: %d", hook))
@@ -345,5 +351,5 @@ type Target interface {
// Action takes an action on the packet and returns a verdict on how
// traversal should (or should not) continue. If the return value is
// Jump, it also returns the index of the rule to jump to.
Action(packet *PacketBuffer, connections *ConnTrack, hook Hook, gso *GSO, r *Route, address tcpip.Address) (RuleVerdict, int)
Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int)
}
+1 -1
View File
@@ -310,7 +310,7 @@ func (n *neighborCache) handleUpperLevelConfirmation(addr tcpip.Address) {
func (n *neighborCache) init(nic *nic, r LinkAddressResolver) {
*n = neighborCache{
nic: nic,
state: NewNUDState(nic.stack.nudConfigs, nic.stack.randomGenerator),
state: NewNUDState(nic.stack.nudConfigs, nic.stack.clock, nic.stack.randomGenerator),
linkRes: r,
}
n.mu.Lock()
+24 -18
View File
@@ -31,10 +31,10 @@ const (
// NeighborEntry describes a neighboring device in the local network.
type NeighborEntry struct {
Addr tcpip.Address
LinkAddr tcpip.LinkAddress
State NeighborState
UpdatedAtNanos int64
Addr tcpip.Address
LinkAddr tcpip.LinkAddress
State NeighborState
UpdatedAt time.Time
}
// NeighborState defines the state of a NeighborEntry within the Neighbor
@@ -138,10 +138,10 @@ func newNeighborEntry(cache *neighborCache, remoteAddr tcpip.Address, nudState *
// calling `setStateLocked`.
func newStaticNeighborEntry(cache *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress, state *NUDState) *neighborEntry {
entry := NeighborEntry{
Addr: addr,
LinkAddr: linkAddr,
State: Static,
UpdatedAtNanos: cache.nic.stack.clock.NowNanoseconds(),
Addr: addr,
LinkAddr: linkAddr,
State: Static,
UpdatedAt: cache.nic.stack.clock.Now(),
}
n := &neighborEntry{
cache: cache,
@@ -166,14 +166,20 @@ func (e *neighborEntry) notifyCompletionLocked(err tcpip.Error) {
if ch := e.mu.done; ch != nil {
close(ch)
e.mu.done = nil
// Dequeue the pending packets in a new goroutine to not hold up the current
// Dequeue the pending packets asynchronously to not hold up the current
// goroutine as writing packets may be a costly operation.
//
// At the time of writing, when writing packets, a neighbor's link address
// is resolved (which ends up obtaining the entry's lock) while holding the
// link resolution queue's lock. Dequeuing packets in a new goroutine avoids
// a lock ordering violation.
go e.cache.nic.linkResQueue.dequeue(ch, e.mu.neigh.LinkAddr, err)
// link resolution queue's lock. Dequeuing packets asynchronously avoids a
// lock ordering violation.
//
// NB: this is equivalent to spawning a goroutine directly using the go
// keyword but allows tests that use manual clocks to deterministically
// wait for this work to complete.
e.cache.nic.stack.clock.AfterFunc(0, func() {
e.cache.nic.linkResQueue.dequeue(ch, e.mu.neigh.LinkAddr, err)
})
}
}
@@ -224,7 +230,7 @@ func (e *neighborEntry) cancelTimerLocked() {
//
// Precondition: e.mu MUST be locked.
func (e *neighborEntry) removeLocked() {
e.mu.neigh.UpdatedAtNanos = e.cache.nic.stack.clock.NowNanoseconds()
e.mu.neigh.UpdatedAt = e.cache.nic.stack.clock.Now()
e.dispatchRemoveEventLocked()
e.cancelTimerLocked()
// TODO(https://gvisor.dev/issues/5583): test the case where this function is
@@ -246,7 +252,7 @@ func (e *neighborEntry) setStateLocked(next NeighborState) {
prev := e.mu.neigh.State
e.mu.neigh.State = next
e.mu.neigh.UpdatedAtNanos = e.cache.nic.stack.clock.NowNanoseconds()
e.mu.neigh.UpdatedAt = e.cache.nic.stack.clock.Now()
config := e.nudState.Config()
switch next {
@@ -307,7 +313,7 @@ func (e *neighborEntry) setStateLocked(next NeighborState) {
// a shared lock.
e.mu.timer = timer{
done: &done,
timer: e.cache.nic.stack.Clock().AfterFunc(0, func() {
timer: e.cache.nic.stack.Clock().AfterFunc(immediateDuration, func() {
var err tcpip.Error = &tcpip.ErrTimeout{}
if remaining != 0 {
err = e.cache.linkRes.LinkAddressRequest(addr, "" /* localAddr */, linkAddr)
@@ -354,14 +360,14 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
case Unknown, Unreachable:
prev := e.mu.neigh.State
e.mu.neigh.State = Incomplete
e.mu.neigh.UpdatedAtNanos = e.cache.nic.stack.clock.NowNanoseconds()
e.mu.neigh.UpdatedAt = e.cache.nic.stack.clock.Now()
switch prev {
case Unknown:
e.dispatchAddEventLocked()
case Unreachable:
e.dispatchChangeEventLocked()
e.cache.nic.stats.Neighbor.UnreachableEntryLookups.Increment()
e.cache.nic.stats.neighbor.unreachableEntryLookups.Increment()
}
config := e.nudState.Config()
@@ -378,7 +384,7 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
// a shared lock.
e.mu.timer = timer{
done: &done,
timer: e.cache.nic.stack.Clock().AfterFunc(0, func() {
timer: e.cache.nic.stack.Clock().AfterFunc(immediateDuration, func() {
var err tcpip.Error = &tcpip.ErrTimeout{}
if remaining != 0 {
// As per RFC 4861 section 7.2.2:
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff. Show more