fea: ceci name proxy support.

This commit is contained in:
Daniel Ding
2025-04-17 11:53:22 +08:00
parent 522733592b
commit 32bd526dec
15 changed files with 276 additions and 90 deletions

View File

@@ -4,7 +4,6 @@ import (
"flag" "flag"
"os" "os"
"github.com/luscis/openlan/pkg/access"
"github.com/luscis/openlan/pkg/config" "github.com/luscis/openlan/pkg/config"
"github.com/luscis/openlan/pkg/libol" "github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/proxy" "github.com/luscis/openlan/pkg/proxy"
@@ -13,10 +12,17 @@ import (
func main() { func main() {
mode := "http" mode := "http"
conf := "" conf := ""
nodate := false
flag.StringVar(&mode, "mode", "http", "Proxy mode for http, socks, tcp and name") flag.StringVar(&mode, "mode", "http", "Proxy mode for http, socks, tcp and name")
flag.StringVar(&conf, "conf", "ceci.yaml", "The configuration file") flag.StringVar(&conf, "conf", "ceci.yaml", "The configuration file")
flag.BoolVar(&nodate, "nodate", nodate, "Dont display message datetime")
flag.Parse() flag.Parse()
if nodate {
libol.NoLogDate()
}
if !(mode == "http" || mode == "socks" || mode == "tcp" || mode == "name") { if !(mode == "http" || mode == "socks" || mode == "tcp" || mode == "name") {
libol.Warn("Ceci: not support mode:%s", mode) libol.Warn("Ceci: not support mode:%s", mode)
os.Exit(1) os.Exit(1)
@@ -24,41 +30,35 @@ func main() {
libol.PreNotify() libol.PreNotify()
var x proxy.Proxyer
if mode == "name" { if mode == "name" {
c := &config.Point{ c := &config.NameProxy{Conf: conf}
RequestAddr: true,
Terminal: "off",
Conf: conf,
}
if err := c.Initialize(); err != nil { if err := c.Initialize(); err != nil {
return return
} }
p := access.NewPoint(c) x = proxy.NewNameProxy(c)
p.Initialize()
libol.Go(p.Start)
} else if mode == "socks" { } else if mode == "socks" {
c := &config.SocksProxy{Conf: conf} c := &config.SocksProxy{Conf: conf}
if err := c.Initialize(); err != nil { if err := c.Initialize(); err != nil {
return return
} }
p := proxy.NewSocksProxy(c) x = proxy.NewSocksProxy(c)
libol.Go(p.Start)
} else if mode == "tcp" { } else if mode == "tcp" {
c := &config.TcpProxy{Conf: conf} c := &config.TcpProxy{Conf: conf}
if err := c.Initialize(); err != nil { if err := c.Initialize(); err != nil {
return return
} }
p := proxy.NewTcpProxy(c) x = proxy.NewTcpProxy(c)
libol.Go(p.Start)
} else { } else {
c := &config.HttpProxy{Conf: conf} c := &config.HttpProxy{Conf: conf}
if err := c.Initialize(); err != nil { if err := c.Initialize(); err != nil {
return return
} }
p := proxy.NewHttpProxy(c, nil) x = proxy.NewHttpProxy(c, nil)
libol.Go(p.Start)
} }
libol.Go(x.Start)
libol.SdNotify() libol.SdNotify()
libol.Wait() libol.Wait()
x.Stop()
} }

View File

@@ -1,25 +1,13 @@
protocol: tcp
bind: 127.0.0.1 listen: 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
nameto: 114.114.114.114 nameto: 114.114.114.114
backends: backends:
- server: 192.168.11.10 - server: 192.168.11.1
nameto: 8.8.8.8 nameto: 8.8.8.8
match: match:
- openai.com - openai.com
- chatgpt.com - chatgpt.com
- server: 192.168.11.11 - server: 192.168.11.2
nameto: 8.8.8.8 nameto: 8.8.8.8
match: match:
- google.com - google.com

View File

@@ -1,4 +0,0 @@
protocol: tcp
connection: who.openlan.net
username: hi@default
password: cb2ff088a34d

View File

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

View File

@@ -44,7 +44,7 @@ type Http struct {
} }
func (h *Http) Correct() { func (h *Http) Correct() {
CorrectAddr(&h.Listen, 10000) SetListen(&h.Listen, 10000)
if h.Public == "" { if h.Public == "" {
h.Public = VarDir("public") h.Public = VarDir("public")
} }
@@ -59,7 +59,7 @@ func (h *Http) GetUrl() string {
return "https://127.0.0.1:" + port return "https://127.0.0.1:" + port
} }
func CorrectAddr(listen *string, port int) { func SetListen(listen *string, port int) {
if *listen == "" { if *listen == "" {
*listen = fmt.Sprintf("0.0.0.0:%d", port) *listen = fmt.Sprintf("0.0.0.0:%d", port)
return return

View File

@@ -95,7 +95,7 @@ func (ap *Point) Correct() {
ap.Network = strings.SplitN(ap.Username, "@", 2)[1] ap.Network = strings.SplitN(ap.Username, "@", 2)[1]
} }
} }
CorrectAddr(&ap.Connection, 10002) SetListen(&ap.Connection, 10002)
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
ap.Interface.Provider = "tun" ap.Interface.Provider = "tun"
} }

View File

@@ -234,3 +234,35 @@ func (p *Proxy) Save() {
libol.Error("Proxy.Save %s %s", p.Conf, err) 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)
}

View File

@@ -114,7 +114,7 @@ func (s *Switch) Correct() {
s.Alias = GetAlias() s.Alias = GetAlias()
} }
CorrectAddr(&s.Listen, 10002) SetListen(&s.Listen, 10002)
if s.Http == nil { if s.Http == nil {
s.Http = &Http{} s.Http = &Http{}
} }

View File

@@ -122,8 +122,9 @@ func IpRouteAdd(name, prefix, nexthop string, opts ...string) ([]byte, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
args := []string{ args := []string{
"route", "address", prefix, "via", nexthop, "route", "replace", prefix, "via", nexthop,
} }
args = append(args, opts...)
return exec.Command("ip", args...).CombinedOutput() return exec.Command("ip", args...).CombinedOutput()
case "windows": case "windows":
args := []string{ args := []string{

View File

@@ -243,6 +243,14 @@ func (s *SubLogger) Fatal(format string, v ...interface{}) {
s.logger.Write(FATAL, s.Fmt(format), v...) s.logger.Write(FATAL, s.Fmt(format), v...)
} }
func init() { func LogDate() {
log.SetFlags(log.LstdFlags) log.SetFlags(log.LstdFlags)
} }
func NoLogDate() {
log.SetFlags(0)
}
func init() {
LogDate()
}

View File

@@ -3,6 +3,7 @@ package proxy
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
@@ -125,26 +126,28 @@ func NewHttpProxy(cfg *co.HttpProxy, px Proxyer) *HttpProxy {
requests: make(map[string]*HttpRecord), requests: make(map[string]*HttpRecord),
proxer: px, proxer: px,
} }
h.Initialize()
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()
return h 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() { func (t *HttpProxy) loadUrl() {
t.api.HandleFunc("/", t.GetIndex).Methods("GET") t.api.HandleFunc("/", t.GetIndex).Methods("GET")
t.api.HandleFunc("/api", t.GetApi).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"] backend := vars["backend"]
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
if t.cfg.AddMatch(domain, backend) > -1 { if t.cfg.AddMatch(domain, backend) > -1 {
encodeYaml(w, "success") encodeYaml(w, "success")
} else { } else {
encodeYaml(w, "failed") encodeYaml(w, "failed")
} }
t.save() t.lock.Unlock()
t.Save()
} }
func (t *HttpProxy) AddUser(w http.ResponseWriter, r *http.Request) { 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() t.savePass()
} }
func (t *HttpProxy) save() { func (t *HttpProxy) Save() {
t.lock.Lock()
defer t.lock.Unlock()
if t.proxer == nil { if t.proxer == nil {
t.cfg.Save() t.cfg.Save()
} else { } else {
@@ -663,14 +668,14 @@ func (t *HttpProxy) DelMatch(w http.ResponseWriter, r *http.Request) {
backend := vars["backend"] backend := vars["backend"]
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock()
if t.cfg.DelMatch(domain, backend) > -1 { if t.cfg.DelMatch(domain, backend) > -1 {
encodeYaml(w, "success") encodeYaml(w, "success")
} else { } else {
encodeYaml(w, "failed") encodeYaml(w, "failed")
} }
t.save() t.lock.Unlock()
t.Save()
} }
func (t *HttpProxy) GetPac(w http.ResponseWriter, r *http.Request) { 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) encodeYaml(w, urls)
} }
func (t *HttpProxy) Stop() {
if t.server != nil {
t.server.Shutdown(context.Background())
t.server = nil
}
}

146
pkg/proxy/name.go Executable file
View File

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

View File

@@ -22,7 +22,12 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy {
out: libol.NewSubLogger(cfg.Listen), out: libol.NewSubLogger(cfg.Listen),
} }
// Create a SOCKS5 server // 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) authMethods := make([]socks5.Authenticator, 0, 2)
if user != "" { if user != "" {
author := socks5.UserPassAuthenticator{ author := socks5.UserPassAuthenticator{
@@ -34,11 +39,11 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy {
s.out.Debug("SocksProxy: Auth user %s", user) s.out.Debug("SocksProxy: Auth user %s", user)
} }
conf := &socks5.Config{ conf := &socks5.Config{
Backends: cfg.Backends, Backends: s.cfg.Backends,
AuthMethods: authMethods, AuthMethods: authMethods,
Logger: s.out, Logger: s.out,
} }
crt := cfg.Cert crt := s.cfg.Cert
if crt != nil && crt.KeyFile != "" { if crt != nil && crt.KeyFile != "" {
conf.TlsConfig = &tls.Config{ conf.TlsConfig = &tls.Config{
Certificates: crt.GetCertificates(), Certificates: crt.GetCertificates(),
@@ -47,10 +52,8 @@ func NewSocksProxy(cfg *config.SocksProxy) *SocksProxy {
server, err := socks5.New(conf) server, err := socks5.New(conf)
if err != nil { if err != nil {
s.out.Error("NewSocksProxy %s", err) s.out.Error("NewSocksProxy %s", err)
return nil
} }
s.server = server s.server = server
return s
} }
func (s *SocksProxy) Start() { func (s *SocksProxy) Start() {
@@ -79,3 +82,12 @@ func (s *SocksProxy) Start() {
return nil return nil
}) })
} }
func (s *SocksProxy) Stop() {
if s.server != nil {
s.server = nil
}
}
func (s *SocksProxy) Save() {
}

View File

@@ -25,6 +25,9 @@ func NewTcpProxy(cfg *config.TcpProxy) *TcpProxy {
} }
} }
func (t *TcpProxy) Initialize() {
}
func (t *TcpProxy) tunnel(src net.Conn, dst net.Conn) { func (t *TcpProxy) tunnel(src net.Conn, dst net.Conn) {
defer dst.Close() defer dst.Close()
defer src.Close() defer src.Close()
@@ -106,8 +109,11 @@ func (t *TcpProxy) Start() {
func (t *TcpProxy) Stop() { func (t *TcpProxy) Stop() {
if t.listener != nil { if t.listener != nil {
_ = t.listener.Close() t.listener.Close()
t.listener = nil
} }
t.out.Info("TcpProxy.Stop") t.out.Info("TcpProxy.Stop")
t.listener = nil }
func (t *TcpProxy) Save() {
} }

View File

@@ -8,13 +8,12 @@ type Point struct {
Alias string `json:"alias"` Alias string `json:"alias"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Remote string `json:"remote"` Remote string `json:"remote"`
Switch string `json:"switch,omitempty"`
Device string `json:"device"` Device string `json:"device"`
RxBytes uint64 `json:"rxBytes"` RxBytes uint64 `json:"rxbytes"`
TxBytes uint64 `json:"txBytes"` TxBytes uint64 `json:"txbytes"`
ErrPkt uint64 `json:"errors"` ErrPkt uint64 `json:"errors"`
State string `json:"state"` State string `json:"state"`
AliveTime int64 `json:"aliveTime"` AliveTime int64 `json:"alivetime"`
System string `json:"system"` System string `json:"system"`
Address string `json:"address"` Address string `json:"address"`
Names map[string]string `json:"names"` Names map[string]string `json:"names"`