From ef79645ed83c667dcecf4bbab12069d5a3b24881 Mon Sep 17 00:00:00 2001 From: Daniel Ding Date: Mon, 24 Nov 2025 20:30:30 +0800 Subject: [PATCH] fea: cli for router tunnels. --- cmd/api/v5/ceci.go | 43 ++++++++++++-- cmd/api/v5/cmd.go | 1 + cmd/api/v5/ipsec.go | 20 +++++++ cmd/api/v5/router.go | 109 ++++++++++++++++++++++++++++++++++++ pkg/api/api.go | 18 ++++-- pkg/api/network.go | 49 ++++++++++++++++ pkg/api/url.go | 1 + pkg/cache/network.go | 2 - pkg/config/network.go | 4 ++ pkg/config/router.go | 27 +++++++++ pkg/models/network.go | 4 +- pkg/schema/network.go | 6 ++ pkg/switch/bgp_linux.go | 2 + pkg/switch/ceci_linux.go | 2 + pkg/switch/ipsec_linux.go | 3 + pkg/switch/network_linux.go | 39 +++++++------ pkg/switch/router_linux.go | 40 ++++++++++--- 17 files changed, 330 insertions(+), 40 deletions(-) create mode 100644 cmd/api/v5/router.go diff --git a/cmd/api/v5/ceci.go b/cmd/api/v5/ceci.go index 1fa9d03..70a98b6 100755 --- a/cmd/api/v5/ceci.go +++ b/cmd/api/v5/ceci.go @@ -13,10 +13,43 @@ type Ceci struct { } func (u Ceci) Url(prefix string) string { + return prefix + "/api/network/ceci" +} + +func (u Ceci) List(c *cli.Context) error { + url := u.Url(c.String("url")) + clt := u.NewHttp(c.String("token")) + var data schema.Network + if err := clt.GetJSON(url, &data); err != nil { + return err + } + return u.Out(data, "yaml", "") +} + +func (u Ceci) Commands(app *api.App) { + app.Command(&cli.Command{ + Name: "ceci", + Usage: "Ceci TCP proxy", + Subcommands: []*cli.Command{ + { + Name: "ls", + Usage: "List a ceci TCP", + Action: u.List, + }, + CeciTCP{}.Commands(app), + }, + }) +} + +type CeciTCP struct { + Cmd +} + +func (u CeciTCP) Url(prefix string) string { return prefix + "/api/network/ceci/tcp" } -func (u Ceci) Add(c *cli.Context) error { +func (u CeciTCP) Add(c *cli.Context) error { target := strings.Split(c.String("target"), ",") data := &schema.CeciTcp{ Mode: c.String("mode"), @@ -31,7 +64,7 @@ func (u Ceci) Add(c *cli.Context) error { return nil } -func (u Ceci) Remove(c *cli.Context) error { +func (u CeciTCP) Remove(c *cli.Context) error { data := &schema.CeciTcp{ Listen: c.String("listen"), } @@ -43,8 +76,8 @@ func (u Ceci) Remove(c *cli.Context) error { return nil } -func (u Ceci) Commands(app *api.App) { - app.Command(&cli.Command{ +func (u CeciTCP) Commands(app *api.App) *cli.Command { + return &cli.Command{ Name: "ceci", Usage: "Ceci TCP proxy", Subcommands: []*cli.Command{ @@ -68,5 +101,5 @@ func (u Ceci) Commands(app *api.App) { Action: u.Remove, }, }, - }) + } } diff --git a/cmd/api/v5/cmd.go b/cmd/api/v5/cmd.go index eced225..f5735a9 100755 --- a/cmd/api/v5/cmd.go +++ b/cmd/api/v5/cmd.go @@ -43,4 +43,5 @@ func Commands(app *api.App) { BGP{}.Commands(app) Ceci{}.Commands(app) Prefix{}.Commands(app) + Router{}.Commands(app) } diff --git a/cmd/api/v5/ipsec.go b/cmd/api/v5/ipsec.go index 1938684..dfac761 100644 --- a/cmd/api/v5/ipsec.go +++ b/cmd/api/v5/ipsec.go @@ -10,12 +10,32 @@ type IPSec struct { Cmd } +func (o IPSec) Url(prefix string) string { + return prefix + "/api/network/ipsec" +} + +func (o IPSec) List(c *cli.Context) error { + url := o.Url(c.String("url")) + clt := o.NewHttp(c.String("token")) + var data schema.Network + if err := clt.GetJSON(url, &data); err != nil { + return err + } + return o.Out(data, "yaml", "") +} + func (o IPSec) Commands(app *api.App) { tunnel := IPSecTunnel{} app.Command(&cli.Command{ Name: "ipsec", Usage: "IPSec configuration", Subcommands: []*cli.Command{ + { + Name: "ls", + Usage: "Display ipsec network", + Aliases: []string{"ls"}, + Action: o.List, + }, tunnel.Commands(), }, }) diff --git a/cmd/api/v5/router.go b/cmd/api/v5/router.go new file mode 100644 index 0000000..755bda3 --- /dev/null +++ b/cmd/api/v5/router.go @@ -0,0 +1,109 @@ +package v5 + +import ( + "github.com/luscis/openlan/cmd/api" + "github.com/luscis/openlan/pkg/schema" + "github.com/urfave/cli/v2" +) + +// openlan router tunnel add --remote 1.1.1.2 --address 192.168.1.1 --protocol gre +// openlan router tunnel add --remote 1.1.1.2 --address 192.168.1.1 --protocol ipip +// openlan router tunnel remove --remote 1.1.1.2 --address 192.168.1.1 + +type Router struct { + Cmd +} + +func (b Router) Url(prefix string) string { + return prefix + "/api/network/router" +} + +func (b Router) List(c *cli.Context) error { + url := b.Url(c.String("url")) + clt := b.NewHttp(c.String("token")) + var data schema.Network + if err := clt.GetJSON(url, &data); err != nil { + return err + } + return b.Out(data, "yaml", "") +} + +func (b Router) Commands(app *api.App) { + app.Command(&cli.Command{ + Name: "router", + Usage: "Router", + Subcommands: []*cli.Command{ + { + Name: "ls", + Usage: "Display router network", + Aliases: []string{"ls"}, + Action: b.List, + }, + RouterTunnel{}.Commands(), + }, + }) +} + +type RouterTunnel struct { + Cmd +} + +func (s RouterTunnel) Url(prefix string) string { + return prefix + "/api/network/router/tunnel" +} + +func (s RouterTunnel) Add(c *cli.Context) error { + data := &schema.RouterTunnel{ + Remote: c.String("remote"), + Address: c.String("address"), + Protocol: c.String("protocol"), + } + url := s.Url(c.String("url")) + clt := s.NewHttp(c.String("token")) + if err := clt.PostJSON(url, data, nil); err != nil { + return err + } + return nil +} + +func (s RouterTunnel) Remove(c *cli.Context) error { + data := &schema.RouterTunnel{ + Remote: c.String("remote"), + Protocol: c.String("protocol"), + } + url := s.Url(c.String("url")) + clt := s.NewHttp(c.String("token")) + if err := clt.DeleteJSON(url, data, nil); err != nil { + return err + } + return nil +} + +func (s RouterTunnel) Commands() *cli.Command { + return &cli.Command{ + Name: "tunnel", + Usage: "Router tunnels", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "Add router tunnel", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "address", Required: true}, + &cli.StringFlag{Name: "remote", Required: true}, + &cli.StringFlag{Name: "protocol", Value: "gre"}, + }, + Action: s.Add, + }, + { + Name: "remove", + Aliases: []string{"rm"}, + Usage: "Remove router tunnel", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "remote", Required: true}, + &cli.StringFlag{Name: "protocol", Value: "gre"}, + }, + Action: s.Remove, + }, + }, + } +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 532e8e3..6468b88 100755 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -156,11 +156,17 @@ type CeciApi interface { DelTcp(data schema.CeciTcp) } +type RouterApi interface { + AddTunnel(data schema.RouterTunnel) error + DelTunnel(data schema.RouterTunnel) error +} + type callApi struct { - ipsecApi IPSecApi - bgpApi BgpApi - ceciApi CeciApi - workers map[string]NetworkApi + ipsecApi IPSecApi + bgpApi BgpApi + ceciApi CeciApi + routerApi RouterApi + workers map[string]NetworkApi } func (i *callApi) AddWorker(name string, obj NetworkApi) { @@ -189,6 +195,10 @@ func (i *callApi) SetCeciApi(value CeciApi) { i.ceciApi = value } +func (i *callApi) SetRouterApi(value RouterApi) { + i.routerApi = value +} + var Call = &callApi{ workers: make(map[string]NetworkApi), } diff --git a/pkg/api/network.go b/pkg/api/network.go index c93fb3b..ca9d8a3 100755 --- a/pkg/api/network.go +++ b/pkg/api/network.go @@ -232,3 +232,52 @@ func (h DNAT) Delete(w http.ResponseWriter, r *http.Request) { } ResponseJson(w, "success") } + +type RouterTunnel struct { + cs SwitchApi +} + +func (h RouterTunnel) Router(router *mux.Router) { + router.HandleFunc("/api/network/router/tunnel", h.Post).Methods("POST") + router.HandleFunc("/api/network/router/tunnel", h.Delete).Methods("DELETE") +} + +func (h RouterTunnel) Post(w http.ResponseWriter, r *http.Request) { + caller := Call.routerApi + if caller == nil { + http.Error(w, "Router not found", http.StatusBadRequest) + return + } + + value := schema.RouterTunnel{} + if err := GetData(r, &value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := caller.AddTunnel(value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + ResponseJson(w, "success") +} + +func (h RouterTunnel) Delete(w http.ResponseWriter, r *http.Request) { + caller := Call.routerApi + if caller == nil { + http.Error(w, "Router not found", http.StatusBadRequest) + return + } + + value := schema.RouterTunnel{} + if err := GetData(r, &value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := caller.DelTunnel(value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + ResponseJson(w, "success") +} diff --git a/pkg/api/url.go b/pkg/api/url.go index 2149703..15a49aa 100755 --- a/pkg/api/url.go +++ b/pkg/api/url.go @@ -30,5 +30,6 @@ func Add(router *mux.Router, cs SwitchApi) { Rate{cs: cs}.Router(router) SNAT{}.Router(router) DNAT{}.Router(router) + RouterTunnel{}.Router(router) Network{cs: cs}.Router(router) } diff --git a/pkg/cache/network.go b/pkg/cache/network.go index 9c0d5e1..f154804 100755 --- a/pkg/cache/network.go +++ b/pkg/cache/network.go @@ -16,12 +16,10 @@ type network struct { } func (w *network) Add(n *models.Network) { - libol.Debug("network.Add %v", *n) _ = w.Networks.Set(n.Name, n) } func (w *network) Del(name string) { - libol.Debug("network.Del %s", name) w.Networks.Del(name) } diff --git a/pkg/config/network.go b/pkg/config/network.go index 9c0bfc0..7487151 100755 --- a/pkg/config/network.go +++ b/pkg/config/network.go @@ -59,10 +59,14 @@ func (n *Network) NewSpecifies() any { case "bgp": n.Specifies = &BgpSpecifies{} case "ceci": + n.Specifies = &CeciSpecifies{} default: n.Specifies = nil } + if n.Specifies != nil { + n.Name = n.Provider + } return n.Specifies } diff --git a/pkg/config/router.go b/pkg/config/router.go index da1fd74..203189d 100644 --- a/pkg/config/router.go +++ b/pkg/config/router.go @@ -47,3 +47,30 @@ func (n *RouterSpecifies) Correct() { t.Correct() } } + +func (n *RouterSpecifies) FindTunnel(value *RouterTunnel) (*RouterTunnel, int) { + for index, obj := range n.Tunnels { + if obj.Id() == value.Id() { + return obj, index + } + } + return nil, -1 +} + +func (n *RouterSpecifies) AddTunnel(value *RouterTunnel) bool { + _, index := n.FindTunnel(value) + if index == -1 { + n.Tunnels = append(n.Tunnels, value) + return true + } + return false +} + +func (n *RouterSpecifies) DelTunnel(value *RouterTunnel) (*RouterTunnel, bool) { + older, index := n.FindTunnel(value) + if index != -1 { + n.Tunnels = append(n.Tunnels[:index], n.Tunnels[index+1:]...) + return older, true + } + return older, false +} diff --git a/pkg/models/network.go b/pkg/models/network.go index a8062e9..cf1fdee 100755 --- a/pkg/models/network.go +++ b/pkg/models/network.go @@ -45,10 +45,10 @@ type Network struct { Name string `json:"name"` Tenant string `json:"tenant,omitempty"` Gateway string `json:"gateway,omitempty"` - Address string `json:"address"` + Address string `json:"address,omitempty"` IpStart string `json:"startAt,omitempty"` IpEnd string `json:"endAt,omitempty"` - Netmask string `json:"netmask"` + Netmask string `json:"netmask,omitempty"` Routes []*Route `json:"routes,omitempty"` Config interface{} `json:"config,omitempty"` } diff --git a/pkg/schema/network.go b/pkg/schema/network.go index f3c9c03..0bac9a5 100755 --- a/pkg/schema/network.go +++ b/pkg/schema/network.go @@ -52,3 +52,9 @@ type DNAT struct { ToDest string `json:"todestination"` ToDport int `json:"todport"` } + +type RouterTunnel struct { + Protocol string `json:"protocol"` + Remote string `json:"remote"` + Address string `json:"address"` +} diff --git a/pkg/switch/bgp_linux.go b/pkg/switch/bgp_linux.go index 1f125f4..2fef2f9 100644 --- a/pkg/switch/bgp_linux.go +++ b/pkg/switch/bgp_linux.go @@ -25,6 +25,7 @@ func NewBgpWorker(c *co.Network) *BgpWorker { w := &BgpWorker{ WorkerImpl: NewWorkerApi(c), } + api.Call.SetBgpApi(w) w.spec, _ = c.Specifies.(*co.BgpSpecifies) return w } @@ -83,6 +84,7 @@ route-map {{ .Address }}-out permit 10 func (w *BgpWorker) Initialize() { w.out.Info("BgpWorker.Initialize") + w.addCache() } func (w *BgpWorker) save() { diff --git a/pkg/switch/ceci_linux.go b/pkg/switch/ceci_linux.go index 094c38c..381c7ef 100644 --- a/pkg/switch/ceci_linux.go +++ b/pkg/switch/ceci_linux.go @@ -24,12 +24,14 @@ func NewCeciWorker(c *co.Network) *CeciWorker { w := &CeciWorker{ WorkerImpl: NewWorkerApi(c), } + api.Call.SetCeciApi(w) w.spec, _ = c.Specifies.(*co.CeciSpecifies) return w } func (w *CeciWorker) Initialize() { w.out.Info("CeciWorker.Initialize") + w.addCache() } func (w *CeciWorker) killPid(name string) { diff --git a/pkg/switch/ipsec_linux.go b/pkg/switch/ipsec_linux.go index a0ee371..3d7bed2 100644 --- a/pkg/switch/ipsec_linux.go +++ b/pkg/switch/ipsec_linux.go @@ -28,6 +28,8 @@ func NewIPSecWorker(c *co.Network) *IPSecWorker { w := &IPSecWorker{ WorkerImpl: NewWorkerApi(c), } + api.Call.SetIPSecApi(w) + w.spec, _ = c.Specifies.(*co.IPSecSpecifies) return w } @@ -102,6 +104,7 @@ func (w *IPSecWorker) Initialize() { if err := os.Mkdir(IPSecLogDir, 0600); err != nil { w.out.Warn("IPSecWorker.Initialize %s", err) } + w.addCache() } func (w *IPSecWorker) saveSec(name, tmpl string, data interface{}) error { diff --git a/pkg/switch/network_linux.go b/pkg/switch/network_linux.go index 86f80f8..098686b 100755 --- a/pkg/switch/network_linux.go +++ b/pkg/switch/network_linux.go @@ -23,19 +23,13 @@ func NewNetworker(c *co.Network) api.NetworkApi { switch c.Provider { case "ipsec": - secer := NewIPSecWorker(c) - api.Call.SetIPSecApi(secer) - obj = secer + obj = NewIPSecWorker(c) case "bgp": - bgper := NewBgpWorker(c) - api.Call.SetBgpApi(bgper) - obj = bgper + obj = NewBgpWorker(c) case "router": obj = NewRouterWorker(c) case "ceci": - cecer := NewCeciWorker(c) - api.Call.SetCeciApi(cecer) - obj = cecer + obj = NewCeciWorker(c) default: obj = NewOpenLANWorker(c) } @@ -85,6 +79,21 @@ func (w *WorkerImpl) Provider() string { return w.cfg.Provider } +func (w *WorkerImpl) addCache() { + cfg := w.cfg + n := models.Network{ + Name: cfg.Name, + Config: cfg, + } + if cfg.Subnet != nil { + n.IpStart = cfg.Subnet.Start + n.IpEnd = cfg.Subnet.End + n.Netmask = cfg.Subnet.Netmask + n.Address = cfg.Bridge.Address + } + cache.Network.Add(&n) +} + func (w *WorkerImpl) Initialize() { cfg := w.cfg @@ -96,18 +105,8 @@ func (w *WorkerImpl) Initialize() { w.acl = NewACL(cfg.Name) w.acl.Initialize() + w.addCache() w.findhop = NewFindHop(cfg.Name, cfg) - if cfg.Subnet != nil { - n := models.Network{ - Name: cfg.Name, - IpStart: cfg.Subnet.Start, - IpEnd: cfg.Subnet.End, - Netmask: cfg.Subnet.Netmask, - Address: cfg.Bridge.Address, - Config: cfg, - } - cache.Network.Add(&n) - } w.updateVPN() w.createVPN() diff --git a/pkg/switch/router_linux.go b/pkg/switch/router_linux.go index 838b287..e12687e 100755 --- a/pkg/switch/router_linux.go +++ b/pkg/switch/router_linux.go @@ -4,6 +4,7 @@ import ( "github.com/luscis/openlan/pkg/api" co "github.com/luscis/openlan/pkg/config" "github.com/luscis/openlan/pkg/libol" + "github.com/luscis/openlan/pkg/schema" nl "github.com/vishvananda/netlink" ) @@ -17,6 +18,7 @@ func NewRouterWorker(c *co.Network) *RouterWorker { w := &RouterWorker{ WorkerImpl: NewWorkerApi(c), } + api.Call.SetRouterApi(w) w.spec, _ = c.Specifies.(*co.RouterSpecifies) return w } @@ -74,7 +76,7 @@ func (w *RouterWorker) Start(v api.SwitchApi) { w.uuid = v.UUID() for _, tun := range w.spec.Tunnels { - w.AddTunnel(tun) + w.addTunnel(tun) } w.WorkerImpl.Start(v) @@ -102,7 +104,7 @@ func (w *RouterWorker) Stop() { w.WorkerImpl.Stop() for _, tun := range w.spec.Tunnels { - w.DelTunnel(tun) + w.delTunnel(tun) } } @@ -112,7 +114,7 @@ func (w *RouterWorker) Reload(v api.SwitchApi) { w.Start(v) } -func (w *RouterWorker) AddTunnel(data *co.RouterTunnel) { +func (w *RouterWorker) addTunnel(data *co.RouterTunnel) { var link nl.Link switch data.Protocol { @@ -159,14 +161,38 @@ func (w *RouterWorker) AddTunnel(data *co.RouterTunnel) { } } -func (w *RouterWorker) DelTunnel(data *co.RouterTunnel) { - if data.Link == "" { - return - } +func (w *RouterWorker) delTunnel(data *co.RouterTunnel) { if link, err := nl.LinkByName(data.Link); err == nil { if err := nl.LinkDel(link); err != nil { w.out.Error("RouterWorker.DelTunnel %s %s", data.Id(), err) return } + } else { + w.out.Warn("RouterWorker.DelTunnel notFound %s:%s", data.Id(), data.Link) } } + +func (w *RouterWorker) AddTunnel(data schema.RouterTunnel) error { + obj := &co.RouterTunnel{ + Remote: data.Remote, + Protocol: data.Protocol, + Address: data.Address, + } + obj.Correct() + if ok := w.spec.AddTunnel(obj); ok { + w.addTunnel(obj) + } + return nil +} + +func (w *RouterWorker) DelTunnel(data schema.RouterTunnel) error { + obj := &co.RouterTunnel{ + Remote: data.Remote, + Protocol: data.Protocol, + } + obj.Correct() + if old, ok := w.spec.DelTunnel(obj); ok { + w.delTunnel(old) + } + return nil +}