230 lines
5.7 KiB
Go
230 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type State struct {
|
|
mu sync.RWMutex
|
|
GatewayStatus map[string]bool
|
|
GatewayIPs map[string]string
|
|
SubnetCount int
|
|
LastUpdate int64
|
|
ControllerUp bool
|
|
Subnets []string
|
|
}
|
|
|
|
var state State
|
|
|
|
const indexHTML = `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta http-equiv="refresh" content="5">
|
|
<title>SRM Daemon</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
|
|
h1 { color: #333; }
|
|
table { border-collapse: collapse; width: 60%; margin-top: 20px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
th, td { border: 1px solid #ddd; padding: 12px 16px; text-align: left; }
|
|
th { background: #4CAF50; color: white; }
|
|
.up { color: green; font-weight: bold; }
|
|
.down { color: red; font-weight: bold; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>SRM Daemon</h1>
|
|
<table>
|
|
<tr><th>Parameter</th><th>Value</th></tr>
|
|
{{range $name, $up := .GatewayStatus}}
|
|
<tr>
|
|
<td>Gateway "{{html $name}}" ({{index $.GatewayIPs $name}})</td>
|
|
<td class="{{if $up}}up{{else}}down{{end}}">{{if $up}}UP{{else}}DOWN{{end}}</td>
|
|
</tr>
|
|
{{end}}
|
|
<tr><td>Current Routes</td><td>{{.SubnetCount}}</td></tr>
|
|
<tr>
|
|
<td>Last Update</td>
|
|
<td>{{if eq .LastUpdate 0}}Never{{else}}{{formatTime .LastUpdate}}{{end}}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Controller</td>
|
|
<td class="{{if .ControllerUp}}up{{else}}down{{end}}">{{if .ControllerUp}}UP{{else}}DOWN{{end}}</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>`
|
|
|
|
func formatTime(ts int64) string {
|
|
t := time.UnixMilli(ts)
|
|
return t.Format("2006-01-02 15:04:05 MST")
|
|
}
|
|
|
|
func startWebServer(port int) {
|
|
funcMap := template.FuncMap{
|
|
"formatTime": formatTime,
|
|
}
|
|
tmpl, err := template.New("index").Funcs(funcMap).Parse(indexHTML)
|
|
if err != nil {
|
|
log.Fatalf("Failed to parse template: %v", err)
|
|
}
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
state.mu.RLock()
|
|
defer state.mu.RUnlock()
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
tmpl.Execute(w, &state)
|
|
})
|
|
|
|
addr := fmt.Sprintf(":%d", port)
|
|
log.Printf("Web interface listening on %s", addr)
|
|
if err := http.ListenAndServe(addr, nil); err != nil {
|
|
log.Fatalf("Web server failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
configPath := flag.String("c", "", "Path to config JSON file")
|
|
flag.Parse()
|
|
|
|
if *configPath == "" {
|
|
fmt.Fprintln(os.Stderr, "Usage: srmd -c /path/to/config.json")
|
|
os.Exit(1)
|
|
}
|
|
|
|
cfg, err := LoadConfig(*configPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
state.GatewayStatus = make(map[string]bool)
|
|
state.GatewayIPs = make(map[string]string)
|
|
for _, gw := range cfg.Gateway {
|
|
state.GatewayStatus[gw.Name] = false
|
|
state.GatewayIPs[gw.Name] = gw.Address
|
|
}
|
|
|
|
state.Subnets = []string{}
|
|
state.LastUpdate = 0
|
|
|
|
go startWebServer(cfg.Port)
|
|
|
|
var lastUpdate int64
|
|
var subnets []string
|
|
gatewayStates := make(map[string]bool)
|
|
for _, gw := range cfg.Gateway {
|
|
gatewayStates[gw.Name] = false
|
|
}
|
|
|
|
for {
|
|
log.Println("--- Step 2: Checking gateways ---")
|
|
prevStates := make(map[string]bool)
|
|
for k, v := range gatewayStates {
|
|
prevStates[k] = v
|
|
}
|
|
|
|
newStates := make(map[string]bool)
|
|
for _, gw := range cfg.Gateway {
|
|
up := Ping(gw.Address, cfg.Timeout)
|
|
newStates[gw.Name] = up
|
|
log.Printf("Gateway %q (%s): %v", gw.Name, gw.Address, up)
|
|
}
|
|
|
|
changed := false
|
|
for _, gw := range cfg.Gateway {
|
|
if prevStates[gw.Name] != newStates[gw.Name] {
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
gatewayStates = newStates
|
|
|
|
state.mu.Lock()
|
|
for k, v := range gatewayStates {
|
|
state.GatewayStatus[k] = v
|
|
}
|
|
state.mu.Unlock()
|
|
|
|
if changed {
|
|
log.Println("Gateway states changed, updating routes (step 3)")
|
|
DoRouteUpdate(cfg, gatewayStates, subnets)
|
|
} else {
|
|
log.Println("Gateway states unchanged, skipping route update")
|
|
}
|
|
|
|
log.Println("--- Step 4: Getting update from controller ---")
|
|
ts, err := GetUpdate(cfg.Controller, cfg.Timeout)
|
|
if err != nil {
|
|
log.Printf("Controller update failed: %v", err)
|
|
state.mu.Lock()
|
|
state.ControllerUp = false
|
|
state.mu.Unlock()
|
|
time.Sleep(time.Duration(cfg.Interval) * time.Second)
|
|
continue
|
|
}
|
|
state.mu.Lock()
|
|
state.ControllerUp = true
|
|
state.mu.Unlock()
|
|
log.Printf("Controller timestamp: %d (lastUpdate: %d)", ts, lastUpdate)
|
|
|
|
if ts == lastUpdate {
|
|
log.Println("Timestamp unchanged, waiting and retrying")
|
|
time.Sleep(time.Duration(cfg.Interval) * time.Second)
|
|
continue
|
|
}
|
|
|
|
newSubnets, err := GetSubnets(cfg.Controller, cfg.Timeout)
|
|
if err != nil {
|
|
log.Printf("Failed to get subnets: %v", err)
|
|
state.mu.Lock()
|
|
state.ControllerUp = false
|
|
state.mu.Unlock()
|
|
time.Sleep(time.Duration(cfg.Interval) * time.Second)
|
|
continue
|
|
}
|
|
state.mu.Lock()
|
|
state.ControllerUp = true
|
|
state.mu.Unlock()
|
|
|
|
subnetsChanged := !stringSliceEqual(subnets, newSubnets)
|
|
subnets = newSubnets
|
|
|
|
state.mu.Lock()
|
|
state.Subnets = make([]string, len(subnets))
|
|
copy(state.Subnets, subnets)
|
|
state.SubnetCount = len(subnets)
|
|
state.mu.Unlock()
|
|
|
|
if subnetsChanged {
|
|
log.Printf("Subnets changed (%d subnets), updating lastUpdate", len(subnets))
|
|
lastUpdate = ts
|
|
state.mu.Lock()
|
|
state.LastUpdate = ts
|
|
state.mu.Unlock()
|
|
DoRouteUpdate(cfg, gatewayStates, subnets)
|
|
} else {
|
|
log.Println("Subnets unchanged, waiting")
|
|
time.Sleep(time.Duration(cfg.Interval) * time.Second)
|
|
}
|
|
}
|
|
}
|
|
|
|
func stringSliceEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|