diff --git a/cmd/ceci/main.go b/cmd/ceci/main.go index e96ed8f..d96a3c0 100644 --- a/cmd/ceci/main.go +++ b/cmd/ceci/main.go @@ -4,7 +4,6 @@ import ( "flag" "os" - "github.com/luscis/openlan/pkg/access" "github.com/luscis/openlan/pkg/config" "github.com/luscis/openlan/pkg/libol" "github.com/luscis/openlan/pkg/proxy" @@ -13,10 +12,17 @@ import ( func main() { mode := "http" conf := "" + nodate := false + flag.StringVar(&mode, "mode", "http", "Proxy mode for http, socks, tcp and name") flag.StringVar(&conf, "conf", "ceci.yaml", "The configuration file") + flag.BoolVar(&nodate, "nodate", nodate, "Dont display message datetime") flag.Parse() + if nodate { + libol.NoLogDate() + } + if !(mode == "http" || mode == "socks" || mode == "tcp" || mode == "name") { libol.Warn("Ceci: not support mode:%s", mode) os.Exit(1) @@ -24,41 +30,35 @@ func main() { libol.PreNotify() + var x proxy.Proxyer if mode == "name" { - c := &config.Point{ - RequestAddr: true, - Terminal: "off", - Conf: conf, - } + c := &config.NameProxy{Conf: conf} if err := c.Initialize(); err != nil { return } - p := access.NewPoint(c) - p.Initialize() - libol.Go(p.Start) + x = proxy.NewNameProxy(c) } else if mode == "socks" { c := &config.SocksProxy{Conf: conf} if err := c.Initialize(); err != nil { return } - p := proxy.NewSocksProxy(c) - libol.Go(p.Start) + x = proxy.NewSocksProxy(c) } else if mode == "tcp" { c := &config.TcpProxy{Conf: conf} if err := c.Initialize(); err != nil { return } - p := proxy.NewTcpProxy(c) - libol.Go(p.Start) + x = proxy.NewTcpProxy(c) } else { c := &config.HttpProxy{Conf: conf} if err := c.Initialize(); err != nil { return } - p := proxy.NewHttpProxy(c, nil) - libol.Go(p.Start) + x = proxy.NewHttpProxy(c, nil) } + libol.Go(x.Start) libol.SdNotify() libol.Wait() + x.Stop() } diff --git a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.default b/dist/rootfs/etc/openlan/switch/ceci/name.yaml.default index b2c677a..d366b7b 100644 --- a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.default +++ b/dist/rootfs/etc/openlan/switch/ceci/name.yaml.default @@ -1,25 +1,13 @@ -protocol: tcp -bind: 127.0.0.1 -connection: 2.2.2.1 -interface: - address: 192.168.11.33/24 -username: test@vxlan -password: fvph0ldhdvai -crypt: - secret: 477bb0c55edb -forward: - server: 192.168.11.1 - match: - - 8.8.8.8 - - 8.8.4.4 + +listen: 127.0.0.1 nameto: 114.114.114.114 backends: -- server: 192.168.11.10 +- server: 192.168.11.1 nameto: 8.8.8.8 match: - openai.com - chatgpt.com -- server: 192.168.11.11 +- server: 192.168.11.2 nameto: 8.8.8.8 match: - google.com diff --git a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.example b/dist/rootfs/etc/openlan/switch/ceci/name.yaml.example deleted file mode 100755 index 2b9eecb..0000000 --- a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.example +++ /dev/null @@ -1,4 +0,0 @@ -protocol: tcp -connection: who.openlan.net -username: hi@default -password: cb2ff088a34d diff --git a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.full.example b/dist/rootfs/etc/openlan/switch/ceci/name.yaml.full.example deleted file mode 100755 index de3c198..0000000 --- a/dist/rootfs/etc/openlan/switch/ceci/name.yaml.full.example +++ /dev/null @@ -1,14 +0,0 @@ - -interface: - address: 172.32.100.10/24 -connection: who.openlan.net -username: hi@default -password: 1f4ee82b5eb6 -protocol: tls -crypt: - algorithm: aes-256 - secret: 1f4ee82b5eb6 -cert: - insecure: true - - diff --git a/pkg/config/common.go b/pkg/config/common.go index f16d52d..af40ca6 100755 --- a/pkg/config/common.go +++ b/pkg/config/common.go @@ -44,7 +44,7 @@ type Http struct { } func (h *Http) Correct() { - CorrectAddr(&h.Listen, 10000) + SetListen(&h.Listen, 10000) if h.Public == "" { h.Public = VarDir("public") } @@ -59,7 +59,7 @@ func (h *Http) GetUrl() string { return "https://127.0.0.1:" + port } -func CorrectAddr(listen *string, port int) { +func SetListen(listen *string, port int) { if *listen == "" { *listen = fmt.Sprintf("0.0.0.0:%d", port) return diff --git a/pkg/config/point.go b/pkg/config/point.go index a7c3c02..c5b33fe 100755 --- a/pkg/config/point.go +++ b/pkg/config/point.go @@ -95,7 +95,7 @@ func (ap *Point) Correct() { ap.Network = strings.SplitN(ap.Username, "@", 2)[1] } } - CorrectAddr(&ap.Connection, 10002) + SetListen(&ap.Connection, 10002) if runtime.GOOS == "darwin" { ap.Interface.Provider = "tun" } diff --git a/pkg/config/proxy.go b/pkg/config/proxy.go index 4645e28..ba15995 100755 --- a/pkg/config/proxy.go +++ b/pkg/config/proxy.go @@ -234,3 +234,35 @@ func (p *Proxy) Save() { libol.Error("Proxy.Save %s %s", p.Conf, err) } } + +type NameProxy struct { + Conf string `json:"-" yaml:"-"` + Listen string `json:"listen,omitempty"` + Nameto string `json:"nameto,omitempty" yaml:"nameto,omitempty"` + Metric int + Backends ToForwards `json:"backends,omitempty" yaml:"backends,omitempty"` +} + +func (t *NameProxy) Initialize() error { + libol.Info("NameProxy.Initialize %s", t.Conf) + if err := t.Load(); err != nil { + libol.Error("NameProxy.Initialize %s", err) + return err + } + t.Correct() + return nil +} + +func (t *NameProxy) Correct() { + SetListen(&t.Listen, 53) + if t.Metric == 0 { + t.Metric = 300 + } +} + +func (t *NameProxy) Load() error { + if t.Conf == "" || libol.FileExist(t.Conf) != nil { + return libol.NewErr("invalid configure file") + } + return libol.UnmarshalLoad(t, t.Conf) +} diff --git a/pkg/config/switch.go b/pkg/config/switch.go index 862dffe..258d226 100755 --- a/pkg/config/switch.go +++ b/pkg/config/switch.go @@ -114,7 +114,7 @@ func (s *Switch) Correct() { s.Alias = GetAlias() } - CorrectAddr(&s.Listen, 10002) + SetListen(&s.Listen, 10002) if s.Http == nil { s.Http = &Http{} } diff --git a/pkg/libol/iputils.go b/pkg/libol/iputils.go index f709e7e..20d48f1 100755 --- a/pkg/libol/iputils.go +++ b/pkg/libol/iputils.go @@ -122,8 +122,9 @@ func IpRouteAdd(name, prefix, nexthop string, opts ...string) ([]byte, error) { switch runtime.GOOS { case "linux": args := []string{ - "route", "address", prefix, "via", nexthop, + "route", "replace", prefix, "via", nexthop, } + args = append(args, opts...) return exec.Command("ip", args...).CombinedOutput() case "windows": args := []string{ diff --git a/pkg/libol/logger.go b/pkg/libol/logger.go index 8b97b7d..0a7904a 100755 --- a/pkg/libol/logger.go +++ b/pkg/libol/logger.go @@ -243,6 +243,14 @@ func (s *SubLogger) Fatal(format string, v ...interface{}) { s.logger.Write(FATAL, s.Fmt(format), v...) } -func init() { +func LogDate() { log.SetFlags(log.LstdFlags) } + +func NoLogDate() { + log.SetFlags(0) +} + +func init() { + LogDate() +} diff --git a/pkg/proxy/http.go b/pkg/proxy/http.go index 4c2096f..cf03e42 100755 --- a/pkg/proxy/http.go +++ b/pkg/proxy/http.go @@ -3,6 +3,7 @@ package proxy import ( "bufio" "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/base64" @@ -125,26 +126,28 @@ func NewHttpProxy(cfg *co.HttpProxy, px Proxyer) *HttpProxy { requests: make(map[string]*HttpRecord), proxer: px, } - - h.server = &http.Server{ - Addr: cfg.Listen, - Handler: h, - } - user, pass := co.SplitSecret(cfg.Secret) - if user != "" { - h.pass[user] = pass - h.out.Debug("HttpProxy: Auth user %s", user) - } - if cfg.SocksProxy != nil { - h.socks = NewSocksProxy(cfg.SocksProxy) - h.socks.server.SetBackends(h) - } - h.loadUrl() - h.loadPass() - + h.Initialize() return h } +func (t *HttpProxy) Initialize() { + t.server = &http.Server{ + Addr: t.cfg.Listen, + Handler: t, + } + user, pass := co.SplitSecret(t.cfg.Secret) + if user != "" { + t.pass[user] = pass + t.out.Debug("HttpProxy: Auth user %s", user) + } + if t.cfg.SocksProxy != nil { + t.socks = NewSocksProxy(t.cfg.SocksProxy) + t.socks.server.SetBackends(t) + } + t.loadUrl() + t.loadPass() +} + func (t *HttpProxy) loadUrl() { t.api.HandleFunc("/", t.GetIndex).Methods("GET") t.api.HandleFunc("/api", t.GetApi).Methods("GET") @@ -609,14 +612,13 @@ func (t *HttpProxy) AddMatch(w http.ResponseWriter, r *http.Request) { backend := vars["backend"] t.lock.Lock() - defer t.lock.Unlock() - if t.cfg.AddMatch(domain, backend) > -1 { encodeYaml(w, "success") } else { encodeYaml(w, "failed") } - t.save() + t.lock.Unlock() + t.Save() } func (t *HttpProxy) AddUser(w http.ResponseWriter, r *http.Request) { @@ -648,7 +650,10 @@ func (t *HttpProxy) DelUser(w http.ResponseWriter, r *http.Request) { t.savePass() } -func (t *HttpProxy) save() { +func (t *HttpProxy) Save() { + t.lock.Lock() + defer t.lock.Unlock() + if t.proxer == nil { t.cfg.Save() } else { @@ -663,14 +668,14 @@ func (t *HttpProxy) DelMatch(w http.ResponseWriter, r *http.Request) { backend := vars["backend"] t.lock.Lock() - defer t.lock.Unlock() - if t.cfg.DelMatch(domain, backend) > -1 { encodeYaml(w, "success") } else { encodeYaml(w, "failed") } - t.save() + t.lock.Unlock() + + t.Save() } func (t *HttpProxy) GetPac(w http.ResponseWriter, r *http.Request) { @@ -718,3 +723,10 @@ func (t *HttpProxy) GetApi(w http.ResponseWriter, r *http.Request) { }) encodeYaml(w, urls) } + +func (t *HttpProxy) Stop() { + if t.server != nil { + t.server.Shutdown(context.Background()) + t.server = nil + } +} diff --git a/pkg/proxy/name.go b/pkg/proxy/name.go new file mode 100755 index 0000000..17aa711 --- /dev/null +++ b/pkg/proxy/name.go @@ -0,0 +1,146 @@ +package proxy + +import ( + "fmt" + "sync" + "time" + + "github.com/luscis/openlan/pkg/config" + "github.com/luscis/openlan/pkg/libol" + "github.com/miekg/dns" +) + +type NameProxy struct { + listen string + cfg *config.NameProxy + server *dns.Server + out *libol.SubLogger + lock sync.RWMutex + names map[string]string + addrs map[string]string +} + +func NewNameProxy(cfg *config.NameProxy) *NameProxy { + return &NameProxy{ + listen: cfg.Listen, + cfg: cfg, + out: libol.NewSubLogger(cfg.Listen), + names: make(map[string]string), + addrs: make(map[string]string), + } +} + +func (n *NameProxy) Initialize() { +} + +func (n *NameProxy) Forward(name, addr, nexthop string) { + opts := []string{"metric", fmt.Sprintf("%d", n.cfg.Metric)} + if out, err := libol.IpRouteAdd("", addr, nexthop, opts...); err != nil { + n.out.Warn("Access.Forward: %s %s: %s", addr, err, out) + return + } + n.out.Info("NameProxy.Forward: %s <- %s via %s ", nexthop, name, addr) +} + +func (n *NameProxy) UpdateDNS(name, addr string) bool { + n.lock.Lock() + defer n.lock.Unlock() + + updated := false + if _, ok := n.names[name]; !ok { + n.names[name] = addr + updated = true + } + + if _, ok := n.addrs[addr]; !ok { + n.addrs[addr] = name + updated = true + } + + return updated +} + +func (n *NameProxy) FindBackend(r *dns.Msg) *config.ForwardTo { + if len(r.Question) == 0 { + return nil + } + + name := r.Question[0].Name + n.out.Debug("NameProxy.FindBackend %s", name) + + n.lock.RLock() + defer n.lock.RUnlock() + + via := n.cfg.Backends.FindBackend(name) + if via != nil { + n.out.Debug("NameProxy.FindBackend %s via %s", name, via.Server) + } + return via +} + +func (n *NameProxy) handleDNS(conn dns.ResponseWriter, r *dns.Msg) { + client := &dns.Client{ + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + Net: "udp", + } + nameto := n.cfg.Nameto + + libol.Go(func() { + via := n.FindBackend(r) + + if via != nil { + nameto = via.Nameto + } + + config.SetListen(&nameto, 53) + if nameto == "0.0.0.0:53" || nameto == n.listen { + n.out.Error("NameProxy.handleDNS nil(%s)", nameto) + return + } + + n.out.Info("NameProxy.handleDNS %s <- %v via %s", nameto, r.Question, conn.RemoteAddr()) + resp, _, err := client.Exchange(r, nameto) + if err != nil { + n.out.Error("NameProxy.handleDNS %s: %v", r, err) + return + } + + if via != nil && via.Server != "" { + for _, rr := range resp.Answer { + if aa, ok := rr.(*dns.A); ok { + name := aa.Hdr.Name + addr := aa.A.String() + if n.UpdateDNS(name, addr) { + n.Forward(name, addr, via.Server) + } + } + } + } + + if err := conn.WriteMsg(resp); err != nil { + n.out.Error("NameProxy.handleDNS %s", err) + } + }) +} + +func (n *NameProxy) Start() { + dns.HandleFunc(".", n.handleDNS) + n.server = &dns.Server{Addr: n.listen, Net: "udp"} + n.out.Info("NameProxy.StartDNS on %s", n.listen) + + if err := n.server.ListenAndServe(); err != nil { + n.out.Error("NameProxy.StartDNS server: %v", err) + } +} + +func (n *NameProxy) Stop() { + if n.server != nil { + n.server.Shutdown() + n.server = nil + } + n.out.Info("NameProxy.Stop") +} + +func (n *NameProxy) Save() { +} diff --git a/pkg/proxy/socks.go b/pkg/proxy/socks.go index 001e927..b0858f7 100755 --- a/pkg/proxy/socks.go +++ b/pkg/proxy/socks.go @@ -22,7 +22,12 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy { out: libol.NewSubLogger(cfg.Listen), } // Create a SOCKS5 server - user, pass := co.SplitSecret(cfg.Secret) + s.Initialize() + return s +} + +func (s *SocksProxy) Initialize() { + user, pass := co.SplitSecret(s.cfg.Secret) authMethods := make([]socks5.Authenticator, 0, 2) if user != "" { author := socks5.UserPassAuthenticator{ @@ -34,11 +39,11 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy { s.out.Debug("SocksProxy: Auth user %s", user) } conf := &socks5.Config{ - Backends: cfg.Backends, + Backends: s.cfg.Backends, AuthMethods: authMethods, Logger: s.out, } - crt := cfg.Cert + crt := s.cfg.Cert if crt != nil && crt.KeyFile != "" { conf.TlsConfig = &tls.Config{ Certificates: crt.GetCertificates(), @@ -47,10 +52,8 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy { server, err := socks5.New(conf) if err != nil { s.out.Error("NewSocksProxy %s", err) - return nil } s.server = server - return s } func (s *SocksProxy) Start() { @@ -79,3 +82,12 @@ func (s *SocksProxy) Start() { return nil }) } + +func (s *SocksProxy) Stop() { + if s.server != nil { + s.server = nil + } +} + +func (s *SocksProxy) Save() { +} diff --git a/pkg/proxy/tcp.go b/pkg/proxy/tcp.go index 3bd8400..b683d67 100755 --- a/pkg/proxy/tcp.go +++ b/pkg/proxy/tcp.go @@ -25,6 +25,9 @@ func NewTcpProxy(cfg *config.TcpProxy) *TcpProxy { } } +func (t *TcpProxy) Initialize() { +} + func (t *TcpProxy) tunnel(src net.Conn, dst net.Conn) { defer dst.Close() defer src.Close() @@ -106,8 +109,11 @@ func (t *TcpProxy) Start() { func (t *TcpProxy) Stop() { if t.listener != nil { - _ = t.listener.Close() + t.listener.Close() + t.listener = nil } t.out.Info("TcpProxy.Stop") - t.listener = nil +} + +func (t *TcpProxy) Save() { } diff --git a/pkg/schema/point.go b/pkg/schema/point.go index dab12b7..018ce26 100755 --- a/pkg/schema/point.go +++ b/pkg/schema/point.go @@ -8,13 +8,12 @@ type Point struct { Alias string `json:"alias"` Protocol string `json:"protocol"` Remote string `json:"remote"` - Switch string `json:"switch,omitempty"` Device string `json:"device"` - RxBytes uint64 `json:"rxBytes"` - TxBytes uint64 `json:"txBytes"` + RxBytes uint64 `json:"rxbytes"` + TxBytes uint64 `json:"txbytes"` ErrPkt uint64 `json:"errors"` State string `json:"state"` - AliveTime int64 `json:"aliveTime"` + AliveTime int64 `json:"alivetime"` System string `json:"system"` Address string `json:"address"` Names map[string]string `json:"names"`