fea: support add client for openvpn.
Some checks failed
Coverage CI / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Ubuntu CI / build (push) Has been cancelled

This commit is contained in:
Daniel Ding
2025-10-21 15:03:23 +08:00
parent 25277b573d
commit 755e721bfd
19 changed files with 335 additions and 84 deletions

View File

@@ -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)
}

View File

@@ -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(),

View File

@@ -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,
},
},
}
}

View File

@@ -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

View File

@@ -0,0 +1,4 @@
connection: <remote-server>
protocol: tcp
username: <username>@<network>
password: <password>

View File

@@ -1,4 +1,3 @@
listen: 127.0.0.1:1080
socks:
listen: 127.0.0.1:1081

View File

@@ -1,4 +1,3 @@
listen: 127.0.0.1
access:
- connection: <remote-server>

View File

@@ -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"
}
]
}
}

4
dist/rootfs/etc/openlan/tcp/tcp.yaml vendored Normal file
View File

@@ -0,0 +1,4 @@
listen: 192.168.1.100:8081
target:
- 192.168.1.10:8080
- 192.168.1.11:8080

View File

@@ -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))
}

View File

@@ -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, "")
}

View File

@@ -119,8 +119,7 @@ func (h Network) RestartVPN(w http.ResponseWriter, r *http.Request) {
return
}
worker.RestartVPN()
worker.StartVPN()
ResponseJson(w, true)
}

View File

@@ -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")
}

View File

@@ -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)
}
}

View File

@@ -104,7 +104,6 @@ func (t *TcpProxy) Start() {
}
}
})
return
}
func (t *TcpProxy) Stop() {

View File

@@ -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"`

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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