ecn_inner_test.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package nebula
  2. import (
  3. "io"
  4. "log/slog"
  5. "testing"
  6. )
  7. func TestInnerECN(t *testing.T) {
  8. cases := []struct {
  9. name string
  10. pkt []byte
  11. want byte
  12. }{
  13. {"empty", nil, 0},
  14. {"v4_NotECT", v4WithToS(0x00), 0x00},
  15. {"v4_ECT0", v4WithToS(0x02), 0x02},
  16. {"v4_ECT1", v4WithToS(0x01), 0x01},
  17. {"v4_CE", v4WithToS(0x03), 0x03},
  18. {"v4_DSCP_then_NotECT", v4WithToS(0x88 | 0x00), 0x00},
  19. {"v4_DSCP_then_CE", v4WithToS(0x88 | 0x03), 0x03},
  20. {"v6_NotECT", v6WithTC(0x00), 0x00},
  21. {"v6_ECT0", v6WithTC(0x02), 0x02},
  22. {"v6_CE", v6WithTC(0x03), 0x03},
  23. {"v6_DSCP_then_CE", v6WithTC(0x88 | 0x03), 0x03},
  24. {"unknown_version", []byte{0xa5, 0xff}, 0},
  25. }
  26. for _, c := range cases {
  27. t.Run(c.name, func(t *testing.T) {
  28. got := innerECN(c.pkt)
  29. if got != c.want {
  30. t.Errorf("innerECN=0x%02x want 0x%02x", got, c.want)
  31. }
  32. })
  33. }
  34. }
  35. // v4WithToS returns a 2-byte slice tall enough for innerECN: byte 0 carries
  36. // version=4 in the high nibble, byte 1 is the full ToS so we exercise both
  37. // the DSCP and ECN portions through the byte 1 mask.
  38. func v4WithToS(tos byte) []byte {
  39. return []byte{0x45, tos}
  40. }
  41. // v6WithTC builds a 2-byte slice that places a known traffic class value
  42. // across bytes 0 (high nibble of TC) and 1 (low nibble of TC). innerECN
  43. // extracts ECN as (b[1]>>4)&0x03, which corresponds to TC[1:0].
  44. func v6WithTC(tc byte) []byte {
  45. return []byte{0x60 | (tc>>4)&0x0f, (tc & 0x0f) << 4}
  46. }
  47. func TestApplyOuterECN(t *testing.T) {
  48. silent := slog.New(slog.NewTextHandler(io.Discard, nil))
  49. hi := &HostInfo{}
  50. // Build a v4 packet helper with a given inner ECN field.
  51. v4 := func(innerECN byte) []byte {
  52. // 20-byte minimal IPv4 header with ToS = innerECN (DSCP zeroed).
  53. return []byte{
  54. 0x45, innerECN, 0, 28,
  55. 0, 0, 0x40, 0,
  56. 64, 6, 0, 0,
  57. 10, 0, 0, 1,
  58. 10, 0, 0, 2,
  59. }
  60. }
  61. // Build a v6 packet helper with a given inner ECN field. ECN occupies
  62. // TC[1:0] which sit at byte 1 mask 0x30.
  63. v6 := func(innerECN byte) []byte {
  64. // 40-byte minimal IPv6 header with TC[1:0] = innerECN.
  65. pkt := make([]byte, 40)
  66. pkt[0] = 0x60 // version=6, TC[7:4]=0
  67. pkt[1] = (innerECN & 0x03) << 4 // TC[3:0]: low 2 bits = ECN, top 2 = DSCP-low (0)
  68. return pkt
  69. }
  70. type cell struct {
  71. outer byte
  72. inner byte
  73. wantECN byte
  74. wantSame bool // expect inner unchanged (true => verify the byte didn't move)
  75. }
  76. // RFC 6040 normal-mode combine table. Only outer==CE causes mutation.
  77. table := []cell{
  78. {ecnNotECT, ecnNotECT, ecnNotECT, true},
  79. {ecnNotECT, ecnECT0, ecnECT0, true},
  80. {ecnNotECT, ecnECT1, ecnECT1, true},
  81. {ecnNotECT, ecnCE, ecnCE, true},
  82. {ecnECT0, ecnNotECT, ecnNotECT, true},
  83. {ecnECT0, ecnECT0, ecnECT0, true},
  84. {ecnECT0, ecnECT1, ecnECT1, true},
  85. {ecnECT0, ecnCE, ecnCE, true},
  86. {ecnECT1, ecnNotECT, ecnNotECT, true},
  87. {ecnECT1, ecnECT0, ecnECT0, true},
  88. {ecnECT1, ecnECT1, ecnECT1, true},
  89. {ecnECT1, ecnCE, ecnCE, true},
  90. {ecnCE, ecnNotECT, ecnNotECT, true}, // legacy: log, leave alone
  91. {ecnCE, ecnECT0, ecnCE, false}, // CE folded in
  92. {ecnCE, ecnECT1, ecnCE, false},
  93. {ecnCE, ecnCE, ecnCE, true},
  94. }
  95. for _, c := range table {
  96. t.Run("v4", func(t *testing.T) {
  97. pkt := v4(c.inner)
  98. applyOuterECN(pkt, c.outer, hi, silent)
  99. got := pkt[1] & 0x03
  100. if got != c.wantECN {
  101. t.Errorf("v4 outer=0x%02x inner=0x%02x: got 0x%02x want 0x%02x", c.outer, c.inner, got, c.wantECN)
  102. }
  103. })
  104. t.Run("v6", func(t *testing.T) {
  105. pkt := v6(c.inner)
  106. applyOuterECN(pkt, c.outer, hi, silent)
  107. got := (pkt[1] >> 4) & 0x03
  108. if got != c.wantECN {
  109. t.Errorf("v6 outer=0x%02x inner=0x%02x: got 0x%02x want 0x%02x", c.outer, c.inner, got, c.wantECN)
  110. }
  111. })
  112. }
  113. }