Skip to content

Commit

Permalink
udp: endpoint-independent mapping mk1
Browse files Browse the repository at this point in the history
  • Loading branch information
ignoramous committed Aug 20, 2024
1 parent 205c9ba commit aaef320
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 204 deletions.
20 changes: 20 additions & 0 deletions intra/netstack/hdl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import (
"strings"
"sync"

"github.com/celzero/firestack/intra/settings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

Expand Down Expand Up @@ -134,3 +137,20 @@ func nsaddr2ip(addr tcpip.Address) net.IP {
b := addr.AsSlice()
return net.IP(b)
}

func addrport2nsaddr(ipp netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var proto tcpip.NetworkProtocolNumber
var addr tcpip.Address
if ipp.Addr().Is4() {
proto = ipv4.ProtocolNumber
addr = tcpip.AddrFrom4(ipp.Addr().As4())
} else {
proto = ipv6.ProtocolNumber
addr = tcpip.AddrFrom16(ipp.Addr().As16())
}
return tcpip.FullAddress{
NIC: settings.NICID,
Addr: addr,
Port: ipp.Port(),
}, proto
}
75 changes: 43 additions & 32 deletions intra/netstack/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/celzero/firestack/intra/core"
"github.com/celzero/firestack/intra/log"
"github.com/celzero/firestack/intra/settings"
"gvisor.dev/gvisor/pkg/tcpip"

"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"

Expand All @@ -30,7 +29,7 @@ type GUDPConnHandler interface {
// Proxy proxies data between conn (src) and dst.
Proxy(conn *GUDPConn, src, dst netip.AddrPort) bool
// ProxyMux proxies data between conn and multiple destinations.
ProxyMux(conn *GUDPConn, src netip.AddrPort) bool
ProxyMux(conn *GUDPConn, src, dst netip.AddrPort) bool
// Error notes the error in connecting src to dst.
Error(conn *GUDPConn, src, dst netip.AddrPort, err error)
// CloseConns closes conns by ids, or all if ids is empty.
Expand All @@ -42,21 +41,26 @@ type GUDPConnHandler interface {
var _ core.UDPConn = (*GUDPConn)(nil)

type GUDPConn struct {
c *core.Volatile[*gonet.UDPConn] // conn exposes UDP semantics atop endpoint
ep *core.Volatile[tcpip.Endpoint] // ep is the endpoint for netstack io
src netip.AddrPort // local addr (remote addr in netstack)
dst netip.AddrPort // remote addr (local addr in netstack)
req *udp.ForwarderRequest // egress request as UDP
stack *stack.Stack
c *core.Volatile[*gonet.UDPConn] // conn exposes UDP semantics atop endpoint
src netip.AddrPort // local addr (remote addr in netstack)
dst netip.AddrPort // remote addr (local addr in netstack)
req *udp.ForwarderRequest // egress request as UDP

eim bool // endpoint is muxed
eif bool // endpoint is transparent
}

// ref: github.com/google/gvisor/blob/e89e736f1/pkg/tcpip/adapters/gonet/gonet_test.go#L373
func makeGUDPConn(r *udp.ForwarderRequest, src, dst netip.AddrPort) *GUDPConn {
func makeGUDPConn(s *stack.Stack, r *udp.ForwarderRequest, src, dst netip.AddrPort) *GUDPConn {
return &GUDPConn{
c: core.NewZeroVolatile[*gonet.UDPConn](),
ep: core.NewZeroVolatile[tcpip.Endpoint](),
src: src,
dst: dst,
req: r,
stack: s,
c: core.NewZeroVolatile[*gonet.UDPConn](),
src: src,
dst: dst,
req: r,
eim: settings.EndpointIndependentMapping.Load(),
eif: settings.EndpointIndependentFiltering.Load(),
}
}

Expand Down Expand Up @@ -90,11 +94,11 @@ func udpForwarder(s *stack.Stack, h GUDPConnHandler) *udp.Forwarder {
// multiple dst in the unconnected udp case.
dst := localAddrPort(id)

gc := makeGUDPConn(req, src, dst)
gc := makeGUDPConn(s, req, src, dst)
// setup to recv right away, so that netstack's internal state is consistent
// in case there are multiple forwarders dispatching from the TUN device.
if !settings.SingleThreaded.Load() {
if err := gc.tryConnect(); err != nil {
if err := gc.Establish(); err != nil {
log.E("ns: udp: forwarder: connect: %v; src(%v) dst(%v)", err, src, dst)
go h.Error(gc, src, dst, err)
return
Expand All @@ -103,10 +107,10 @@ func udpForwarder(s *stack.Stack, h GUDPConnHandler) *udp.Forwarder {

// proxy in a separate gorountine; return immediately
// why? netstack/dispatcher.go:newReadvDispatcher
if gc.connected() { // gc is connected udp; proxy it like a stream
go h.Proxy(gc, src, dst)
if gc.eim {
go h.ProxyMux(gc, src, dst)
} else {
go h.ProxyMux(gc, src)
go h.Proxy(gc, src, dst)
}
})
}
Expand All @@ -119,17 +123,16 @@ func (g *GUDPConn) conn() *gonet.UDPConn {
return g.c.Load()
}

func (g *GUDPConn) endpoint() tcpip.Endpoint {
return g.ep.Load()
}

func (g *GUDPConn) StatefulTeardown() (fin bool) {
_ = g.tryConnect() // establish circuit then teardown
_ = g.Close() // then shutdown
return true // always fin
}

func (g *GUDPConn) Connect() error {
func (g *GUDPConn) Establish() error {
if g.eif {
return g.tryBind()
}
return g.tryConnect()
}

Expand All @@ -139,18 +142,33 @@ func (g *GUDPConn) tryConnect() error {
}

wq := new(waiter.Queue)
// use gonet.DialUDP instead?
if endpoint, err := g.req.CreateEndpoint(wq); err != nil {
// ex: CONNECT endpoint for [fd66:f83a:c650::1]:15753 => [fd66:f83a:c650::3]:53; err(no route to host)
log.E("ns: udp: connect: endpoint for %v => %v; err(%v)", g.src, g.dst, err)
return e(err)
} else {
g.ep.Store(endpoint)
g.c.Store(gonet.NewUDPConn(wq, endpoint))
}
return nil
}

func (g *GUDPConn) tryBind() error {
if g.ok() { // already setup
return nil
}

src, proto := addrport2nsaddr(g.src)
// unconnected socket w/ gonet.DialUDP
if conn, err := gonet.DialUDP(g.stack, &src, nil, proto); err != nil {
log.E("ns: udp: bind: endpoint for %v [=> %v]; err(%v)", g.src, g.dst, err)
return err
} else {
// todo: handle the first pkt like in g.req.CreateEndpoint
g.c.Store(conn)
}
return nil
}

func (g *GUDPConn) LocalAddr() (addr net.Addr) {
if c := g.conn(); c != nil {
addr = c.RemoteAddr()
Expand Down Expand Up @@ -227,15 +245,8 @@ func (g *GUDPConn) SetWriteDeadline(t time.Time) error {

// Close closes the connection.
func (g *GUDPConn) Close() error {
if ep := g.endpoint(); ep != nil {
ep.Abort()
}
if c := g.conn(); c != nil {
_ = c.Close()
}
return nil
}

func (g *GUDPConn) connected() bool {
return !g.dst.Addr().IsUnspecified()
}
8 changes: 8 additions & 0 deletions intra/settings/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ var Loopingback = atomic.Bool{}
// in a single-threaded mode.
var SingleThreaded = atomic.Bool{}

// EndpointIndependentMapping is a global flag to enable endpoint-independent
// mapping for UDP as per RFC 4787.
var EndpointIndependentMapping = atomic.Bool{}

// EndpointIndependentFiltering is a global flag to enable endpoint-independent
// filtering for UDP as per RFC 4787.
var EndpointIndependentFiltering = atomic.Bool{}

// L3 returns the string'd repr of engine.
func L3(engine int) string {
switch engine {
Expand Down
Loading

1 comment on commit aaef320

@ignoramous
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#77

Please sign in to comment.