1
0

inside_test.go 8.8 KB

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