inside_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package nebula
  2. import (
  3. "encoding/binary"
  4. "io"
  5. "net/netip"
  6. "testing"
  7. "github.com/gaissmai/bart"
  8. "github.com/slackhq/nebula/firewall"
  9. "github.com/slackhq/nebula/overlay/tio"
  10. "github.com/slackhq/nebula/test"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. const (
  15. ipv4HeaderLen = 20
  16. ipv6HeaderLen = 40
  17. )
  18. // capturingTun is a tio.Queue that records what is written to it. A queue that
  19. // discards writes is indistinguishable from a packet that was never forwarded.
  20. type capturingTun struct {
  21. writes [][]byte
  22. }
  23. func (c *capturingTun) Read() ([]tio.Packet, error) { return nil, io.EOF }
  24. func (c *capturingTun) Close() error { return nil }
  25. func (c *capturingTun) Write(b []byte) (int, error) {
  26. c.writes = append(c.writes, append([]byte(nil), b...))
  27. return len(b), nil
  28. }
  29. func newSelfForwardInterface(myAddrs ...netip.Addr) (*Interface, *capturingTun) {
  30. vpnAddrs := &bart.Lite{}
  31. for _, a := range myAddrs {
  32. vpnAddrs.Insert(netip.PrefixFrom(a, a.BitLen()))
  33. }
  34. tun := &capturingTun{}
  35. return &Interface{
  36. l: test.NewLogger(),
  37. myVpnAddrsTable: vpnAddrs,
  38. myBroadcastAddrsTable: &bart.Lite{},
  39. queues: []tio.Queue{tun},
  40. }, tun
  41. }
  42. func consumeInside(f *Interface, packet []byte) {
  43. f.consumeInsidePacket(tio.Packet{Bytes: packet}, &firewall.ParsedPacket{}, make([]byte, 12), nil, make([]byte, mtu), 0, nil)
  44. }
  45. // l4Proto describes one upper-layer header for these tests: its IP next-header
  46. // value, where its checksum field sits within the header, and how to build a
  47. // minimal instance of it.
  48. type l4Proto struct {
  49. name string
  50. nextHdr uint8
  51. cksumAt int
  52. build func() []byte
  53. }
  54. var (
  55. tcpSyn = l4Proto{"tcp", firewall.ProtoTCP, 16, func() []byte {
  56. h := make([]byte, 20)
  57. binary.BigEndian.PutUint16(h[0:2], 49152)
  58. binary.BigEndian.PutUint16(h[2:4], 443)
  59. binary.BigEndian.PutUint32(h[4:8], 0x11223344) // sequence
  60. h[12] = 5 << 4 // data offset, no options
  61. h[13] = 0x02 // SYN
  62. binary.BigEndian.PutUint16(h[14:16], 65535) // window
  63. return h
  64. }}
  65. udpDatagram = l4Proto{"udp", firewall.ProtoUDP, 6, func() []byte {
  66. h := make([]byte, 8+4)
  67. binary.BigEndian.PutUint16(h[0:2], 49152)
  68. binary.BigEndian.PutUint16(h[2:4], 53)
  69. binary.BigEndian.PutUint16(h[4:6], uint16(len(h)))
  70. copy(h[8:], "ping")
  71. return h
  72. }}
  73. icmpEcho = l4Proto{"icmp", firewall.ProtoICMP, 2, func() []byte { return echoRequest(8) }}
  74. icmpv6Echo = l4Proto{"icmpv6", firewall.ProtoICMPv6, 2, func() []byte { return echoRequest(128) }}
  75. )
  76. // echoRequest builds an echo request body. The type differs between ICMP and
  77. // ICMPv6, the rest of the header does not.
  78. func echoRequest(typ uint8) []byte {
  79. h := make([]byte, 8)
  80. h[0] = typ
  81. binary.BigEndian.PutUint16(h[4:6], 0xbeef) // identifier
  82. binary.BigEndian.PutUint16(h[6:8], 1) // sequence
  83. return h
  84. }
  85. func buildIPv6(src, dst netip.Addr, p l4Proto) []byte {
  86. l4 := p.build()
  87. pkt := make([]byte, ipv6HeaderLen+len(l4))
  88. pkt[0] = 0x60
  89. binary.BigEndian.PutUint16(pkt[4:6], uint16(len(l4)))
  90. pkt[6] = p.nextHdr
  91. pkt[7] = 64
  92. copy(pkt[8:24], src.AsSlice())
  93. copy(pkt[24:40], dst.AsSlice())
  94. copy(pkt[ipv6HeaderLen:], l4)
  95. if l4 := pkt[ipv6HeaderLen:]; p.nextHdr == firewall.ProtoTCP || p.nextHdr == firewall.ProtoUDP {
  96. sum := ipv6PseudoheaderSum(src, dst, uint32(p.nextHdr), uint32(len(l4)))
  97. binary.BigEndian.PutUint16(l4[p.cksumAt:], ^fold(sumBytes(l4, sum)))
  98. }
  99. return pkt
  100. }
  101. func buildIPv4(src, dst netip.Addr, p l4Proto) []byte {
  102. l4 := p.build()
  103. pkt := make([]byte, ipv4HeaderLen+len(l4))
  104. pkt[0] = 0x45
  105. binary.BigEndian.PutUint16(pkt[2:4], uint16(len(pkt)))
  106. pkt[8] = 64
  107. pkt[9] = p.nextHdr
  108. copy(pkt[12:16], src.AsSlice())
  109. copy(pkt[16:20], dst.AsSlice())
  110. copy(pkt[ipv4HeaderLen:], l4)
  111. if l4 := pkt[ipv4HeaderLen:]; p.nextHdr == firewall.ProtoTCP || p.nextHdr == firewall.ProtoUDP {
  112. sum := sumBytes(pkt[12:20], uint32(p.nextHdr)+uint32(len(l4)))
  113. binary.BigEndian.PutUint16(l4[p.cksumAt:], ^fold(sumBytes(l4, sum)))
  114. }
  115. return pkt
  116. }
  117. // ipv6PseudoheaderSum is the RFC 2460 section 8.1 pseudo-header sum: source,
  118. // destination, a 32 bit upper-layer packet length and a 32 bit zero-padded next
  119. // header. Kept local to the test so these assertions do not check nebula's
  120. // checksum code against itself.
  121. func ipv6PseudoheaderSum(src, dst netip.Addr, nextHeader, length uint32) uint32 {
  122. var csum uint32
  123. s, d := src.AsSlice(), dst.AsSlice()
  124. for i := 0; i < 16; i += 2 {
  125. csum += uint32(s[i])<<8 | uint32(s[i+1])
  126. csum += uint32(d[i])<<8 | uint32(d[i+1])
  127. }
  128. return csum + length + nextHeader
  129. }
  130. func sumBytes(b []byte, csum uint32) uint32 {
  131. for i := 0; i+1 < len(b); i += 2 {
  132. csum += uint32(b[i])<<8 | uint32(b[i+1])
  133. }
  134. if len(b)%2 == 1 {
  135. csum += uint32(b[len(b)-1]) << 8
  136. }
  137. return csum
  138. }
  139. func fold(csum uint32) uint16 {
  140. for csum > 0xffff {
  141. csum = (csum >> 16) + (csum & 0xffff)
  142. }
  143. return uint16(csum)
  144. }
  145. // l4ChecksumValid6 verifies an IPv6 upper-layer checksum the way a receiver
  146. // does: the pseudo-header plus the whole upper-layer segment, checksum field
  147. // included, folds to 0xffff. The next header field is the upper-layer protocol
  148. // only while there are no extension headers, which is all this file builds.
  149. func l4ChecksumValid6(pkt []byte) bool {
  150. src, _ := netip.AddrFromSlice(pkt[8:24])
  151. dst, _ := netip.AddrFromSlice(pkt[24:40])
  152. l4 := pkt[ipv6HeaderLen:]
  153. return fold(sumBytes(l4, ipv6PseudoheaderSum(src, dst, uint32(pkt[6]), uint32(len(l4))))) == 0xffff
  154. }
  155. // l4ChecksumValid4 is the IPv4 counterpart: the RFC 793/768 pseudo-header is
  156. // source, destination, a zero byte, the protocol and the upper-layer length.
  157. func l4ChecksumValid4(pkt []byte) bool {
  158. ihl := int(pkt[0]&0x0f) << 2
  159. l4 := pkt[ihl:]
  160. return fold(sumBytes(l4, sumBytes(pkt[12:20], uint32(pkt[9])+uint32(len(l4))))) == 0xffff
  161. }
  162. // TestConsumeInsidePacketSelfTraffic covers the self-addressed branch of
  163. // consumeInsidePacket, taken where immediatelyForwardToSelf is set (see
  164. // inside_bsd.go): the packet goes straight back to the tun, ahead of the
  165. // firewall and the handshake.
  166. func TestConsumeInsidePacketSelfTraffic(t *testing.T) {
  167. v4 := netip.MustParseAddr("100.100.1.42")
  168. v6 := netip.MustParseAddr("fd00::42")
  169. tests := []struct {
  170. name string
  171. addr netip.Addr
  172. pkt []byte
  173. }{
  174. {"ipv4/tcp", v4, buildIPv4(v4, v4, tcpSyn)},
  175. {"ipv4/udp", v4, buildIPv4(v4, v4, udpDatagram)},
  176. {"ipv4/icmp", v4, buildIPv4(v4, v4, icmpEcho)},
  177. {"ipv6/tcp", v6, buildIPv6(v6, v6, tcpSyn)},
  178. {"ipv6/udp", v6, buildIPv6(v6, v6, udpDatagram)},
  179. {"ipv6/icmpv6", v6, buildIPv6(v6, v6, icmpv6Echo)},
  180. }
  181. for _, tt := range tests {
  182. t.Run(tt.name, func(t *testing.T) {
  183. f, tun := newSelfForwardInterface(tt.addr)
  184. // consumeInsidePacket writes through the slice it is handed, so a
  185. // packet that arrived with a valid checksum must come back out of
  186. // bytes taken before the call, unchanged.
  187. want := append([]byte(nil), tt.pkt...)
  188. consumeInside(f, tt.pkt)
  189. if immediatelyForwardToSelf {
  190. require.Len(t, tun.writes, 1)
  191. assert.Equal(t, want, tun.writes[0])
  192. } else {
  193. assert.Empty(t, tun.writes, "self traffic reaches the tun over loopback here and must be dropped")
  194. }
  195. })
  196. }
  197. }
  198. // TestConsumeInsidePacketSelfTrafficChecksum shows that the self-forward
  199. // returns the bytes it was handed, so a packet that arrived with a wrong
  200. // upper-layer checksum is written back with that same wrong checksum and the
  201. // kernel drops it on re-entry.
  202. //
  203. // This is how a macOS host loses TCP and UDP to its own IPv6 overlay address:
  204. // the kernel writes only the pseudo-header sum into the checksum field and
  205. // defers completion to hardware offload, state that does not survive the
  206. // crossing into userspace. Which kernels do this, for which protocols and IP
  207. // versions, is a property of the kernel and belongs to a test against a live
  208. // one; here the checksum is simply wrong, and the forward must make it right.
  209. func TestConsumeInsidePacketSelfTrafficChecksum(t *testing.T) {
  210. if !immediatelyForwardToSelf {
  211. t.Skip("self traffic never reaches the tun on this platform")
  212. }
  213. versions := []struct {
  214. name string
  215. addr netip.Addr
  216. build func(src, dst netip.Addr, p l4Proto) []byte
  217. l4At int
  218. valid func(pkt []byte) bool
  219. }{
  220. {"v4", netip.MustParseAddr("100.100.1.42"), buildIPv4, ipv4HeaderLen, l4ChecksumValid4},
  221. {"v6", netip.MustParseAddr("fd00::42"), buildIPv6, ipv6HeaderLen, l4ChecksumValid6},
  222. }
  223. for _, v := range versions {
  224. for _, p := range []l4Proto{tcpSyn, udpDatagram} {
  225. t.Run(v.name+"/"+p.name, func(t *testing.T) {
  226. pkt := v.build(v.addr, v.addr, p)
  227. binary.BigEndian.PutUint16(pkt[v.l4At+p.cksumAt:], 0x1234)
  228. require.False(t, v.valid(pkt), "the packet under test must start with a wrong checksum")
  229. f, tun := newSelfForwardInterface(v.addr)
  230. consumeInside(f, pkt)
  231. require.Len(t, tun.writes, 1)
  232. assert.True(t, v.valid(tun.writes[0]),
  233. "a forwarded %s packet must carry a valid checksum, got 0x%04x",
  234. p.name, binary.BigEndian.Uint16(tun.writes[0][v.l4At+p.cksumAt:]))
  235. })
  236. }
  237. }
  238. }