netstack.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Package amneziawgnet embeds amneziawg-go (a userspace AmneziaWG
  2. // implementation, https://github.com/amnezia-vpn/amneziawg-go) directly in
  3. // the panel process, as an alternative to internal/amneziawg's
  4. // kernel-module (DKMS) + awg-quick approach. A gVisor userspace network
  5. // stack (gvisor.dev/gvisor/pkg/tcpip -- already an indirect dependency via
  6. // xray-core's own proxy/wireguard support) terminates each tunnel, and a
  7. // forwarder recovers each connection's real, dynamically-arbitrary
  8. // destination for the caller to relay onward (see Phase 2 of the migration
  9. // plan: a loopback SOCKS5 dial into Xray, giving native stats/routing/
  10. // sniffing for free).
  11. package amneziawgnet
  12. import (
  13. "fmt"
  14. "net/netip"
  15. "os"
  16. "syscall"
  17. awgtun "github.com/amnezia-vpn/amneziawg-go/v3/tun"
  18. "gvisor.dev/gvisor/pkg/buffer"
  19. "gvisor.dev/gvisor/pkg/tcpip"
  20. "gvisor.dev/gvisor/pkg/tcpip/header"
  21. "gvisor.dev/gvisor/pkg/tcpip/link/channel"
  22. "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
  23. "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
  24. "gvisor.dev/gvisor/pkg/tcpip/stack"
  25. "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
  26. "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
  27. "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
  28. )
  29. // tunQueueDepth is the outbound packet queue depth for both the gVisor
  30. // channel endpoint and the handoff channel to amneziawg-go's TUN reader
  31. // (see the stackTun literal in createNetTUNWithStack for why both need it).
  32. const tunQueueDepth = 1024
  33. // stackTun implements amneziawg-go's tun.Device directly against a gVisor
  34. // channel endpoint, the same approach amneziawg-go's own tun/netstack
  35. // package and xray-core's proxy/wireguard/netstack.go both take. Neither of
  36. // those exposes the raw *stack.Stack a forwarder needs (amneziawg-go's Net
  37. // type keeps it unexported), so this is a local, from-source reimplementation
  38. // rather than a wrapper -- adapted from amneziawg-go v3.0.3's
  39. // tun/netstack/tun.go (MIT licensed), trimmed to the constructor this
  40. // package needs.
  41. type stackTun struct {
  42. ep *channel.Endpoint
  43. stack *stack.Stack
  44. events chan awgtun.Event
  45. notifyHandle *channel.NotificationHandle
  46. incomingPacket chan *buffer.View
  47. mtu int
  48. }
  49. // createNetTUNWithStack builds a gVisor-backed tun.Device for the given
  50. // local addresses (interface address(es), one per family) and returns the
  51. // underlying *stack.Stack alongside it so a caller can attach a forwarder
  52. // (see forwarder.go / udp.go).
  53. func createNetTUNWithStack(localAddresses []netip.Addr, mtu int) (awgtun.Device, *stack.Stack, error) {
  54. opts := stack.Options{
  55. NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
  56. TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
  57. // HandleLocal must stay false: promiscuous+spoofing mode (see
  58. // forwarder.go) is what lets a destination other than the stack's
  59. // own configured address reach the forwarder at all.
  60. HandleLocal: false,
  61. }
  62. dev := &stackTun{
  63. // tunQueueDepth matches channel.New's own outbound queue depth
  64. // below. WriteNotify (called synchronously from whatever gVisor
  65. // goroutine is sending TCP data for the download/server->client
  66. // direction) pushes into incomingPacket; RoutineReadFromTUN (a
  67. // single amneziawg-go goroutine that encrypts and sends each
  68. // packet over UDP) is the only reader. With no buffer, every
  69. // outbound packet forced a full synchronous handoff between the
  70. // two -- gVisor's sender blocked until the encrypt loop was ready
  71. // for the next one, one packet at a time, no pipelining. The
  72. // upload/client->server direction has no equivalent stall:
  73. // Write->InjectInbound->DeliverNetworkPacket hands off into
  74. // gVisor's own ~1MB per-connection TCP receive buffer and returns
  75. // immediately. Buffering this channel gives the download
  76. // direction the same slack the upload direction already had.
  77. ep: channel.New(tunQueueDepth, uint32(mtu), ""),
  78. stack: stack.New(opts),
  79. events: make(chan awgtun.Event, 10),
  80. incomingPacket: make(chan *buffer.View, tunQueueDepth),
  81. mtu: mtu,
  82. }
  83. sackEnabledOpt := tcpip.TCPSACKEnabled(true)
  84. if err := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt); err != nil {
  85. return nil, nil, fmt.Errorf("amneziawgnet: enable TCP SACK: %s", err)
  86. }
  87. dev.notifyHandle = dev.ep.AddNotify(dev)
  88. if err := dev.stack.CreateNIC(1, dev.ep); err != nil {
  89. return nil, nil, fmt.Errorf("amneziawgnet: CreateNIC: %s", err)
  90. }
  91. var hasV4, hasV6 bool
  92. for _, ip := range localAddresses {
  93. var protoNumber tcpip.NetworkProtocolNumber
  94. switch {
  95. case ip.Is4():
  96. protoNumber = ipv4.ProtocolNumber
  97. hasV4 = true
  98. case ip.Is6():
  99. protoNumber = ipv6.ProtocolNumber
  100. hasV6 = true
  101. default:
  102. continue
  103. }
  104. protoAddr := tcpip.ProtocolAddress{
  105. Protocol: protoNumber,
  106. AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
  107. }
  108. if err := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{}); err != nil {
  109. return nil, nil, fmt.Errorf("amneziawgnet: AddProtocolAddress(%v): %s", ip, err)
  110. }
  111. }
  112. if hasV4 {
  113. dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
  114. }
  115. if hasV6 {
  116. dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
  117. }
  118. dev.events <- awgtun.EventUp
  119. return dev, dev.stack, nil
  120. }
  121. func (t *stackTun) Name() (string, error) { return "amneziawgnet", nil }
  122. func (t *stackTun) File() *os.File { return nil }
  123. func (t *stackTun) Events() <-chan awgtun.Event { return t.events }
  124. func (t *stackTun) MTU() (int, error) { return t.mtu, nil }
  125. func (t *stackTun) BatchSize() int { return 1 }
  126. // Read blocks for the first packet, then opportunistically drains any more
  127. // that are already buffered (non-blocking), up to len(buf). amneziawg-go's
  128. // caller (RoutineReadFromTUN) sizes buf/sizes to device.BatchSize(), which
  129. // is the UDP bind's own batch size (128 on Linux, see conn.IdealBatchSize)
  130. // since that's larger than BatchSize()'s 1 below -- so real buffer capacity
  131. // for a batch is already there. Without this drain loop, Read always
  132. // returned exactly one packet no matter how many buf could hold, so every
  133. // downstream step (peer lookup, per-peer staging, and ultimately the UDP
  134. // bind's own genuinely batched Send/sendmmsg) processed the download
  135. // direction one packet at a time while the upload direction's equivalent
  136. // (bind.Receive/recvmmsg -> decrypt -> stackTun.Write, which already loops
  137. // over its whole buf) processed up to 128 per cycle. That asymmetry is
  138. // real, not gVisor/amneziawg-go's -- both the receive and send paths on the
  139. // UDP bind support batching identically, only this Read implementation
  140. // didn't use it.
  141. func (t *stackTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
  142. view, ok := <-t.incomingPacket
  143. if !ok {
  144. return 0, os.ErrClosed
  145. }
  146. n, err := view.Read(buf[0][offset:])
  147. if err != nil {
  148. return 0, err
  149. }
  150. sizes[0] = n
  151. count := 1
  152. for count < len(buf) {
  153. select {
  154. case view, ok := <-t.incomingPacket:
  155. if !ok {
  156. return count, nil
  157. }
  158. n, err := view.Read(buf[count][offset:])
  159. if err != nil {
  160. return count, nil
  161. }
  162. sizes[count] = n
  163. count++
  164. default:
  165. return count, nil
  166. }
  167. }
  168. return count, nil
  169. }
  170. func (t *stackTun) Write(buf [][]byte, offset int) (int, error) {
  171. for _, b := range buf {
  172. packet := b[offset:]
  173. if len(packet) == 0 {
  174. continue
  175. }
  176. pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
  177. switch packet[0] >> 4 {
  178. case 4:
  179. t.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
  180. case 6:
  181. t.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
  182. default:
  183. return 0, syscall.EAFNOSUPPORT
  184. }
  185. }
  186. return len(buf), nil
  187. }
  188. func (t *stackTun) WriteNotify() {
  189. pkt := t.ep.Read()
  190. if pkt == nil {
  191. return
  192. }
  193. view := pkt.ToView()
  194. pkt.DecRef()
  195. t.incomingPacket <- view
  196. }
  197. func (t *stackTun) Close() error {
  198. t.stack.RemoveNIC(1)
  199. t.stack.Close()
  200. t.ep.RemoveNotify(t.notifyHandle)
  201. t.ep.Close()
  202. if t.events != nil {
  203. close(t.events)
  204. }
  205. if t.incomingPacket != nil {
  206. close(t.incomingPacket)
  207. }
  208. return nil
  209. }
  210. // enablePromiscuousRouting puts the NIC into promiscuous + spoofing mode,
  211. // the precondition both AttachTCPForwarder and AttachUDPHandler need to see
  212. // packets addressed to a destination other than the stack's own configured
  213. // local address. Safe to call from both (and more than once): gVisor's
  214. // SetPromiscuousMode/SetSpoofing just set a bool on the NIC, not something
  215. // that accumulates or needs undoing between calls.
  216. func enablePromiscuousRouting(gstack *stack.Stack) {
  217. gstack.SetPromiscuousMode(1, true)
  218. gstack.SetSpoofing(1, true)
  219. }
  220. // addrFromTcpip converts a gVisor tcpip.Address (4 or 16 raw bytes) to the
  221. // stdlib netip.Addr type the rest of this package and its callers use.
  222. func addrFromTcpip(a tcpip.Address) netip.Addr {
  223. if a.Len() == 4 {
  224. var b [4]byte
  225. copy(b[:], a.AsSlice())
  226. return netip.AddrFrom4(b)
  227. }
  228. var b [16]byte
  229. copy(b[:], a.AsSlice())
  230. return netip.AddrFrom16(b)
  231. }