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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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) {
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.out.Info("TcpProxy.Stop")
t.listener.Close()
t.listener = nil
}
t.out.Info("TcpProxy.Stop")
}
func (t *TcpProxy) Save() {
}

View File

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