ecn_inner_test.go 3.5 KB

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