protocols_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package dynamicrouting
  2. import (
  3. "os"
  4. "os/exec"
  5. "strings"
  6. "testing"
  7. )
  8. func TestAllProtocolsBirdConfigGeneration(t *testing.T) {
  9. mgr := &Manager{
  10. config: DynamicConfig{
  11. BGPInstance: BGPInstance{
  12. Enabled: true,
  13. AS: 65001,
  14. RouterID: "10.42.62.254",
  15. },
  16. BGPPeers: []BGPPeer{
  17. {
  18. ID: "peer-1",
  19. Name: "Uplink",
  20. RemoteAddress: "10.42.36.253",
  21. RemoteAS: 65002,
  22. UseBFD: true,
  23. },
  24. },
  25. OSPFInstances: []OSPFInstance{
  26. {
  27. ID: "ospf-1",
  28. Name: "Default",
  29. Version: 2,
  30. RouterID: "10.42.62.254",
  31. Enabled: true,
  32. },
  33. },
  34. OSPFAreas: []OSPFArea{
  35. {
  36. ID: "area-0",
  37. Name: "Default",
  38. AreaID: "0.0.0.0",
  39. AreaType: "default",
  40. },
  41. },
  42. OSPFInterfaces: []OSPFInterface{
  43. {
  44. ID: "ospf-if-1",
  45. Interfaces: "eth0",
  46. Area: "Default",
  47. Cost: 10,
  48. UseBFD: true,
  49. },
  50. },
  51. BFDInstance: BFDInstance{
  52. Enabled: true,
  53. },
  54. BFDInterfaces: []BFDInterface{
  55. {
  56. ID: "bfd-if-1",
  57. Interface: "eth0",
  58. MinRxInterval: 100,
  59. MinTxInterval: 100,
  60. Multiplier: 3,
  61. },
  62. },
  63. BFDNeighbors: []BFDNeighbor{
  64. {
  65. ID: "bfd-nbr-1",
  66. Address: "10.42.36.253",
  67. Interface: "eth0",
  68. },
  69. },
  70. RIPInstances: []RIPInstance{
  71. {
  72. ID: "rip-1",
  73. Name: "default",
  74. Version: 2,
  75. Enabled: true,
  76. RedistributeConnected: true,
  77. RedistributeStatic: true,
  78. },
  79. },
  80. RIPInterfaces: []RIPInterface{
  81. {
  82. ID: "rip-if-1",
  83. Instance: "default",
  84. Interface: "eth0",
  85. Metric: 1,
  86. Mode: "multicast",
  87. UseBFD: true,
  88. },
  89. },
  90. BabelInstances: []BabelInstance{
  91. {
  92. ID: "babel-1",
  93. Name: "default",
  94. RouterID: "10.42.62.254",
  95. Enabled: true,
  96. RedistributeConnected: true,
  97. },
  98. },
  99. BabelInterfaces: []BabelInterface{
  100. {
  101. ID: "babel-if-1",
  102. Instance: "default",
  103. Interface: "eth0",
  104. Type: "wired",
  105. RxCost: 10,
  106. HelloInterval: 4,
  107. UpdateInterval: 16,
  108. },
  109. },
  110. RAdvInstances: []RAdvInstance{
  111. {
  112. ID: "radv-1",
  113. Name: "default",
  114. Enabled: true,
  115. },
  116. },
  117. RAdvInterfaces: []RAdvInterface{
  118. {
  119. ID: "radv-if-1",
  120. Instance: "default",
  121. Interface: "eth0",
  122. MinRAInterval: 200,
  123. MaxRAInterval: 600,
  124. DefaultLifetime: 1800,
  125. Prefixes: []string{"2001:db8:1::/64"},
  126. RDNSS: []string{"2001:4860:4860::8888"},
  127. },
  128. },
  129. },
  130. }
  131. cfg := mgr.generateBirdConfig()
  132. // Verify all protocol blocks are generated
  133. if !strings.Contains(cfg, "protocol bfd bfd_main") {
  134. t.Errorf("Expected protocol bfd bfd_main in config, got:\n%s", cfg)
  135. }
  136. if !strings.Contains(cfg, "protocol rip rip_default") {
  137. t.Errorf("Expected protocol rip in config, got:\n%s", cfg)
  138. }
  139. if !strings.Contains(cfg, "protocol babel babel_default") {
  140. t.Errorf("Expected protocol babel in config, got:\n%s", cfg)
  141. }
  142. if !strings.Contains(cfg, "protocol radv radv_default") {
  143. t.Errorf("Expected protocol radv in config, got:\n%s", cfg)
  144. }
  145. if !strings.Contains(cfg, "bfd yes;") {
  146. t.Errorf("Expected bfd yes; in peer and interface, got:\n%s", cfg)
  147. }
  148. // Validate generated config directly with BIRD 2 compiler if bird binary is available
  149. if _, err := exec.LookPath("bird"); err == nil {
  150. tmpFile, err := os.CreateTemp("", "bird_val_*.conf")
  151. if err != nil {
  152. t.Fatalf("Failed to create temp file: %v", err)
  153. }
  154. defer os.Remove(tmpFile.Name())
  155. if _, err := tmpFile.WriteString(cfg); err != nil {
  156. t.Fatalf("Failed to write temp file: %v", err)
  157. }
  158. tmpFile.Close()
  159. cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
  160. out, err := cmd.CombinedOutput()
  161. if err != nil {
  162. t.Fatalf("bird -p validation failed: %s\nConfig:\n%s", string(out), cfg)
  163. }
  164. }
  165. }
  166. func TestBabelFallbackInterfaceAndInstanceManagement(t *testing.T) {
  167. tmpDir, err := os.MkdirTemp("", "debtik_babel_test_*")
  168. if err != nil {
  169. t.Fatalf("Failed to create temp dir: %v", err)
  170. }
  171. defer os.RemoveAll(tmpDir)
  172. mgr := &Manager{
  173. configPath: tmpDir + "/dynamic.json",
  174. config: DynamicConfig{
  175. BabelInstances: []BabelInstance{},
  176. BabelInterfaces: []BabelInterface{},
  177. },
  178. }
  179. // 1. Save Babel instance without explicit enabled flag
  180. inst, err := mgr.SaveBabelInstance(BabelInstance{
  181. Name: "internal-Maiden2",
  182. RouterID: "192.168.132.1",
  183. })
  184. if err != nil {
  185. t.Fatalf("SaveBabelInstance failed: %v", err)
  186. }
  187. if !inst.Enabled {
  188. t.Errorf("Expected inst.Enabled to be true when Disabled is false, got false")
  189. }
  190. // 2. Generate BIRD config when no interfaces exist - should generate fallback interface "*"
  191. cfg := mgr.generateBirdConfig()
  192. if !strings.Contains(cfg, `protocol babel babel_internal_Maiden2_0`) {
  193. t.Errorf("Expected protocol babel in config, got:\n%s", cfg)
  194. }
  195. if !strings.Contains(cfg, `interface "*"`) {
  196. t.Errorf("Expected fallback interface \"*\" in config, got:\n%s", cfg)
  197. }
  198. // 3. Save Babel interface without explicit instance - should default to "internal-Maiden2"
  199. iface, err := mgr.SaveBabelInterface(BabelInterface{
  200. Interface: "eth0",
  201. RxCost: 96,
  202. })
  203. if err != nil {
  204. t.Fatalf("SaveBabelInterface failed: %v", err)
  205. }
  206. if iface.Instance != "internal-Maiden2" {
  207. t.Errorf("Expected iface.Instance to default to internal-Maiden2, got %q", iface.Instance)
  208. }
  209. // 4. Regenerate config - now should contain eth0 template
  210. cfg2 := mgr.generateBirdConfig()
  211. if !strings.Contains(cfg2, `interface "eth0"`) {
  212. t.Errorf("Expected interface \"eth0\" in config, got:\n%s", cfg2)
  213. }
  214. // 5. Validate with bird -p if bird is installed
  215. if _, err := exec.LookPath("bird"); err == nil {
  216. tmpFile, err := os.CreateTemp("", "bird_babel_val_*.conf")
  217. if err != nil {
  218. t.Fatalf("Failed to create temp file: %v", err)
  219. }
  220. defer os.Remove(tmpFile.Name())
  221. if _, err := tmpFile.WriteString(cfg2); err != nil {
  222. t.Fatalf("Failed to write temp file: %v", err)
  223. }
  224. tmpFile.Close()
  225. cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
  226. out, err := cmd.CombinedOutput()
  227. if err != nil {
  228. t.Fatalf("bird -p validation failed on babel config: %s\nConfig:\n%s", string(out), cfg2)
  229. }
  230. }
  231. }
  232. func TestBFDAndRIPAndRAdvManagement(t *testing.T) {
  233. tmpDir, err := os.MkdirTemp("", "debtik_protocols_test_*")
  234. if err != nil {
  235. t.Fatalf("Failed to create temp dir: %v", err)
  236. }
  237. defer os.RemoveAll(tmpDir)
  238. mgr := &Manager{
  239. configPath: tmpDir + "/dynamic.json",
  240. }
  241. // 1. Save BFD instance
  242. err = mgr.SaveBFDInstance(BFDInstance{Enabled: true})
  243. if err != nil {
  244. t.Fatalf("SaveBFDInstance failed: %v", err)
  245. }
  246. bfdInst := mgr.GetBFDInstance()
  247. if !bfdInst.Enabled {
  248. t.Errorf("Expected bfdInst.Enabled to be true")
  249. }
  250. // 2. Check BFD config generation with 0 interfaces - should contain fallback interface "*"
  251. cfg := mgr.generateBirdConfig()
  252. if !strings.Contains(cfg, `protocol bfd bfd_main`) {
  253. t.Errorf("Expected protocol bfd bfd_main in config, got:\n%s", cfg)
  254. }
  255. if !strings.Contains(cfg, `interface "*"`) {
  256. t.Errorf("Expected fallback interface \"*\" in BFD config, got:\n%s", cfg)
  257. }
  258. // 3. Save BFD multihop neighbor with local address
  259. _, err = mgr.SaveBFDNeighbor(BFDNeighbor{
  260. Address: "10.0.0.1",
  261. LocalAddress: "10.0.0.2",
  262. Multihop: true,
  263. })
  264. if err != nil {
  265. t.Fatalf("SaveBFDNeighbor failed: %v", err)
  266. }
  267. // 4. Save RIP instance without explicit enabled flag
  268. ripInst, err := mgr.SaveRIPInstance(RIPInstance{
  269. Name: "rip1",
  270. Version: 2,
  271. })
  272. if err != nil {
  273. t.Fatalf("SaveRIPInstance failed: %v", err)
  274. }
  275. if !ripInst.Enabled {
  276. t.Errorf("Expected ripInst.Enabled to be true, got false")
  277. }
  278. // 5. Save RAdv instance without explicit enabled flag
  279. radvInst, err := mgr.SaveRAdvInstance(RAdvInstance{
  280. Name: "radv1",
  281. })
  282. if err != nil {
  283. t.Fatalf("SaveRAdvInstance failed: %v", err)
  284. }
  285. if !radvInst.Enabled {
  286. t.Errorf("Expected radvInst.Enabled to be true, got false")
  287. }
  288. // 6. Verify config with BIRD compiler
  289. cfgAll := mgr.generateBirdConfig()
  290. if !strings.Contains(cfgAll, "neighbor 10.0.0.1 local 10.0.0.2 multihop;") {
  291. t.Errorf("Expected multihop neighbor with local address, got:\n%s", cfgAll)
  292. }
  293. if !strings.Contains(cfgAll, `protocol rip rip_rip1_0`) {
  294. t.Errorf("Expected protocol rip in config, got:\n%s", cfgAll)
  295. }
  296. if !strings.Contains(cfgAll, `protocol radv radv_radv1_0`) {
  297. t.Errorf("Expected protocol radv in config, got:\n%s", cfgAll)
  298. }
  299. // 7. Verify OSPF fallback interface when no templates exist
  300. mgr.config.OSPFInterfaces = nil
  301. cfgOspfFallback := mgr.generateBirdConfig()
  302. if !strings.Contains(cfgOspfFallback, `interface "*"`) {
  303. t.Errorf("Expected fallback wildcard interface for OSPF area, got:\n%s", cfgOspfFallback)
  304. }
  305. if _, err := exec.LookPath("bird"); err == nil {
  306. tmpFile, err := os.CreateTemp("", "bird_all_val_*.conf")
  307. if err != nil {
  308. t.Fatalf("Failed to create temp file: %v", err)
  309. }
  310. defer os.Remove(tmpFile.Name())
  311. if _, err := tmpFile.WriteString(cfgOspfFallback); err != nil {
  312. t.Fatalf("Failed to write temp file: %v", err)
  313. }
  314. tmpFile.Close()
  315. cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
  316. out, err := cmd.CombinedOutput()
  317. if err != nil {
  318. t.Fatalf("bird -p validation failed on all protocols config: %s\nConfig:\n%s", string(out), cfgOspfFallback)
  319. }
  320. }
  321. }