package main import ( "flag" "fmt" "time" db "github.com/luscis/openlan/pkg/database" "github.com/luscis/openlan/pkg/libol" ) type Config struct { UdpPort int LogLevel int LogFile string } func (c *Config) Parse() { flag.IntVar(&c.UdpPort, "port", 4500, "UDP port listen on") flag.StringVar(&c.LogFile, "log:file", "/dev/null", "File log saved to") flag.IntVar(&c.LogLevel, "log:level", 20, "Log level value") flag.Parse() } type UdpServer struct { stop chan struct{} out *libol.SubLogger server *libol.UdpInServer cfg *Config links *libol.SafeStrMap } func NewUdpServer(cfg *Config) *UdpServer { c := &UdpServer{ out: libol.NewSubLogger("udp"), stop: make(chan struct{}), cfg: cfg, links: libol.NewSafeStrMap(128), } return c } func (u *UdpServer) Initialize() { u.server = &libol.UdpInServer{ Port: uint16(u.cfg.UdpPort), } } func (u *UdpServer) Start() { u.out.Info("UdpServer.Start on %d", u.server.Port) if err := u.server.Open(); err != nil { u.out.Error("UdpServer.Start open socket %s", err) return } } func (u *UdpServer) Stop() { u.out.Info("UdpServer.Stop on %d", u.server.Port) } func (u *UdpServer) Device2UUID(value string) string { if link := u.links.Get(value); link != nil { if older, ok := link.(*db.VirtualLink); ok { return older.UUID } } return "" } func (u *UdpServer) toStatus(li *db.DBClient, from *libol.UdpInConnection) { device := fmt.Sprintf("spi:%d", from.Spi) obj := &db.VirtualLink{ UUID: u.Device2UUID(device), } if err := li.Client.Get(obj); err != nil { return } if obj.Status == nil { obj.Status = make(map[string]string, 2) } new_conn := fmt.Sprintf("udp:%s", from.Connection()) if obj.Status["remote_connection"] != new_conn || obj.Status["hostname"] != from.Hostname { obj.Status["remote_connection"] = new_conn obj.Status["hostname"] = from.Hostname u.out.Info("Updating %s on %s from %s", device, new_conn, from.Hostname) } // Updating status obj.Status["update_at"] = time.Now().UTC().String() ops, err := li.Client.Where(obj).Update(obj) if err != nil { u.out.Warn("UdpServer.toStatus update %s", err) return } if _, err := li.Client.Transact(ops...); err != nil { u.out.Warn("UdpServer.toStatus commit %s", err) return } // Reply pong if obj.Connection == "any" { _ = u.server.Send(from) } } func (u *UdpServer) toLinkState(li *db.DBClient, from *libol.UdpInConnection) { device := fmt.Sprintf("spi:%d", from.Spi) obj := &db.VirtualLink{ UUID: u.Device2UUID(device), } if err := li.Client.Get(obj); err != nil { return } if obj.LinkState != "up" { obj.LinkState = "up" ops, err := li.Client.Where(obj).Update(obj) if err != nil { u.out.Warn("UdpServer.toLinkState update %s", err) return } if _, err := li.Client.Transact(ops...); err != nil { u.out.Warn("UdpServer.toLinkState commit %s", err) return } } // TODO check update_at and update to down if expired. } func (u *UdpServer) Pong() { li, err := db.NewClient(nil) if err != nil { u.out.Error("UdpServer.Pong open db with %s", err) return } for { from, _ := u.server.Recv() u.out.Cmd("UdpServer.Pong received %s", from.String()) u.toStatus(li, from) u.toLinkState(li, from) } } func (u *UdpServer) toPing(li *db.DBClient, obj *db.VirtualLink) { addr, port := db.GetAddrPort(obj.Connection[4:]) if port == 0 { port = 4500 } conn := &libol.UdpInConnection{ Spi: obj.Spi(), RemotePort: uint16(port), RemoteAddr: addr, } u.out.Cmd("UdpServer.toPing send to %s", conn.String()) _ = u.server.Send(conn) } func (u *UdpServer) Ping() { li, err := db.NewClient(nil) if err != nil { u.out.Error("UdpServer.Ping open db with %s", err) return } for { var ls []db.VirtualLink _ = li.Client.List(&ls) u.links.Clear() for i := range ls { obj := &ls[i] if err := u.links.Mod(obj.Device, obj); err != nil { u.out.Error("UdpServer.Ping %s", err) } if !obj.IsUdpIn() { continue } u.toPing(li, obj) } time.Sleep(10 * time.Second) } } func main() { c := &Config{} c.Parse() libol.SetLogger(c.LogFile, c.LogLevel) srv := NewUdpServer(c) srv.Initialize() srv.Start() libol.Go(srv.Ping) libol.Go(srv.Pong) libol.Wait() srv.Stop() }