diff --git a/cmd/api/v5/client.go b/cmd/api/v5/client.go index 3a9add4..516e4f5 100755 --- a/cmd/api/v5/client.go +++ b/cmd/api/v5/client.go @@ -131,8 +131,8 @@ func (c Cmd) Tmpl() string { } func (c Cmd) Out(data interface{}, format string, tmpl string) error { - if tmpl == "" && format == "table" { - format = "json" + if tmpl == "" { + format = "yaml" } return api.Out(data, format, tmpl) } diff --git a/cmd/api/v5/network.go b/cmd/api/v5/network.go index 445aef3..05937f4 100755 --- a/cmd/api/v5/network.go +++ b/cmd/api/v5/network.go @@ -119,17 +119,13 @@ func (u Network) Commands(app *api.App) { Name: "list", Usage: "Display all network", Aliases: []string{"ls"}, - Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, - }, - Action: u.List, + Action: u.List, }, { Name: "add", Usage: "Add a network", Flags: []cli.Flag{ &cli.StringFlag{Name: "file"}, - &cli.StringFlag{Name: "name"}, &cli.StringFlag{Name: "provider"}, &cli.StringFlag{Name: "address"}, &cli.StringFlag{Name: "namespace"}, @@ -140,19 +136,13 @@ func (u Network) Commands(app *api.App) { Name: "remove", Usage: "Remove a network", Aliases: []string{"rm"}, - Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, - }, - Action: u.Remove, + Action: u.Remove, }, { Name: "save", Usage: "Save a network", Aliases: []string{"sa"}, - Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, - }, - Action: u.Save, + Action: u.Save, }, Access{}.Commands(), Qos{}.Commands(), diff --git a/cmd/api/v5/openvpn.go b/cmd/api/v5/openvpn.go index 548d6d0..bee6e5b 100755 --- a/cmd/api/v5/openvpn.go +++ b/cmd/api/v5/openvpn.go @@ -36,6 +36,37 @@ func (u VPNClient) List(c *cli.Context) error { return u.Out(items, c.String("format"), u.Tmpl()) } +func (u VPNClient) Add(c *cli.Context) error { + url := u.Url(c.String("url"), c.String("name")) + + value := &schema.VPNClient{ + Name: c.String("user"), + Address: c.String("address"), + } + + clt := u.NewHttp(c.String("token")) + if err := clt.PostJSON(url, value, nil); err != nil { + return err + } + + return nil +} + +func (u VPNClient) Remove(c *cli.Context) error { + url := u.Url(c.String("url"), c.String("name")) + + value := &schema.VPNClient{ + Name: c.String("user"), + } + + clt := u.NewHttp(c.String("token")) + if err := clt.DeleteJSON(url, value, nil); err != nil { + return err + } + + return nil +} + func (u VPNClient) Commands() *cli.Command { return &cli.Command{ Name: "client", @@ -47,6 +78,24 @@ func (u VPNClient) Commands() *cli.Command { Aliases: []string{"ls"}, Action: u.List, }, + { + Name: "add", + Usage: "Add a client", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "user", Required: true}, + &cli.StringFlag{Name: "address", Required: true}, + }, + Action: u.Add, + }, + { + Name: "remove", + Usage: "Remove a client", + Aliases: []string{"rm"}, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "user", Required: true}, + }, + Action: u.Remove, + }, }, } } diff --git a/cmd/ceci/main.go b/cmd/ceci/main.go index d96a3c0..e2938c5 100644 --- a/cmd/ceci/main.go +++ b/cmd/ceci/main.go @@ -31,25 +31,26 @@ func main() { libol.PreNotify() var x proxy.Proxyer - if mode == "name" { + switch mode { + case "name": c := &config.NameProxy{Conf: conf} if err := c.Initialize(); err != nil { return } x = proxy.NewNameProxy(c) - } else if mode == "socks" { + case "socks": c := &config.SocksProxy{Conf: conf} if err := c.Initialize(); err != nil { return } x = proxy.NewSocksProxy(c) - } else if mode == "tcp" { + case "tcp": c := &config.TcpProxy{Conf: conf} if err := c.Initialize(); err != nil { return } x = proxy.NewTcpProxy(c) - } else { + default: c := &config.HttpProxy{Conf: conf} if err := c.Initialize(); err != nil { return diff --git a/dist/rootfs/etc/openlan/access/ac1.yaml b/dist/rootfs/etc/openlan/access/ac1.yaml new file mode 100644 index 0000000..93db3a6 --- /dev/null +++ b/dist/rootfs/etc/openlan/access/ac1.yaml @@ -0,0 +1,4 @@ +connection: +protocol: tcp +username: @ +password: \ No newline at end of file diff --git a/dist/rootfs/etc/openlan/http/https.yaml.example b/dist/rootfs/etc/openlan/http/https.yaml.example index e8904e5..f278e2b 100644 --- a/dist/rootfs/etc/openlan/http/https.yaml.example +++ b/dist/rootfs/etc/openlan/http/https.yaml.example @@ -1,4 +1,3 @@ - listen: 127.0.0.1:1080 socks: listen: 127.0.0.1:1081 diff --git a/dist/rootfs/etc/openlan/name/name.yaml.default b/dist/rootfs/etc/openlan/name/name.yaml.default index 6e3b4dc..25d0192 100644 --- a/dist/rootfs/etc/openlan/name/name.yaml.default +++ b/dist/rootfs/etc/openlan/name/name.yaml.default @@ -1,4 +1,3 @@ - listen: 127.0.0.1 access: - connection: diff --git a/dist/rootfs/etc/openlan/switch/network/nat.json.example b/dist/rootfs/etc/openlan/switch/network/nat.json.example new file mode 100755 index 0000000..95424c0 --- /dev/null +++ b/dist/rootfs/etc/openlan/switch/network/nat.json.example @@ -0,0 +1,22 @@ +{ + "name": "nat", + "provider": "nat", + "specifies": { + "rules": [ + { + "action": "dnat", + "protocol": "tcp", + "ipdest": "192.168.11.1", + "portdest": "8080", + "toipdest": "192.168.11.11:8081", + "toportdest": "192.168.11.11:8081" + }, + { + "action": "snat", + "protocol": "tcp", + "ipsource": "192.168.11.0/24", + "toipsource": "100.100.100.100" + } + ] + } +} \ No newline at end of file diff --git a/dist/rootfs/etc/openlan/tcp/tcp.yaml b/dist/rootfs/etc/openlan/tcp/tcp.yaml new file mode 100644 index 0000000..1a20eed --- /dev/null +++ b/dist/rootfs/etc/openlan/tcp/tcp.yaml @@ -0,0 +1,4 @@ +listen: 192.168.1.100:8081 +target: +- 192.168.1.10:8080 +- 192.168.1.11:8080 \ No newline at end of file diff --git a/pkg/api/api.go b/pkg/api/api.go index dcd3904..ee26ebc 100755 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -64,7 +64,10 @@ type Router interface { } type VPNer interface { - RestartVPN() + StartVPN() + AddVPNClient(name, local string) error + DelVPNClient(name string) error + ListClients(call func(name, local string)) } type Qoser interface { @@ -121,7 +124,7 @@ type Networker interface { type IPSecer interface { AddTunnel(data schema.IPSecTunnel) DelTunnel(data schema.IPSecTunnel) - RestartTunnel(data schema.IPSecTunnel) + StartTunnel(data schema.IPSecTunnel) ListTunnels(call func(obj schema.IPSecTunnel)) } diff --git a/pkg/api/ipsec.go b/pkg/api/ipsec.go index c7988f4..70f5a77 100755 --- a/pkg/api/ipsec.go +++ b/pkg/api/ipsec.go @@ -16,7 +16,7 @@ func (h IPSec) Router(router *mux.Router) { router.HandleFunc("/api/network/ipsec/tunnel", h.Get).Methods("GET") router.HandleFunc("/api/network/ipsec/tunnel", h.Post).Methods("POST") router.HandleFunc("/api/network/ipsec/tunnel", h.Delete).Methods("DELETE") - router.HandleFunc("/api/network/ipsec/tunnel/restart", h.Restart).Methods("PUT") + router.HandleFunc("/api/network/ipsec/tunnel/restart", h.Start).Methods("PUT") } func (h IPSec) Get(w http.ResponseWriter, r *http.Request) { @@ -60,7 +60,7 @@ func (h IPSec) Delete(w http.ResponseWriter, r *http.Request) { ResponseMsg(w, 0, "") } -func (h IPSec) Restart(w http.ResponseWriter, r *http.Request) { +func (h IPSec) Start(w http.ResponseWriter, r *http.Request) { tun := &schema.IPSecTunnel{} if err := GetData(r, tun); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -70,6 +70,6 @@ func (h IPSec) Restart(w http.ResponseWriter, r *http.Request) { http.Error(w, "network is nil", http.StatusBadRequest) return } - Call.secer.RestartTunnel(*tun) + Call.secer.StartTunnel(*tun) ResponseMsg(w, 0, "") } diff --git a/pkg/api/network.go b/pkg/api/network.go index cf720c7..ee9d35b 100755 --- a/pkg/api/network.go +++ b/pkg/api/network.go @@ -119,8 +119,7 @@ func (h Network) RestartVPN(w http.ResponseWriter, r *http.Request) { return } - worker.RestartVPN() - + worker.StartVPN() ResponseJson(w, true) } diff --git a/pkg/api/openvpn.go b/pkg/api/openvpn.go index cc84fbd..9391610 100755 --- a/pkg/api/openvpn.go +++ b/pkg/api/openvpn.go @@ -1,10 +1,11 @@ package api import ( + "net/http" + "github.com/gorilla/mux" "github.com/luscis/openlan/pkg/cache" "github.com/luscis/openlan/pkg/schema" - "net/http" ) type VPNClient struct { @@ -13,13 +14,15 @@ type VPNClient struct { func (h VPNClient) Router(router *mux.Router) { router.HandleFunc("/api/vpn/client", h.List).Methods("GET") router.HandleFunc("/api/vpn/client/{id}", h.List).Methods("GET") + router.HandleFunc("/api/vpn/client/{id}", h.Add).Methods("POST") + router.HandleFunc("/api/vpn/client/{id}", h.Remove).Methods("DELETE") } func (h VPNClient) List(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) name := vars["id"] - clients := make([]schema.VPNClient, 0, 1024) + clients := make(map[string]schema.VPNClient, 1024) if name == "" { for n := range cache.Network.List() { if n == nil { @@ -29,16 +32,85 @@ func (h VPNClient) List(w http.ResponseWriter, r *http.Request) { if client == nil { break } - clients = append(clients, *client) + clients[client.Name] = *client } } } else { + worker := Call.GetWorker(name) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + for client := range cache.VPNClient.List(name) { if client == nil { break } - clients = append(clients, *client) + clients[client.Name] = *client } + + worker.ListClients(func(name, address string) { + if _, ok := clients[name]; ok { + return + } + + clients[name] = schema.VPNClient{ + Name: name, + Address: address, + } + }) } - ResponseJson(w, clients) + + items := make([]schema.VPNClient, 0, 1024) + for _, v := range clients { + items = append(items, v) + } + + ResponseJson(w, items) +} + +func (h VPNClient) Add(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["id"] + + worker := Call.GetWorker(name) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + + value := &schema.VPNClient{} + if err := GetData(r, value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := worker.AddVPNClient(value.Name, value.Address); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + ResponseJson(w, "success") +} + +func (h VPNClient) Remove(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["id"] + + worker := Call.GetWorker(name) + if worker == nil { + http.Error(w, "Network not found", http.StatusBadRequest) + return + } + + value := &schema.VPNClient{} + if err := GetData(r, value); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := worker.DelVPNClient(value.Name); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + ResponseJson(w, "success") } diff --git a/pkg/config/openvpn.go b/pkg/config/openvpn.go index 6c77c82..2d9497d 100755 --- a/pkg/config/openvpn.go +++ b/pkg/config/openvpn.go @@ -1,7 +1,9 @@ package config import ( + "encoding/binary" "fmt" + "net" "strconv" "strings" @@ -9,15 +11,15 @@ import ( ) type OpenVPN struct { - Network string `json:"network" yaml:"network"` - Url string `json:"url,omitempty" yaml:"url,omitempty"` - Directory string `json:"directory,omitempty" yaml:"directory,omitempty"` + Network string `json:"-" yaml:"-"` + Url string `json:"-" yaml:"-"` + Directory string `json:"-" yaml:"-"` Listen string `json:"listen" yaml:"listen"` Protocol string `json:"protocol,omitempty" yaml:"protocol,omitempty"` Subnet string `json:"subnet" yaml:"subnet"` Device string `json:"device" yaml:"device"` Version int `json:"version,omitempty" yaml:"version,omitempty"` - Auth string `json:"auth,omitempty" yaml:"auth,omitempty"` // xauth or cert. + Auth string `json:"-" yaml:"-"` // xauth or cert. DhPem string `json:"dhPem,omitempty" yaml:"dhPem,omitempty"` RootCa string `json:"rootCa,omitempty" yaml:"rootCa,omitempty"` ServerCrt string `json:"cert,omitempty" yaml:"cert,omitempty"` @@ -34,7 +36,33 @@ type OpenVPN struct { type OpenVPNClient struct { Name string `json:"name" yaml:"name"` Address string `json:"address" yaml:"address"` - Netmask string `json:"netmask" yaml:"netmask"` + Netmask string `json:"-" yaml:"-"` +} + +func DecIP4(value string) string { + ip := net.ParseIP(value) + if ip == nil { + return "" + } + ip = ip.To4() + if ip == nil { + return "" + } + + ip4 := binary.BigEndian.Uint32(ip) + ip4-- + + newIP := make([]byte, 4) + binary.BigEndian.PutUint32(newIP, ip4) + + return net.IP(newIP).String() +} + +func (c *OpenVPNClient) Correct(network string) { + c.Netmask = DecIP4(c.Address) + if !strings.Contains(c.Name, "@") { + c.Name = c.Name + "@" + network + } } var defaultVpn = &OpenVPN{ @@ -61,9 +89,7 @@ func (o *OpenVPN) AuthBin(obj *OpenVPN) string { func (o *OpenVPN) Correct(pool, network string) { o.Network = network - if o.Auth == "" { - o.Auth = defaultVpn.Auth - } + o.Auth = defaultVpn.Auth if o.Protocol == "" { o.Protocol = defaultVpn.Protocol } @@ -86,10 +112,7 @@ func (o *OpenVPN) Correct(pool, network string) { o.Cipher = defaultVpn.Cipher } - if o.Script == "" { - o.Script = o.AuthBin(defaultVpn) - } - + o.Script = o.AuthBin(defaultVpn) o.Directory = VarDir("openvpn", o.Network) if !strings.Contains(o.Listen, ":") { o.Listen += ":1194" @@ -107,9 +130,7 @@ func (o *OpenVPN) Correct(pool, network string) { if c.Name == "" || c.Address == "" { continue } - if !strings.Contains(c.Name, "@") { - c.Name = c.Name + "@" + o.Network - } + c.Correct(o.Network) } } @@ -142,3 +163,47 @@ func (o *OpenVPN) DelRedirectDef1() bool { } return find > -1 } + +func (o *OpenVPN) FindClient(name string) (*OpenVPNClient, int) { + for i, obj := range o.Clients { + if name == obj.Name { + return obj, i + } + } + return nil, -1 +} + +func (o *OpenVPN) AddClient(name, address string) bool { + value := &OpenVPNClient{ + Name: name, + Address: address, + } + value.Correct(o.Network) + + _, index := o.FindClient(name) + if index == -1 { + o.Clients = append(o.Clients, value) + } + + return index == -1 +} + +func (o *OpenVPN) DelClient(name string) (*OpenVPNClient, bool) { + value := &OpenVPNClient{ + Name: name, + } + value.Correct(o.Network) + + obj, index := o.FindClient(value.Name) + if index != -1 { + o.Clients = append(o.Clients[:index], o.Clients[index+1:]...) + } + + return obj, index != -1 +} + +func (o *OpenVPN) ListClients(call func(name, address string)) { + for _, obj := range o.Clients { + call(obj.Name, obj.Address) + } +} diff --git a/pkg/proxy/tcp.go b/pkg/proxy/tcp.go index b683d67..b9de81b 100755 --- a/pkg/proxy/tcp.go +++ b/pkg/proxy/tcp.go @@ -104,7 +104,6 @@ func (t *TcpProxy) Start() { } } }) - return } func (t *TcpProxy) Stop() { diff --git a/pkg/schema/openvpn.go b/pkg/schema/openvpn.go index 7c9c702..c8d913a 100755 --- a/pkg/schema/openvpn.go +++ b/pkg/schema/openvpn.go @@ -5,7 +5,6 @@ type VPNClient struct { Name string `json:"name"` UUID string `json:"uuid"` Network string `json:"network"` - User string `json:"user"` Remote string `json:"remote"` Device string `json:"device"` RxBytes uint64 `json:"rxBytes"` diff --git a/pkg/switch/ipsec_linux.go b/pkg/switch/ipsec_linux.go index 74be8aa..7c8fdbb 100644 --- a/pkg/switch/ipsec_linux.go +++ b/pkg/switch/ipsec_linux.go @@ -297,7 +297,7 @@ func (w *IPSecWorker) DelTunnel(data schema.IPSecTunnel) { } } -func (w *IPSecWorker) RestartTunnel(data schema.IPSecTunnel) { +func (w *IPSecWorker) StartTunnel(data schema.IPSecTunnel) { cfg := &co.IPSecTunnel{ Left: data.Left, Right: data.Right, diff --git a/pkg/switch/network_linux.go b/pkg/switch/network_linux.go index a58502e..198693a 100755 --- a/pkg/switch/network_linux.go +++ b/pkg/switch/network_linux.go @@ -450,7 +450,7 @@ func (w *WorkerImpl) DisableZTrust() { } } -func (w *WorkerImpl) letVPN2VRF() { +func (w *WorkerImpl) setVPN2VRF() { _, vpn := w.GetCfgs() promise := libol.NewPromise() promise.Go(func() error { @@ -501,7 +501,7 @@ func (w *WorkerImpl) Start(v api.Switcher) { if !(w.vpn == nil) { w.vpn.Start() if !(w.vrf == nil) { - w.letVPN2VRF() + w.setVPN2VRF() } w.fire.Mangle.In.AddRule(cn.IPRule{ Input: vpn.Device, @@ -574,12 +574,12 @@ func (w *WorkerImpl) unloadRoute(rt co.PrefixRoute) { w.findhop.UnloadHop(rt.FindHop, &nlr) return } - w.out.Debug("WorkerImpl.UnLoadRoute: %s", nlr.String()) + w.out.Debug("WorkerImpl.unloadRoute: %s", nlr.String()) if err := nl.RouteDel(&nlr); err != nil { - w.out.Warn("WorkerImpl.UnLoadRoute: %s", err) + w.out.Warn("WorkerImpl.unloadRoute: %s", err) return } - w.out.Info("WorkerImpl.UnLoadRoute: %v", rt.String()) + w.out.Info("WorkerImpl.unloadRoute: %v", rt.String()) } func (w *WorkerImpl) unloadRoutes() { @@ -589,13 +589,46 @@ func (w *WorkerImpl) unloadRoutes() { } } -func (w *WorkerImpl) RestartVPN() { - if w.vpn != nil { - w.vpn.Restart() - if !(w.vrf == nil) { - w.letVPN2VRF() - } +func (w *WorkerImpl) StartVPN() { + vpn := w.vpn + if vpn == nil { + return } + + vpn.Stop() + vpn.checkAlreadyClose(vpn.Pid(true)) + vpn.Initialize() + vpn.Start() + if !(w.vrf == nil) { + w.setVPN2VRF() + } +} + +func (w *WorkerImpl) AddVPNClient(name, address string) error { + vpn := w.vpn + if vpn == nil { + return libol.NewErr("VPN was disabled") + } + + return vpn.AddClient(name, address) +} + +func (w *WorkerImpl) DelVPNClient(name string) error { + vpn := w.vpn + if vpn == nil { + return libol.NewErr("VPN was disabled") + } + + return vpn.DelClient(name) +} + +func (w *WorkerImpl) ListClients(call func(name, local string)) { + vpn := w.vpn + if vpn == nil { + return + } + + vpn.ListClients(call) } func (w *WorkerImpl) Stop() { diff --git a/pkg/switch/openvpn.go b/pkg/switch/openvpn.go index 5b968e7..4e95753 100755 --- a/pkg/switch/openvpn.go +++ b/pkg/switch/openvpn.go @@ -362,6 +362,7 @@ func (o *OpenVPN) writeClientConfig() error { if err := os.Mkdir(ccd, 0600); err != nil { o.out.Info("OpenVPN.writeClientConfig %s", err) } + o.cleanClientConfig() for _, fic := range o.Cfg.Clients { if fic.Name == "" || fic.Address == "" { continue @@ -372,12 +373,42 @@ func (o *OpenVPN) writeClientConfig() error { o.out.Warn("OpenVPN.writeClientConfig %s", err) } } - return nil } +func (o *OpenVPN) cleanClientConfig() { + ccd := o.DirectoryClientConfig() + files, err := filepath.Glob(path.Join(ccd, "*")) + if err != nil { + libol.Warn("OpenVPN.cleanClientConfig %v", err) + } + for _, file := range files { + if err := os.Remove(file); err != nil { + o.out.Warn("OpenVPN.cleanClientConfig %s", err) + } + } +} + +func (o *OpenVPN) AddClient(name, address string) error { + if o.Cfg.AddClient(name, address) { + o.writeClientConfig() + } + return nil +} + +func (o *OpenVPN) DelClient(name string) error { + if _, ok := o.Cfg.DelClient(name); ok { + o.writeClientConfig() + } + return nil +} + +func (o *OpenVPN) ListClients(call func(name, address string)) { + o.Cfg.ListClients(call) +} + func createExecutableFile(path string) (*os.File, error) { - return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700) } func (o *OpenVPN) writeClientStatusScripts(data *OpenVPNData) error { @@ -404,7 +435,7 @@ func (o *OpenVPN) writeClientStatusScripts(data *OpenVPNData) error { clientDisConnectFile := filepath.Join(cid, "client-disconnect.sh") fp2, err := createExecutableFile(clientDisConnectFile) - if err != nil || fp == nil { + if err != nil || fp2 == nil { return err } defer fp2.Close() @@ -421,18 +452,7 @@ func (o *OpenVPN) writeClientStatusScripts(data *OpenVPNData) error { } func (o *OpenVPN) Clean() { - ccd := o.DirectoryClientConfig() - for _, fic := range o.Cfg.Clients { - if fic.Name == "" || fic.Address == "" { - continue - } - file := filepath.Join(ccd, fic.Name) - if err := libol.FileExist(file); err == nil { - if err := os.Remove(file); err != nil { - o.out.Warn("OpenVPN.Clean %s", err) - } - } - } + o.cleanClientConfig() files := []string{o.FileStats(true), o.FileIpp(true), o.FileClient(true)} for _, file := range files { if err := libol.FileExist(file); err == nil { @@ -526,13 +546,6 @@ func (o *OpenVPN) Stop() { o.Clean() } -func (o *OpenVPN) Restart() { - o.Stop() - o.checkAlreadyClose(o.Pid(true)) - o.Initialize() - o.Start() -} - func (o *OpenVPN) checkAlreadyClose(pid string) { timeout := 10 * time.Second