From dbe58aa126cd26bd1e3749bf396dcb796a5095f0 Mon Sep 17 00:00:00 2001 From: buliangjun <34369005+buliangjunpp@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:21:04 +0800 Subject: [PATCH] fea: support list/add/del/save output (#48) --- cmd/api/v5/cmd.go | 1 + cmd/api/v5/config.go | 15 ++ cmd/api/v5/output.go | 135 ++++++++++++++++++ .../switch/network/network.json.example | 19 +-- .../openlan/switch/network/v1024.json.example | 18 +-- .../switch/output/example.json.example | 17 +++ docs/install.md | 2 +- pkg/api/api.go | 7 + pkg/api/output.go | 65 ++++++++- pkg/api/url.go | 2 +- pkg/config/network.go | 23 ++- pkg/config/switch.go | 1 + pkg/switch/network.go | 62 ++++++-- 13 files changed, 320 insertions(+), 47 deletions(-) create mode 100644 cmd/api/v5/output.go create mode 100644 dist/rootfs/etc/openlan/switch/output/example.json.example diff --git a/cmd/api/v5/cmd.go b/cmd/api/v5/cmd.go index a82729b..bdfc20a 100755 --- a/cmd/api/v5/cmd.go +++ b/cmd/api/v5/cmd.go @@ -47,4 +47,5 @@ func Commands(app *api.App) { Log{}.Commands(app) Guest{}.Commands(app) Knock{}.Commands(app) + Output{}.Commands(app) } diff --git a/cmd/api/v5/config.go b/cmd/api/v5/config.go index 1923d4d..6630286 100755 --- a/cmd/api/v5/config.go +++ b/cmd/api/v5/config.go @@ -144,6 +144,21 @@ func (u Config) Check(c *cli.Context) error { } } } + + // Check output config + out.Info("%15s: %s", "check", "output") + pattern = filepath.Join(dir, "switch", "output", "*.json") + if files, err := filepath.Glob(pattern); err == nil { + for _, file := range files { + var obj []config.Output + if err := libol.UnmarshalLoad(&obj, file); err != nil { + out.Warn("%15s: %s", filepath.Base(file), err) + } else { + out.Info("%15s: %s", filepath.Base(file), "success") + } + } + } + return nil } diff --git a/cmd/api/v5/output.go b/cmd/api/v5/output.go new file mode 100644 index 0000000..7e91e06 --- /dev/null +++ b/cmd/api/v5/output.go @@ -0,0 +1,135 @@ +package v5 + +import ( + "github.com/luscis/openlan/cmd/api" + "github.com/luscis/openlan/pkg/libol" + "github.com/luscis/openlan/pkg/schema" + "github.com/urfave/cli/v2" +) + +type Output struct { + Cmd +} + +func (o Output) Url(prefix, name string) string { + return prefix + "/api/network/" + name + "/output" +} + +func (o Output) Add(c *cli.Context) error { + network := c.String("network") + if len(network) == 0 { + return libol.NewErr("invalid network") + } + output := &schema.Output{ + Network: network, + Remote: c.String("remote"), + Segment: c.Int("segment"), + Protocol: c.String("protocol"), + } + url := o.Url(c.String("url"), network) + clt := o.NewHttp(c.String("token")) + if err := clt.PostJSON(url, output, nil); err != nil { + return err + } + return nil +} + +func (o Output) Remove(c *cli.Context) error { + network := c.String("network") + if len(network) == 0 { + return libol.NewErr("invalid network") + } + output := &schema.Output{ + Network: network, + Device: c.String("device"), + } + url := o.Url(c.String("url"), network) + clt := o.NewHttp(c.String("token")) + if err := clt.DeleteJSON(url, output, nil); err != nil { + return err + } + return nil +} + +func (o Output) Save(c *cli.Context) error { + network := c.String("network") + url := o.Url(c.String("url"), network) + + clt := o.NewHttp(c.String("token")) + if err := clt.PutJSON(url, nil, nil); err != nil { + return err + } + + return nil +} + +func (o Output) Tmpl() string { + return `# total {{ len . }} +{{ps -24 "network"}} {{ps -15 "protocol"}} {{ps -15 "Remote"}} {{ps -15 "segment"}} {{ps -15 "device"}} +{{- range . }} +{{ps -24 .Network}} {{ps -15 .Protocol}} {{ps -15 .Remote}} {{pi -15 .Segment }} {{ps -15 .Device}} +{{- end }} +` +} + +func (o Output) List(c *cli.Context) error { + url := o.Url(c.String("url"), c.String("network")) + clt := o.NewHttp(c.String("token")) + var items []schema.Output + if err := clt.GetJSON(url, &items); err != nil { + return err + } + return o.Out(items, c.String("format"), o.Tmpl()) +} + +func (o Output) Commands(app *api.App) { + app.Command(&cli.Command{ + Name: "output", + Aliases: []string{"op"}, + Usage: "Output configuration", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "Add an output for the network", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "network"}, + &cli.StringFlag{Name: "remote"}, + &cli.IntFlag{Name: "segment"}, + &cli.StringFlag{Name: "protocol"}, + //&cli.StringFlag{Name: "connection"}, + //&cli.StringFlag{Name: "secret"}, + //&cli.StringFlag{Name: "auth"}, + }, + Action: o.Add, + }, + { + Name: "remove", + Usage: "Remove an output from the network", + Aliases: []string{"rm"}, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "network"}, + &cli.StringFlag{Name: "device"}, + }, + Action: o.Remove, + }, + { + Name: "list", + Usage: "Display all outputs of the network", + Aliases: []string{"ls"}, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "network", Required: true}, + }, + Action: o.List, + }, + { + Name: "save", + Usage: "Save all outputs", + Aliases: []string{"sa"}, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "network", Required: true}, + }, + Action: o.Save, + }, + }, + }) +} diff --git a/dist/rootfs/etc/openlan/switch/network/network.json.example b/dist/rootfs/etc/openlan/switch/network/network.json.example index 5792b27..4bbd7a2 100755 --- a/dist/rootfs/etc/openlan/switch/network/network.json.example +++ b/dist/rootfs/etc/openlan/switch/network/network.json.example @@ -45,22 +45,5 @@ }, "acl": "acl-100", "dhcp": "enable", - "namespace": "example", - "outputs": [ - { - "segment": 43, - "remote": "3.3.3.5", - "dstport": 8899, - "protocol": "vxlan" - }, - { - "segment": 55, - "remote": "3.3.3.3", - "protocol": "gre" - }, - { - "segment": 23, - "remote": "enp2s2" - } - ] + "namespace": "example" } diff --git a/dist/rootfs/etc/openlan/switch/network/v1024.json.example b/dist/rootfs/etc/openlan/switch/network/v1024.json.example index 14d21b9..4e585e8 100755 --- a/dist/rootfs/etc/openlan/switch/network/v1024.json.example +++ b/dist/rootfs/etc/openlan/switch/network/v1024.json.example @@ -12,21 +12,5 @@ "startAt": "192.168.55.100", "endAt": "192.168.55.130" }, - "dhcp": "enable", - "outputs": [ - { - "segment": 43, - "remote": "3.3.3.5", - "protocol": "vxlan" - }, - { - "segment": 55, - "remote": "3.3.3.3", - "protocol": "gre" - }, - { - "segment": 23, - "remote": "enp2s2" - } - ] + "dhcp": "enable" } diff --git a/dist/rootfs/etc/openlan/switch/output/example.json.example b/dist/rootfs/etc/openlan/switch/output/example.json.example new file mode 100644 index 0000000..629fc2d --- /dev/null +++ b/dist/rootfs/etc/openlan/switch/output/example.json.example @@ -0,0 +1,17 @@ +[ + { + "segment": 43, + "remote": "3.3.3.5", + "dstport": 8899, + "protocol": "vxlan" + }, + { + "segment": 55, + "remote": "3.3.3.3", + "protocol": "gre" + }, + { + "segment": 23, + "remote": "enp2s2" + } +] \ No newline at end of file diff --git a/docs/install.md b/docs/install.md index 0444232..9573656 100755 --- a/docs/install.md +++ b/docs/install.md @@ -36,7 +36,7 @@ OpenLAN软件包含下面部分: { "protocol": "tcp", "crypt": { - "algo": "aes-128", ## 支持xor,aes-128,aes-192等对称加密算法 + "algorithm": "aes-128", ## 支持xor,aes-128,aes-192等对称加密算法 "secret": "ea64d5b0c96c" } } diff --git a/pkg/api/api.go b/pkg/api/api.go index 28915d2..9eeeabc 100755 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -55,6 +55,12 @@ type Qoser interface { Save() } +type Outputer interface { + AddOutput(segment int, protocol, Remote string) + DelOutput(device string) + SaveOutput() +} + type Networker interface { String() string ID() string @@ -70,6 +76,7 @@ type Networker interface { Qoser() Qoser IfAddr() string ACLer() ACLer + Outputer } var workers = make(map[string]Networker) diff --git a/pkg/api/output.go b/pkg/api/output.go index e0a6dba..8fcf7ff 100755 --- a/pkg/api/output.go +++ b/pkg/api/output.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "net/http" "github.com/gorilla/mux" @@ -17,6 +18,8 @@ type Output struct { func (h Output) Router(router *mux.Router) { router.HandleFunc("/api/network/{id}/output", h.Get).Methods("GET") router.HandleFunc("/api/network/{id}/output", h.Post).Methods("POST") + router.HandleFunc("/api/network/{id}/output", h.Delete).Methods("DELETE") + router.HandleFunc("/api/network/{id}/output", h.Save).Methods("PUT") } func (h Output) Get(w http.ResponseWriter, r *http.Request) { @@ -35,5 +38,65 @@ func (h Output) Get(w http.ResponseWriter, r *http.Request) { } func (h Output) Post(w http.ResponseWriter, r *http.Request) { - ResponseJson(w, "outputs") + output := &schema.Output{} + if err := GetData(r, output); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + cs := h.Switcher.Config() + if cs.Network == nil { + http.Error(w, "switch has no network can not add output", http.StatusBadRequest) + return + } + network := cs.GetNetwork(output.Network) + if network == nil { + http.Error(w, fmt.Sprintf("switch has no network with %s can not add output", output.Network), http.StatusBadRequest) + return + } + worker := GetWorker(output.Network) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + worker.AddOutput(output.Segment, output.Protocol, output.Remote) + ResponseMsg(w, 0, "") +} + +func (h Output) Delete(w http.ResponseWriter, r *http.Request) { + output := &schema.Output{} + if err := GetData(r, output); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + cs := h.Switcher.Config() + if cs.Network == nil { + http.Error(w, "switch has no network can not del output", http.StatusBadRequest) + return + } + network := cs.GetNetwork(output.Network) + if network == nil { + http.Error(w, fmt.Sprintf("switch has no network with %s can not del output", output.Network), http.StatusBadRequest) + return + } + worker := GetWorker(output.Network) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + worker.DelOutput(output.Device) + ResponseMsg(w, 0, "") +} + +func (h Output) Save(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + + worker := GetWorker(id) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + worker.SaveOutput() + + ResponseJson(w, "success") } diff --git a/pkg/api/url.go b/pkg/api/url.go index b4d0b76..ba28c71 100755 --- a/pkg/api/url.go +++ b/pkg/api/url.go @@ -24,6 +24,6 @@ func Add(router *mux.Router, switcher Switcher) { OpenAPI{}.Router(router) ZTrust{}.Router(router) QosApi{}.Router(router) - Output{}.Router(router) + Output{Switcher: switcher}.Router(router) ACL{}.Router(router) } diff --git a/pkg/config/network.go b/pkg/config/network.go index f3ed247..ac99580 100755 --- a/pkg/config/network.go +++ b/pkg/config/network.go @@ -22,7 +22,7 @@ type Network struct { Acl string `json:"acl,omitempty"` Specifies interface{} `json:"specifies,omitempty"` Dhcp string `json:"dhcp,omitempty"` - Outputs []Output `json:"outputs"` + Outputs []Output `json:"outputs,omitempty"` ZTrust string `json:"ztrust"` Qos string `json:"qos,omitempty"` Namespace string `json:"namespace"` @@ -122,15 +122,26 @@ func (n *Network) LoadRoute() { } } +func (n *Network) LoadOutput() { + file := n.Dir("output", n.Name+".json") + if err := libol.FileExist(file); err == nil { + if err := libol.UnmarshalLoad(&n.Outputs, file); err != nil { + libol.Error("Network.LoadOutput... %n", err) + } + } +} + func (n *Network) Save() { obj := *n obj.Routes = nil obj.Links = nil + obj.Outputs = nil if err := libol.MarshalSave(&obj, obj.File, true); err != nil { libol.Error("Network.Save %s %s", obj.Name, err) } n.SaveRoute() n.SaveLink() + n.SaveOutput() } func (n *Network) SaveRoute() { @@ -153,6 +164,16 @@ func (n *Network) SaveLink() { } } +func (n *Network) SaveOutput() { + file := n.Dir("output", n.Name+".json") + if n.Outputs == nil { + return + } + if err := libol.MarshalSave(n.Outputs, file, true); err != nil { + libol.Error("Network.SaveOutput %s %s", n.Name, err) + } +} + func (n *Network) Reload() { switch n.Provider { case "esp": diff --git a/pkg/config/switch.go b/pkg/config/switch.go index f5413ea..f2e9680 100755 --- a/pkg/config/switch.go +++ b/pkg/config/switch.go @@ -187,6 +187,7 @@ func (s *Switch) LoadNetwork() { } obj.LoadLink() obj.LoadRoute() + obj.LoadOutput() s.Network = append(s.Network, obj) } s.Format() diff --git a/pkg/switch/network.go b/pkg/switch/network.go index dc788cb..394f3d9 100755 --- a/pkg/switch/network.go +++ b/pkg/switch/network.go @@ -153,7 +153,7 @@ func (w *WorkerImpl) AddPhysical(bridge string, output string) { } } -func (w *WorkerImpl) AddOutput(bridge string, port *LinuxPort) { +func (w *WorkerImpl) addOutput(bridge string, port *LinuxPort) { cfg := port.cfg out := &models.Output{ Network: w.cfg.Name, @@ -210,11 +210,11 @@ func (w *WorkerImpl) AddOutput(bridge string, port *LinuxPort) { } else { link, err := nl.LinkByName(cfg.Remote) if link == nil { - w.out.Error("WorkerImpl.AddOutput %s %s", cfg.Remote, err) + w.out.Error("WorkerImpl.addOutput %s %s", cfg.Remote, err) return } if err := nl.LinkSetUp(link); err != nil { - w.out.Warn("WorkerImpl.AddOutput %s %s", cfg.Remote, err) + w.out.Warn("WorkerImpl.addOutput %s %s", cfg.Remote, err) } if cfg.Segment > 0 { @@ -246,7 +246,7 @@ func (w *WorkerImpl) AddOutput(bridge string, port *LinuxPort) { out.Device = port.link cache.Output.Add(port.link, out) - w.out.Info("WorkerImpl.AddOutput %s %s", port.link, port.String()) + w.out.Info("WorkerImpl.addOutput %s %s", port.link, port.String()) w.AddPhysical(bridge, port.link) } @@ -349,7 +349,7 @@ func (w *WorkerImpl) Start(v api.Switcher) { port := &LinuxPort{ cfg: output, } - w.AddOutput(cfg.Bridge.Name, port) + w.addOutput(cfg.Bridge.Name, port) w.outputs = append(w.outputs, port) } @@ -425,9 +425,9 @@ func (w *WorkerImpl) DelPhysical(bridge string, output string) { } } -func (w *WorkerImpl) DelOutput(bridge string, port *LinuxPort) { +func (w *WorkerImpl) delOutput(bridge string, port *LinuxPort) { cfg := port.cfg - w.out.Info("WorkerImpl.DelOutput %s %s", port.link, port.String()) + w.out.Info("WorkerImpl.delOutput %s %s", port.link, port.String()) cache.Output.Del(port.link) w.DelPhysical(bridge, port.link) @@ -513,7 +513,7 @@ func (w *WorkerImpl) Stop() { } for _, output := range w.outputs { - w.DelOutput(w.cfg.Bridge.Name, output) + w.delOutput(w.cfg.Bridge.Name, output) } w.outputs = nil @@ -811,3 +811,49 @@ func (w *WorkerImpl) IfAddr() string { func (w *WorkerImpl) ACLer() api.ACLer { return w.acl } + +func (w *WorkerImpl) AddOutput(segment int, protocol, Remote string) { + output := co.Output{ + Segment: segment, + Protocol: protocol, + Remote: Remote, + } + w.cfg.Outputs = append(w.cfg.Outputs, output) + port := &LinuxPort{ + cfg: output, + } + w.addOutput(w.cfg.Bridge.Name, port) + w.outputs = append(w.outputs, port) +} + +func (w *WorkerImpl) DelOutput(device string) { + var linuxport *LinuxPort + for _, v := range w.outputs { + if v.link == device { + linuxport = v + break + } + } + if linuxport == nil { + return + } + Outputs := make([]co.Output, 0, len(w.cfg.Outputs)) + for _, v := range w.cfg.Outputs { + if v != linuxport.cfg { + Outputs = append(Outputs, v) + } + } + w.cfg.Outputs = Outputs + w.delOutput(w.cfg.Bridge.Name, linuxport) + outputs := make([]*LinuxPort, 0, len(w.outputs)) + for _, v := range w.outputs { + if v != linuxport { + outputs = append(outputs, v) + } + } + w.outputs = outputs +} + +func (w *WorkerImpl) SaveOutput() { + w.cfg.SaveOutput() +}