ospf_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package dynamicrouting
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. )
  8. func TestOSPFStatusIsPerInstance(t *testing.T) {
  9. dir := t.TempDir()
  10. text := "#!/bin/sh\nprintf 'ospf_a OSPF master4 up 00:00\\nospf_b OSPF master4 down 00:00\\n'\n"
  11. if err := os.WriteFile(filepath.Join(dir, "birdc"), []byte(text), 0755); err != nil {
  12. t.Fatal(err)
  13. }
  14. t.Setenv("PATH", dir+":"+os.Getenv("PATH"))
  15. m := &Manager{}
  16. m.config.OSPFInstances = []OSPFInstance{{Name: "a"}, {Name: "b"}, {Name: "c"}}
  17. got := m.ListOSPFInstances()
  18. if !got[0].Running || got[1].Running || got[2].Running {
  19. t.Fatal("one OSPF instance marked other instances running", got)
  20. }
  21. }
  22. func TestOSPFBirdConfigGeneration(t *testing.T) {
  23. m := &Manager{}
  24. m.config.OSPFInstances = []OSPFInstance{
  25. {
  26. ID: "inst-1",
  27. Name: "core",
  28. Version: 2,
  29. VRF: "vrf-mgmt",
  30. RoutingTable: "t100",
  31. RouterID: "10.255.0.1",
  32. Enabled: true,
  33. RedistributeConnected: true,
  34. RedistributeStatic: false, // Must NOT export RTS_STATIC
  35. RedistributeBGP: true,
  36. DefaultMetric: 55,
  37. },
  38. }
  39. m.config.OSPFAreas = []OSPFArea{
  40. {
  41. ID: "area-1",
  42. Name: "stub-area",
  43. Instance: "core",
  44. AreaID: "0.0.0.10",
  45. AreaType: "stub",
  46. DefaultCost: 25,
  47. NoSummaries: true,
  48. },
  49. }
  50. m.config.OSPFAreaRanges = []OSPFAreaRange{
  51. {
  52. ID: "range-1",
  53. Area: "stub-area",
  54. Prefix: "10.10.0.0/16",
  55. Cost: 10,
  56. Advertise: false, // Must emit hidden;
  57. },
  58. {
  59. ID: "range-2",
  60. Area: "stub-area",
  61. Prefix: "10.20.0.0/16",
  62. Cost: 5,
  63. Advertise: true,
  64. },
  65. }
  66. m.config.OSPFInterfaces = []OSPFInterface{
  67. {
  68. ID: "iface-1",
  69. Interfaces: "eth0, eth1",
  70. Area: "stub-area",
  71. Networks: "10.10.1.0/24",
  72. NetworkType: "nonbroadcast",
  73. Cost: 15,
  74. Priority: 64,
  75. Auth: "md5",
  76. AuthKey: "secretpass",
  77. },
  78. }
  79. m.config.OSPFStaticNbrs = []OSPFStaticNeighbor{
  80. {
  81. ID: "snbr-1",
  82. Address: "10.10.1.5",
  83. Area: "stub-area",
  84. PollInterval: 30,
  85. Cost: 15,
  86. },
  87. {
  88. ID: "snbr-2",
  89. Address: "192.168.99.1",
  90. Area: "stub-area",
  91. PollInterval: 45,
  92. Cost: 20,
  93. },
  94. }
  95. conf := m.generateBirdConfig()
  96. // 1. Verify instance settings
  97. if !strings.Contains(conf, "protocol ospf v2 ospf_core {") {
  98. t.Fatalf("missing protocol ospf v2 ospf_core in config:\n%s", conf)
  99. }
  100. if !strings.Contains(conf, "vrf vrf-mgmt;") {
  101. t.Fatalf("missing vrf vrf-mgmt in config:\n%s", conf)
  102. }
  103. if !strings.Contains(conf, "router id 10.255.0.1;") {
  104. t.Fatalf("missing router id 10.255.0.1 in config:\n%s", conf)
  105. }
  106. if !strings.Contains(conf, "table t100;") {
  107. t.Fatalf("missing table t100 in config:\n%s", conf)
  108. }
  109. // 2. Verify redistribution & default metric
  110. ospfBlock := conf[strings.Index(conf, "protocol ospf v2 ospf_core"):]
  111. if strings.Contains(ospfBlock, "RTS_STATIC") {
  112. t.Fatalf("RTS_STATIC exported even though RedistributeStatic is false:\n%s", ospfBlock)
  113. }
  114. if !strings.Contains(ospfBlock, "RTS_DEVICE, RTS_BGP") {
  115. t.Fatalf("expected RTS_DEVICE, RTS_BGP in export filter:\n%s", ospfBlock)
  116. }
  117. if !strings.Contains(ospfBlock, "ospf_metric1 = 55;") {
  118. t.Fatalf("missing ospf_metric1 = 55; in export filter:\n%s", ospfBlock)
  119. }
  120. // 3. Verify area options: stub, default cost, summary no
  121. if !strings.Contains(conf, "area 0.0.0.10 {") {
  122. t.Fatalf("missing area 0.0.0.10 in config:\n%s", conf)
  123. }
  124. if !strings.Contains(conf, "stub;") {
  125. t.Fatalf("missing stub; in area:\n%s", conf)
  126. }
  127. if !strings.Contains(conf, "default cost 25;") {
  128. t.Fatalf("missing default cost 25; in area:\n%s", conf)
  129. }
  130. if !strings.Contains(conf, "summary no;") {
  131. t.Fatalf("missing summary no; in area:\n%s", conf)
  132. }
  133. // 4. Verify area range advertising: hidden; for advertise=false
  134. if !strings.Contains(conf, "stubnet 10.10.0.0/16 { cost 10; hidden; };") {
  135. t.Fatalf("missing stubnet 10.10.0.0/16 { cost 10; hidden; }; in config:\n%s", conf)
  136. }
  137. if !strings.Contains(conf, "stubnet 10.20.0.0/16 { cost 5; };") {
  138. t.Fatalf("missing stubnet 10.20.0.0/16 { cost 5; }; in config:\n%s", conf)
  139. }
  140. // 5. Verify interface template formatting: comma separated interfaces
  141. if !strings.Contains(conf, "interface \"eth0\", \"eth1\", 10.10.1.0/24 {") {
  142. t.Fatalf("missing interface \"eth0\", \"eth1\", 10.10.1.0/24 { in config:\n%s", conf)
  143. }
  144. if !strings.Contains(conf, "type nonbroadcast;") {
  145. t.Fatalf("missing type nonbroadcast; in config:\n%s", conf)
  146. }
  147. if !strings.Contains(conf, "cost 15;") {
  148. t.Fatalf("missing cost 15; in config:\n%s", conf)
  149. }
  150. if !strings.Contains(conf, "priority 64;") {
  151. t.Fatalf("missing priority 64; in config:\n%s", conf)
  152. }
  153. if !strings.Contains(conf, "authentication cryptographic;\n password \"secretpass\";") {
  154. t.Fatalf("missing authentication cryptographic and password in config:\n%s", conf)
  155. }
  156. // 6. Verify static neighbor attached to matching interface template
  157. if !strings.Contains(conf, "10.10.1.5;") {
  158. t.Fatalf("missing static neighbor 10.10.1.5 in config:\n%s", conf)
  159. }
  160. // 7. Verify remaining static neighbor in fallback interface "*"
  161. if !strings.Contains(conf, "192.168.99.1;") {
  162. t.Fatalf("missing static neighbor 192.168.99.1 in config:\n%s", conf)
  163. }
  164. }
  165. func TestOSPFStaticNeighborManagement(t *testing.T) {
  166. m := &Manager{}
  167. snbr := OSPFStaticNeighbor{
  168. Address: "10.0.0.2",
  169. Area: "Default",
  170. PollInterval: 60,
  171. Cost: 5,
  172. }
  173. if err := m.SaveOSPFStaticNeighbor(snbr); err != nil {
  174. // applyBirdConfig may fail if bird is not running in test env, but config must be in memory
  175. t.Logf("Save returned error (expected in mock env): %v", err)
  176. }
  177. list := m.ListOSPFStaticNeighbors()
  178. if len(list) != 1 || list[0].Address != "10.0.0.2" {
  179. t.Fatalf("expected 1 static neighbor with address 10.0.0.2, got: %+v", list)
  180. }
  181. id := list[0].ID
  182. _ = m.SetOSPFStaticNeighborState(id, true)
  183. list2 := m.ListOSPFStaticNeighbors()
  184. if !list2[0].Disabled {
  185. t.Fatalf("expected neighbor to be disabled, got: %+v", list2[0])
  186. }
  187. _ = m.DeleteOSPFStaticNeighbor(id)
  188. list3 := m.ListOSPFStaticNeighbors()
  189. if len(list3) != 0 {
  190. t.Fatalf("expected 0 static neighbors after delete, got: %+v", list3)
  191. }
  192. }
  193. func TestOSPFOriginateDefault(t *testing.T) {
  194. // 1. Test "never" (default)
  195. m := &Manager{}
  196. m.config.OSPFInstances = []OSPFInstance{
  197. {
  198. ID: "inst-def",
  199. Name: "test_inst",
  200. Version: 2,
  201. Enabled: true,
  202. RedistributeStatic: true,
  203. RedistributeDefault: false,
  204. OriginateDefault: "never",
  205. },
  206. }
  207. conf := m.generateBirdConfig()
  208. if !strings.Contains(conf, "if net = 0.0.0.0/0 then reject;") {
  209. t.Fatalf("expected 0.0.0.0/0 reject in OSPF export filter when originate-default is never:\n%s", conf)
  210. }
  211. // 2. Test "if-installed"
  212. m.config.OSPFInstances[0].RedistributeDefault = true
  213. m.config.OSPFInstances[0].OriginateDefault = "if-installed"
  214. m.config.OSPFInstances[0].DefaultMetric = 20
  215. conf = m.generateBirdConfig()
  216. if !strings.Contains(conf, "if net = 0.0.0.0/0 && source != RTS_OSPF then {") {
  217. t.Fatalf("expected if-installed check in OSPF export filter:\n%s", conf)
  218. }
  219. if !strings.Contains(conf, "ospf_metric1 = 20;") {
  220. t.Fatalf("expected metric 20 in OSPF export filter:\n%s", conf)
  221. }
  222. // 3. Test "always"
  223. m.config.OSPFInstances[0].OriginateDefault = "always"
  224. conf = m.generateBirdConfig()
  225. if !strings.Contains(conf, "protocol static ospf_def_ospf_test_inst {") {
  226. t.Fatalf("expected protocol static ospf_def_ospf_test_inst for always:\n%s", conf)
  227. }
  228. if !strings.Contains(conf, "if proto ~ \"ospf_def_*\" then reject;") {
  229. t.Fatalf("expected kernel export filter to reject ospf_def_*:\n%s", conf)
  230. }
  231. if !strings.Contains(conf, `if proto = "ospf_def_ospf_test_inst" || (net = 0.0.0.0/0 && source != RTS_OSPF) then {`) {
  232. t.Fatalf("expected always check in OSPF export filter:\n%s", conf)
  233. }
  234. }