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)
Ceci{}.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
ACLer() ACLer
FindHoper() FindHoper
DoZTrust()
UndoZTrust()
DoSnat()
UndoSnat()
EnableZTrust()
DisableZTrust()
EnableSnat()
DisableSnat()
}
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"]
if obj := Call.GetWorker(name); obj != nil {
obj.DoSnat()
obj.EnableSnat()
} else {
http.Error(w, name+" not found", http.StatusBadRequest)
return
@@ -152,7 +152,7 @@ func (h SNAT) Delete(w http.ResponseWriter, r *http.Request) {
name := vars["id"]
if obj := Call.GetWorker(name); obj != nil {
obj.UndoSnat()
obj.DisableSnat()
} else {
http.Error(w, name+" not found", http.StatusBadRequest)
return

View File

@@ -1,14 +1,43 @@
package api
import (
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
"net"
"net/http"
"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 {
}
@@ -89,3 +118,28 @@ func (h Device) Get(w http.ResponseWriter, r *http.Request) {
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)
Bgp{}.Router(router)
IPSec{}.Router(router)
Prefix{}.Router(router)
Neighbor{}.Router(router)
Access{}.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)
return
}
worker.DoZTrust()
worker.EnableZTrust()
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)
return
}
worker.UndoZTrust()
worker.DisableZTrust()
ResponseJson(w, "success")
}

View File

@@ -1,8 +1,9 @@
package libol
import (
"github.com/vishvananda/netlink"
"net"
"github.com/vishvananda/netlink"
)
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)
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
package libol
@@ -7,3 +8,7 @@ import "net"
func GetLocalByGw(addr string) (net.IP, error) {
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 {
Prefix string `json:"prefix"`
NextHop string `json:"nexthop"`
FindHop string `json:"findhop"`
NextHop string `json:"nexthop,omitempty"`
FindHop string `json:"findhop,omitempty"`
Metric int `json:"metric"`
Link string `json:"link,omitempty"`
Table int `json:"table,omitempty"`
Source string `json:"source,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)) {
status := w.Status()
for _, tun := range w.spec.Tunnels {
state := status[tun.Name+"-c1"]
obj := schema.IPSecTunnel{
Left: tun.Left,
LeftId: tun.LeftId,
@@ -323,7 +322,9 @@ func (w *IPSecWorker) ListTunnels(call func(obj schema.IPSecTunnel)) {
RightPort: tun.RightPort,
Secret: tun.Secret,
Transport: tun.Transport,
State: state,
}
if state, ok := status[tun.Name+"-c1"]; ok {
obj.State = state
}
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{
Jump: w.snat.Chain().Name,
Comment: "Goto SNAT",
})
}
func (w *WorkerImpl) undoSnat() {
func (w *WorkerImpl) disableSnat() {
w.fire.Nat.Post.DelRuleX(cn.IPRule{
Jump: w.snat.Chain().Name,
Comment: "Goto SNAT",
})
}
func (w *WorkerImpl) DoSnat() {
func (w *WorkerImpl) EnableSnat() {
cfg, _ := w.GetCfgs()
if cfg.Snat == "disable" {
cfg.Snat = "enable"
w.doSnat()
w.enableSnat()
}
}
func (w *WorkerImpl) UndoSnat() {
func (w *WorkerImpl) DisableSnat() {
cfg, _ := w.GetCfgs()
if 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()
w.fire.Mangle.Pre.DelRuleX(cn.IPRule{
Input: vpn.Device,
@@ -434,7 +434,7 @@ func (w *WorkerImpl) undoTrust() {
})
}
func (w *WorkerImpl) DoZTrust() {
func (w *WorkerImpl) EnableZTrust() {
cfg, _ := w.GetCfgs()
if 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()
if cfg.ZTrust == "enable" {
cfg.ZTrust = "disable"
w.undoTrust()
w.disableTrust()
}
}
@@ -515,7 +515,7 @@ func (w *WorkerImpl) Start(v api.Switcher) {
w.fire.Start()
w.snat.Install()
if cfg.Snat != "disable" {
w.doSnat()
w.enableSnat()
}
if cfg.Bridge.Mss > 0 {
// forward to remote
@@ -603,7 +603,7 @@ func (w *WorkerImpl) Stop() {
cfg, _ := w.GetCfgs()
if cfg.Snat != "disable" {
w.undoSnat()
w.disableSnat()
}
w.snat.Cancel()