fea: list prefix route for system.

This commit is contained in:
Daniel Ding
2025-09-05 11:04:33 +08:00
parent db26f4881f
commit 19829f1c5a
17 changed files with 366 additions and 253 deletions

View File

@@ -42,4 +42,5 @@ func Commands(app *api.App) {
Rate{}.Commands(app) Rate{}.Commands(app)
Ceci{}.Commands(app) Ceci{}.Commands(app)
BGP{}.Commands(app) BGP{}.Commands(app)
Prefix{}.Commands(app)
} }

View File

@@ -1,53 +0,0 @@
package v5
import (
"github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2"
)
type Device struct {
Cmd
}
func (u Device) Url(prefix, name string) string {
if name == "" {
return prefix + "/api/device"
} else {
return prefix + "/api/device/" + name
}
}
func (u Device) Tmpl() string {
return `# total {{ len . }}
{{ps -13 "name"}} {{ps -13 "mtu"}} {{ps -16 "mac"}} {{ps -6 "provider"}}
{{- range . }}
{{ps -13 .Name}} {{pi -13 .Mtu}} {{ps -16 .Mac}} {{ps -6 .Provider}}
{{- end }}
`
}
func (u Device) List(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
var items []schema.Device
if err := clt.GetJSON(url, &items); err != nil {
return err
}
return u.Out(items, c.String("format"), u.Tmpl())
}
func (u Device) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "device",
Usage: "linux network device",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "Display all devices",
Aliases: []string{"ls"},
Action: u.List,
},
},
})
}

View File

@@ -1,62 +0,0 @@
package v5
import (
"github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2"
)
type Log struct {
Cmd
}
func (v Log) Url(prefix, name string) string {
return prefix + "/api/log"
}
func (v Log) Tmpl() string {
return `File : {{ .File }}
Level: {{ .Level}}
`
}
func (v Log) List(c *cli.Context) error {
url := v.Url(c.String("url"), "")
clt := v.NewHttp(c.String("token"))
var item schema.Log
if err := clt.GetJSON(url, &item); err != nil {
return err
}
return v.Out(item, c.String("format"), v.Tmpl())
}
func (v Log) Add(c *cli.Context) error {
url := v.Url(c.String("url"), "")
log := &schema.Log{
Level: c.Int("level"),
}
clt := v.NewHttp(c.String("token"))
if err := clt.PostJSON(url, log, nil); err != nil {
return err
}
return nil
}
func (v Log) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "log",
Aliases: []string{"v"},
Usage: "show log information",
Action: v.List,
Subcommands: []*cli.Command{
{
Name: "set",
Usage: "set log level",
Flags: []cli.Flag{
&cli.IntFlag{Name: "level"},
},
Action: v.Add,
},
},
})
}

View File

@@ -1,76 +0,0 @@
package v5
import (
"fmt"
"github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2"
)
type PProf struct {
Cmd
}
func (u PProf) Url(prefix, name string) string {
return prefix + "/api/pprof"
}
func (u PProf) Add(c *cli.Context) error {
pp := schema.PProf{
Listen: c.String("listen"),
}
if pp.Listen == "" {
return libol.NewErr("listen value is empty")
}
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
if err := clt.PostJSON(url, pp, nil); err != nil {
return err
}
return nil
}
func (u PProf) Del(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
if err := clt.DeleteJSON(url, nil, nil); err != nil {
return err
}
return nil
}
func (u PProf) List(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
var pp schema.PProf
if err := clt.GetJSON(url, &pp); err != nil {
return err
}
fmt.Println(pp.Listen)
return nil
}
func (u PProf) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "pprof",
Usage: "Debug pprof tool",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "Show configuration",
Aliases: []string{"ls"},
Action: u.List,
},
{
Name: "enable",
Usage: "Enable pprof tool",
Flags: []cli.Flag{
&cli.StringFlag{Name: "listen", Value: "127.0.0.1:6060"},
},
Action: u.Add,
},
},
})
}

219
cmd/api/v5/system.go Normal file
View File

@@ -0,0 +1,219 @@
package v5
import (
"fmt"
"github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2"
)
type Prefix struct {
Cmd
}
func (r Prefix) Url(prefix string) string {
return prefix + "/api/prefix"
}
func (r Prefix) Tmpl() string {
return `# total {{ len . }}
{{ps -18 "destination"}} {{ps -15 "nexthop"}} {{ps -16 "link"}} {{ps -15 "source"}} {{"metric"}}
{{- range . }}
{{ps -18 .Prefix}} {{ps -15 .NextHop}} {{ps -16 .Link}} {{ps -15 .Source}} {{.Metric}}
{{- end }}
`
}
func (r Prefix) List(c *cli.Context) error {
url := r.Url(c.String("url"))
clt := r.NewHttp(c.String("token"))
var items []schema.PrefixRoute
if err := clt.GetJSON(url, &items); err != nil {
return err
}
return r.Out(items, c.String("format"), r.Tmpl())
}
func (r Prefix) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "prefix",
Usage: "System prefix",
Subcommands: []*cli.Command{
{
Name: "list",
Aliases: []string{"ls"},
Usage: "List system routes.",
Action: r.List,
},
},
})
}
type Log struct {
Cmd
}
func (v Log) Url(prefix, name string) string {
return prefix + "/api/log"
}
func (v Log) Tmpl() string {
return `File : {{ .File }}
Level: {{ .Level}}
`
}
func (v Log) List(c *cli.Context) error {
url := v.Url(c.String("url"), "")
clt := v.NewHttp(c.String("token"))
var item schema.Log
if err := clt.GetJSON(url, &item); err != nil {
return err
}
return v.Out(item, c.String("format"), v.Tmpl())
}
func (v Log) Add(c *cli.Context) error {
url := v.Url(c.String("url"), "")
log := &schema.Log{
Level: c.Int("level"),
}
clt := v.NewHttp(c.String("token"))
if err := clt.PostJSON(url, log, nil); err != nil {
return err
}
return nil
}
func (v Log) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "log",
Aliases: []string{"v"},
Usage: "show log information",
Action: v.List,
Subcommands: []*cli.Command{
{
Name: "set",
Usage: "set log level",
Flags: []cli.Flag{
&cli.IntFlag{Name: "level"},
},
Action: v.Add,
},
},
})
}
type Device struct {
Cmd
}
func (u Device) Url(prefix, name string) string {
if name == "" {
return prefix + "/api/device"
} else {
return prefix + "/api/device/" + name
}
}
func (u Device) Tmpl() string {
return `# total {{ len . }}
{{ps -13 "name"}} {{ps -13 "mtu"}} {{ps -16 "mac"}} {{ps -6 "provider"}}
{{- range . }}
{{ps -13 .Name}} {{pi -13 .Mtu}} {{ps -16 .Mac}} {{ps -6 .Provider}}
{{- end }}
`
}
func (u Device) List(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
var items []schema.Device
if err := clt.GetJSON(url, &items); err != nil {
return err
}
return u.Out(items, c.String("format"), u.Tmpl())
}
func (u Device) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "device",
Usage: "linux network device",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "Display all devices",
Aliases: []string{"ls"},
Action: u.List,
},
},
})
}
type PProf struct {
Cmd
}
func (u PProf) Url(prefix, name string) string {
return prefix + "/api/pprof"
}
func (u PProf) Add(c *cli.Context) error {
pp := schema.PProf{
Listen: c.String("listen"),
}
if pp.Listen == "" {
return libol.NewErr("listen value is empty")
}
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
if err := clt.PostJSON(url, pp, nil); err != nil {
return err
}
return nil
}
func (u PProf) Del(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
if err := clt.DeleteJSON(url, nil, nil); err != nil {
return err
}
return nil
}
func (u PProf) List(c *cli.Context) error {
url := u.Url(c.String("url"), "")
clt := u.NewHttp(c.String("token"))
var pp schema.PProf
if err := clt.GetJSON(url, &pp); err != nil {
return err
}
fmt.Println(pp.Listen)
return nil
}
func (u PProf) Commands(app *api.App) {
app.Command(&cli.Command{
Name: "pprof",
Usage: "Debug pprof tool",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "Show configuration",
Aliases: []string{"ls"},
Action: u.List,
},
{
Name: "enable",
Usage: "Enable pprof tool",
Flags: []cli.Flag{
&cli.StringFlag{Name: "listen", Value: "127.0.0.1:6060"},
},
Action: u.Add,
},
},
})
}

View File

@@ -112,10 +112,10 @@ type Networker interface {
Qoser() Qoser Qoser() Qoser
ACLer() ACLer ACLer() ACLer
FindHoper() FindHoper FindHoper() FindHoper
DoZTrust() EnableZTrust()
UndoZTrust() DisableZTrust()
DoSnat() EnableSnat()
UndoSnat() DisableSnat()
} }
type IPSecer interface { type IPSecer interface {

View File

@@ -1,34 +0,0 @@
package api
import (
"net/http"
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/schema"
)
type Log struct {
}
func (l Log) Router(router *mux.Router) {
router.HandleFunc("/api/log", l.List).Methods("GET")
router.HandleFunc("/api/log", l.Add).Methods("POST")
}
func (l Log) List(w http.ResponseWriter, r *http.Request) {
log := schema.NewLogSchema()
ResponseJson(w, log)
}
func (l Log) Add(w http.ResponseWriter, r *http.Request) {
log := &schema.Log{}
if err := GetData(r, log); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
libol.SetLevel(log.Level)
ResponseMsg(w, 0, "")
}

View File

@@ -138,7 +138,7 @@ func (h SNAT) Post(w http.ResponseWriter, r *http.Request) {
name := vars["id"] name := vars["id"]
if obj := Call.GetWorker(name); obj != nil { if obj := Call.GetWorker(name); obj != nil {
obj.DoSnat() obj.EnableSnat()
} else { } else {
http.Error(w, name+" not found", http.StatusBadRequest) http.Error(w, name+" not found", http.StatusBadRequest)
return return
@@ -152,7 +152,7 @@ func (h SNAT) Delete(w http.ResponseWriter, r *http.Request) {
name := vars["id"] name := vars["id"]
if obj := Call.GetWorker(name); obj != nil { if obj := Call.GetWorker(name); obj != nil {
obj.UndoSnat() obj.DisableSnat()
} else { } else {
http.Error(w, name+" not found", http.StatusBadRequest) http.Error(w, name+" not found", http.StatusBadRequest)
return return

View File

@@ -1,14 +1,43 @@
package api package api
import ( import (
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
) )
type Prefix struct {
}
func (l Prefix) Router(router *mux.Router) {
router.HandleFunc("/api/prefix", l.List).Methods("GET")
}
func (l Prefix) List(w http.ResponseWriter, r *http.Request) {
var items []schema.PrefixRoute
routes, _ := libol.ListRoutes()
for _, prefix := range routes {
item := schema.PrefixRoute{
Link: prefix.Link,
Metric: prefix.Priority,
Table: prefix.Table,
Source: prefix.Src,
NextHop: prefix.Gw,
Prefix: prefix.Dst,
}
items = append(items, item)
}
ResponseJson(w, items)
}
type Device struct { type Device struct {
} }
@@ -89,3 +118,28 @@ func (h Device) Get(w http.ResponseWriter, r *http.Request) {
http.Error(w, vars["id"], http.StatusNotFound) http.Error(w, vars["id"], http.StatusNotFound)
} }
} }
type Log struct {
}
func (l Log) Router(router *mux.Router) {
router.HandleFunc("/api/log", l.List).Methods("GET")
router.HandleFunc("/api/log", l.Add).Methods("POST")
}
func (l Log) List(w http.ResponseWriter, r *http.Request) {
log := schema.NewLogSchema()
ResponseJson(w, log)
}
func (l Log) Add(w http.ResponseWriter, r *http.Request) {
log := &schema.Log{}
if err := GetData(r, log); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
libol.SetLevel(log.Level)
ResponseMsg(w, 0, "")
}

View File

@@ -7,6 +7,7 @@ func Add(router *mux.Router, switcher Switcher) {
User{}.Router(router) User{}.Router(router)
Bgp{}.Router(router) Bgp{}.Router(router)
IPSec{}.Router(router) IPSec{}.Router(router)
Prefix{}.Router(router)
Neighbor{}.Router(router) Neighbor{}.Router(router)
Access{}.Router(router) Access{}.Router(router)
OnLine{}.Router(router) OnLine{}.Router(router)

View File

@@ -55,7 +55,7 @@ func (h ZTrust) Enable(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Network not found", http.StatusBadRequest) http.Error(w, "Network not found", http.StatusBadRequest)
return return
} }
worker.DoZTrust() worker.EnableZTrust()
ResponseJson(w, "success") ResponseJson(w, "success")
} }
@@ -68,7 +68,7 @@ func (h ZTrust) Disable(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Network not found", http.StatusBadRequest) http.Error(w, "Network not found", http.StatusBadRequest)
return return
} }
worker.UndoZTrust() worker.DisableZTrust()
ResponseJson(w, "success") ResponseJson(w, "success")
} }

View File

@@ -1,8 +1,9 @@
package libol package libol
import ( import (
"github.com/vishvananda/netlink"
"net" "net"
"github.com/vishvananda/netlink"
) )
func GetLocalByGw(addr string) (net.IP, error) { func GetLocalByGw(addr string) (net.IP, error) {
@@ -43,3 +44,45 @@ func GetLocalByGw(addr string) (net.IP, error) {
Info("GetLocalByGw: find %s on %s", addr, local) Info("GetLocalByGw: find %s on %s", addr, local)
return local, nil return local, nil
} }
func ListRoutes() ([]Prefix, error) {
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return nil, err
}
var data []Prefix
for _, rte := range routes {
link, err := netlink.LinkByIndex(rte.LinkIndex)
if err != nil {
continue
}
entry := Prefix{
Protocol: rte.Protocol,
Priority: rte.Priority,
Link: link.Attrs().Name,
}
if rte.Dst == nil {
entry.Dst = "0.0.0.0/0"
} else {
entry.Dst = rte.Dst.String()
}
if len(rte.Gw) == 0 {
entry.Gw = ""
} else {
entry.Gw = rte.Gw.String()
}
if len(rte.Src) == 0 {
entry.Src = ""
} else {
entry.Src = rte.Src.String()
}
data = append(data, entry)
}
return data, nil
}

View File

@@ -1,3 +1,4 @@
//go:build !linux
// +build !linux // +build !linux
package libol package libol
@@ -7,3 +8,7 @@ import "net"
func GetLocalByGw(addr string) (net.IP, error) { func GetLocalByGw(addr string) (net.IP, error) {
return nil, NewErr("GetLocalByGw notSupport") return nil, NewErr("GetLocalByGw notSupport")
} }
func ListRoutes() ([]Prefix, error) {
return nil, NewErr("ListRoute notSupport")
}

11
pkg/libol/struct.go Normal file
View File

@@ -0,0 +1,11 @@
package libol
type Prefix struct {
Link string
Dst string
Src string
Gw string
Protocol int
Priority int
Table int
}

View File

@@ -10,9 +10,12 @@ type Lease struct {
type PrefixRoute struct { type PrefixRoute struct {
Prefix string `json:"prefix"` Prefix string `json:"prefix"`
NextHop string `json:"nexthop"` NextHop string `json:"nexthop,omitempty"`
FindHop string `json:"findhop"` FindHop string `json:"findhop,omitempty"`
Metric int `json:"metric"` Metric int `json:"metric"`
Link string `json:"link,omitempty"`
Table int `json:"table,omitempty"`
Source string `json:"source,omitempty"`
MultiPath []MultiPath `json:"multipath,omitempty"` MultiPath []MultiPath `json:"multipath,omitempty"`
} }

View File

@@ -313,7 +313,6 @@ func (w *IPSecWorker) RestartTunnel(data schema.IPSecTunnel) {
func (w *IPSecWorker) ListTunnels(call func(obj schema.IPSecTunnel)) { func (w *IPSecWorker) ListTunnels(call func(obj schema.IPSecTunnel)) {
status := w.Status() status := w.Status()
for _, tun := range w.spec.Tunnels { for _, tun := range w.spec.Tunnels {
state := status[tun.Name+"-c1"]
obj := schema.IPSecTunnel{ obj := schema.IPSecTunnel{
Left: tun.Left, Left: tun.Left,
LeftId: tun.LeftId, LeftId: tun.LeftId,
@@ -323,7 +322,9 @@ func (w *IPSecWorker) ListTunnels(call func(obj schema.IPSecTunnel)) {
RightPort: tun.RightPort, RightPort: tun.RightPort,
Secret: tun.Secret, Secret: tun.Secret,
Transport: tun.Transport, Transport: tun.Transport,
State: state, }
if state, ok := status[tun.Name+"-c1"]; ok {
obj.State = state
} }
call(obj) call(obj)
} }

View File

@@ -386,33 +386,33 @@ func (w *WorkerImpl) SetMss(mss int) {
} }
} }
func (w *WorkerImpl) doSnat() { func (w *WorkerImpl) enableSnat() {
w.fire.Nat.Post.AddRuleX(cn.IPRule{ w.fire.Nat.Post.AddRuleX(cn.IPRule{
Jump: w.snat.Chain().Name, Jump: w.snat.Chain().Name,
Comment: "Goto SNAT", Comment: "Goto SNAT",
}) })
} }
func (w *WorkerImpl) undoSnat() { func (w *WorkerImpl) disableSnat() {
w.fire.Nat.Post.DelRuleX(cn.IPRule{ w.fire.Nat.Post.DelRuleX(cn.IPRule{
Jump: w.snat.Chain().Name, Jump: w.snat.Chain().Name,
Comment: "Goto SNAT", Comment: "Goto SNAT",
}) })
} }
func (w *WorkerImpl) DoSnat() { func (w *WorkerImpl) EnableSnat() {
cfg, _ := w.GetCfgs() cfg, _ := w.GetCfgs()
if cfg.Snat == "disable" { if cfg.Snat == "disable" {
cfg.Snat = "enable" cfg.Snat = "enable"
w.doSnat() w.enableSnat()
} }
} }
func (w *WorkerImpl) UndoSnat() { func (w *WorkerImpl) DisableSnat() {
cfg, _ := w.GetCfgs() cfg, _ := w.GetCfgs()
if cfg.Snat != "disable" { if cfg.Snat != "disable" {
cfg.Snat = "disable" cfg.Snat = "disable"
w.undoSnat() w.disableSnat()
} }
} }
@@ -425,7 +425,7 @@ func (w *WorkerImpl) doTrust() {
}) })
} }
func (w *WorkerImpl) undoTrust() { func (w *WorkerImpl) disableTrust() {
_, vpn := w.GetCfgs() _, vpn := w.GetCfgs()
w.fire.Mangle.Pre.DelRuleX(cn.IPRule{ w.fire.Mangle.Pre.DelRuleX(cn.IPRule{
Input: vpn.Device, Input: vpn.Device,
@@ -434,7 +434,7 @@ func (w *WorkerImpl) undoTrust() {
}) })
} }
func (w *WorkerImpl) DoZTrust() { func (w *WorkerImpl) EnableZTrust() {
cfg, _ := w.GetCfgs() cfg, _ := w.GetCfgs()
if cfg.ZTrust != "enable" { if cfg.ZTrust != "enable" {
cfg.ZTrust = "enable" cfg.ZTrust = "enable"
@@ -442,11 +442,11 @@ func (w *WorkerImpl) DoZTrust() {
} }
} }
func (w *WorkerImpl) UndoZTrust() { func (w *WorkerImpl) DisableZTrust() {
cfg, _ := w.GetCfgs() cfg, _ := w.GetCfgs()
if cfg.ZTrust == "enable" { if cfg.ZTrust == "enable" {
cfg.ZTrust = "disable" cfg.ZTrust = "disable"
w.undoTrust() w.disableTrust()
} }
} }
@@ -515,7 +515,7 @@ func (w *WorkerImpl) Start(v api.Switcher) {
w.fire.Start() w.fire.Start()
w.snat.Install() w.snat.Install()
if cfg.Snat != "disable" { if cfg.Snat != "disable" {
w.doSnat() w.enableSnat()
} }
if cfg.Bridge.Mss > 0 { if cfg.Bridge.Mss > 0 {
// forward to remote // forward to remote
@@ -603,7 +603,7 @@ func (w *WorkerImpl) Stop() {
cfg, _ := w.GetCfgs() cfg, _ := w.GetCfgs()
if cfg.Snat != "disable" { if cfg.Snat != "disable" {
w.undoSnat() w.disableSnat()
} }
w.snat.Cancel() w.snat.Cancel()