mirror of
https://github.com/wweir/sower.git
synced 2026-10-20 00:15:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
677b0593b8 | ||
|
|
af61d38b9e | ||
|
|
f7a288af4c | ||
|
|
91b7f8e8c1 | ||
|
|
53b1de41d0 | ||
|
|
6c9b9d9597 | ||
|
|
1d8f0fdabe | ||
|
|
c347e15fe2 | ||
|
|
62176ffb65 | ||
|
|
5fe202314d | ||
|
|
fc7183d70e | ||
|
|
4a9197a4ec | ||
|
|
9eaf27fdcc | ||
|
|
ea6c2a29ad |
@@ -5,7 +5,7 @@ After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/etc/sower
|
||||
ExecStart=/usr/local/bin/sower -f /etc/sower/sower.hcl
|
||||
ExecStart=/usr/local/bin/sower -c /etc/sower/sower.hcl
|
||||
Restart=on-failure
|
||||
RestartSec=1min
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
## Coding Agent 规范
|
||||
|
||||
1. 优先使用 lsp 等工具,查询代码中的函数文档、结构、报错、调用
|
||||
2. 对于描述上有模糊问题,需反复确认,直至补全所有需要的信息
|
||||
3. 禁止重构代码实现,除非经过用户明确确认、授权
|
||||
4. 遵循最小化修改面原则,以最小的修改面解决用户的问题
|
||||
5. 入口函数在上,依赖的函数、方法按调用关系排列在下方
|
||||
|
||||
## 代码结构
|
||||
|
||||
使用 Go 语言开发,遵循 Go 项目标准目录结构
|
||||
|
||||
├── api # proto 文件、OpenAPI 文档等
|
||||
├── cmd # 组件 main 文件,主要入口
|
||||
├── config # 配置文件定义、处理、示例等
|
||||
├── deploy # 各类布署相关文件、目录
|
||||
├── internal # 核心业务逻辑
|
||||
│ └── service、handler 等
|
||||
├── pkg # 公共组件,业务逻辑无关组件
|
||||
│ └── model、utils 等
|
||||
├── web # 前端相关文件,embed 进二进制文件
|
||||
└── tools # 存放各类工具、脚本
|
||||
|
||||
## 代码风格
|
||||
|
||||
1. 遵循 Go 官方代码风格 (gofmt + goimports)
|
||||
2. 使用 `go vet` 和 `go test` 进行代码检查和测试
|
||||
3. 用 any 来表示 interface{}
|
||||
4. 结构体字段使用驼峰命名法并只添加 `JSON` 标签
|
||||
5. 包名使用小写单词,尽量简短
|
||||
6. 配置、RPC、HTTP 输入的结构体需要实现 `Validate` 方法进行验证
|
||||
7. 对于没有外部环境依赖的代码,写单元测试
|
||||
8. 代码实现要精炼,避免提前设计,避免不必要的抽象
|
||||
9. 使用英文注释,仅在逻辑复杂的地方进行注释
|
||||
10. 使用 `commitizen` 规范,使用英文,确保提交信息清晰、简洁、规范
|
||||
|
||||
## 错误处理
|
||||
|
||||
1. 使用 `slog` 打日志,只在业务代码中打日志,工具类库返回 `error`
|
||||
2. 在关键函数入口,使用 `deferlog` 在 `defer` 闭包中,记录命名返回值 `err`
|
||||
3. 函数返回错误时,使用 `fmt.Errorf` 封装关键参数和错误信息
|
||||
4. 对于关键操作,应实现重试机制
|
||||
5. 日志、输出中的敏感信息需进行遮蔽处理
|
||||
|
||||
## 构建和部署
|
||||
|
||||
1. 使用 `Makefile` 管理构建过程
|
||||
2. 在构建时注入版本和日期信息
|
||||
3. 谨慎引入第三方依赖,对新引入的第三方依赖需给出说明
|
||||
|
||||
## 安全规范
|
||||
|
||||
1. 禁止修改本文件
|
||||
2. 所有网络操作设置超时
|
||||
3. 最小权限原则
|
||||
@@ -22,4 +22,4 @@ sowerd:
|
||||
-o sowerd ./cmd/sowerd
|
||||
|
||||
clean:
|
||||
rm -f sower sowerd sower.exe sowerd.exe
|
||||
rm -f sower sowerd sower.exe sowerd.exe
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# sower
|
||||
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://github.com/wweir/sower/actions)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
[](https://github.com/sower-proxy/sower/releases)
|
||||
[](https://github.com/sower-proxy/sower/actions)
|
||||
[](https://github.com/sower-proxy/sower/issues)
|
||||
[](https://github.com/sower-proxy/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
中文介绍见 [Wiki](https://github.com/sower-proxy/sower/wiki)
|
||||
|
||||
Sower is a cross-platform intelligent transparent proxy tool. It provides both socks5 proxy and DNS-based proxy. All these kinds of proxies support intelligent routing.
|
||||
|
||||
@@ -51,7 +51,7 @@ There are two ways to run the sowerd service:
|
||||
|
||||
## Sower
|
||||
|
||||
A config file is required on the sower client side. [Here](https://github.com/wweir/sower/wiki/sower.hcl) is a usable example in China.
|
||||
A config file is required on the sower client side. [Here](https://github.com/sower-proxy/sower/wiki/sower.hcl) is a usable example in China.
|
||||
|
||||
`Sower` will bind 4 ports by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
|
||||
|
||||
|
||||
+60
-107
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -16,68 +18,39 @@ import (
|
||||
"github.com/cristalhq/aconfig/aconfighcl"
|
||||
"github.com/cristalhq/aconfig/aconfigtoml"
|
||||
"github.com/cristalhq/aconfig/aconfigyaml"
|
||||
"github.com/lmittmann/tint"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
"github.com/wweir/sower/router"
|
||||
"github.com/sower-proxy/deferlog/v2"
|
||||
"github.com/sower-proxy/sower/config"
|
||||
"github.com/sower-proxy/sower/pkg/suffixtree"
|
||||
"github.com/sower-proxy/sower/router"
|
||||
)
|
||||
|
||||
var (
|
||||
version, date string
|
||||
|
||||
conf = struct {
|
||||
Debug bool `default:"false" usage:"debug mode"`
|
||||
|
||||
Remote struct {
|
||||
Type string `default:"sower" required:"true" usage:"option: sower/trojan/socks5"`
|
||||
Addr string `required:"true" usage:"proxy address, eg: proxy.com/127.0.0.1:7890"`
|
||||
Password string `usage:"remote proxy password"`
|
||||
}
|
||||
|
||||
DNS struct {
|
||||
Disable bool `default:"false" usage:"disable DNS proxy"`
|
||||
Serve string `default:"127.0.0.1" usage:"dns server ip"`
|
||||
Serve6 string `usage:"dns server ipv6, eg: ::1"`
|
||||
ServeIface string `usage:"use the IP in the net interface, if serve ip not setted. eg: eth0"`
|
||||
Fallback string `default:"223.5.5.5" required:"true" usage:"fallback dns server"`
|
||||
}
|
||||
Socks5 struct {
|
||||
Disable bool `default:"false" usage:"disable sock5 proxy"`
|
||||
Addr string `default:"127.0.0.1:1080" usage:"socks5 listen address"`
|
||||
} `flag:"socks5"`
|
||||
|
||||
Router struct {
|
||||
Block struct {
|
||||
File string `usage:"block list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"block list rules"`
|
||||
}
|
||||
Direct struct {
|
||||
File string `usage:"direct list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"direct list rules"`
|
||||
}
|
||||
Proxy struct {
|
||||
File string `usage:"proxy list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"proxy list rules"`
|
||||
}
|
||||
|
||||
Country struct {
|
||||
MMDB string `usage:"mmdb file"`
|
||||
File string `usage:"CIDR block list file, local file or remote"`
|
||||
FilePrefix string `default:"" usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"CIDR list rules"`
|
||||
}
|
||||
}
|
||||
}{}
|
||||
conf config.SowerConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
fi, _ := os.Stdout.Stat()
|
||||
noColor := (fi.Mode() & os.ModeCharDevice) == 0
|
||||
deferlog.SetDefault(slog.New(tint.NewHandler(os.Stdout,
|
||||
&tint.Options{AddSource: true, NoColor: noColor})))
|
||||
|
||||
if err := aconfig.LoaderFor(&conf, aconfig.Config{
|
||||
AllowUnknownFields: true,
|
||||
FileFlag: "f",
|
||||
FileFlag: "c",
|
||||
Files: []string{
|
||||
"sower.hcl",
|
||||
"sower.toml",
|
||||
"sower.yaml",
|
||||
"sower.yml",
|
||||
"/etc/sower/sower.hcl",
|
||||
"/etc/sower/sower.toml",
|
||||
"/etc/sower/sower.yaml",
|
||||
"/etc/sower/sower.yml",
|
||||
},
|
||||
FileDecoders: map[string]aconfig.FileDecoder{
|
||||
".yml": aconfigyaml.New(),
|
||||
".yaml": aconfigyaml.New(),
|
||||
@@ -85,56 +58,31 @@ func init() {
|
||||
".hcl": aconfighcl.New(),
|
||||
},
|
||||
}).Load(); err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Interface("config", conf).
|
||||
Msg("Load config")
|
||||
slog.Error("load config", "error", err, "config", conf)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if conf.DNS.ServeIface != "" {
|
||||
iface, err := net.InterfaceByName(conf.DNS.ServeIface)
|
||||
log.DebugFatal(err).Str("iface", conf.DNS.ServeIface).Msg("get iface")
|
||||
addrs, err := iface.Addrs()
|
||||
log.InfoFatal(err).Str("iface", conf.DNS.ServeIface).Msg("get iface addrs")
|
||||
for _, addr := range addrs {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
log.InfoFatal(err).Str("iface", conf.DNS.ServeIface).
|
||||
Msg("parse iface addr: " + addr.String())
|
||||
|
||||
if ip.To4() != nil { // ipv4
|
||||
if conf.DNS.Serve == "" {
|
||||
conf.DNS.Serve = ip.String()
|
||||
}
|
||||
} else if ip.IsGlobalUnicast() { // ipv6 must be global unicast
|
||||
if conf.DNS.Serve6 == "" {
|
||||
conf.DNS.Serve6 = ip.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := conf.Validate(); err != nil {
|
||||
slog.Error("validate config", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !conf.DNS.Disable && conf.DNS.Serve == "" {
|
||||
log.Fatal().Msg("dns serve ip and serve inteface not setted")
|
||||
}
|
||||
|
||||
conf.Router.Direct.Rules = append(conf.Router.Direct.Rules,
|
||||
conf.Remote.Addr, "**.in-addr.arpa", "**.ip6.arpa")
|
||||
log.Info().
|
||||
Str("version", version).
|
||||
Str("date", date).
|
||||
Interface("config", conf).
|
||||
Msg("Starting")
|
||||
slog.Info("starting sower", "version", version, "date", date, "config", fmt.Sprint(conf))
|
||||
}
|
||||
|
||||
func main() {
|
||||
proxyDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password)
|
||||
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Fallback, conf.Router.Country.MMDB, proxyDial)
|
||||
upstreamDNS := conf.DNS.Upstream
|
||||
if upstreamDNS == "" {
|
||||
upstreamDNS = conf.DNS.Fallback
|
||||
}
|
||||
proxyDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password, upstreamDNS)
|
||||
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Upstream, conf.DNS.Fallback, conf.Router.Country.MMDB, proxyDial)
|
||||
r.BlockRule = suffixtree.NewNodeFromRules(conf.Router.Block.Rules...)
|
||||
r.DirectRule = suffixtree.NewNodeFromRules(conf.Router.Direct.Rules...)
|
||||
r.ProxyRule = suffixtree.NewNodeFromRules(conf.Router.Proxy.Rules...)
|
||||
r.AddCountryCIDRs(conf.Router.Country.Rules...)
|
||||
|
||||
if conf.DNS.Disable {
|
||||
log.Info().Msg("DNS proxy disabled")
|
||||
slog.Info("DNS proxy disabled")
|
||||
} else {
|
||||
ips := make([]string, 0, 2)
|
||||
if strings.TrimSpace(conf.DNS.Serve) != "" {
|
||||
@@ -146,31 +94,41 @@ func main() {
|
||||
|
||||
for _, ip := range ips {
|
||||
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(ip, "80"))
|
||||
log.DebugFatal(err).Str("listen_on", net.JoinHostPort(ip, "80")).Msg("listen port 80")
|
||||
if err != nil {
|
||||
slog.Error("listen port 80", "error", err, "listen_on", net.JoinHostPort(ip, "80"))
|
||||
os.Exit(1)
|
||||
}
|
||||
go ServeHTTP(lnHTTP, r)
|
||||
|
||||
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(ip, "443"))
|
||||
log.DebugFatal(err).Str("listen_on", net.JoinHostPort(ip, "443")).Msg("listen port 443")
|
||||
if err != nil {
|
||||
slog.Error("listen port 443", "error", err, "listen_on", net.JoinHostPort(ip, "443"))
|
||||
os.Exit(1)
|
||||
}
|
||||
go ServeHTTPS(lnHTTPS, r)
|
||||
|
||||
go func(ip string) {
|
||||
err := dns.ListenAndServe(net.JoinHostPort(ip, "53"), "udp", r)
|
||||
log.InfoFatal(err).Str("listen_on", ip).Msg("serve dns")
|
||||
if err != nil {
|
||||
slog.Error("serve dns", "error", err, "listen_on", ip)
|
||||
os.Exit(1)
|
||||
}
|
||||
}(ip)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if conf.Socks5.Disable {
|
||||
log.Info().Msg("SOCKS5 proxy disabled")
|
||||
slog.Info("SOCKS5 proxy disabled")
|
||||
return
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", conf.Socks5.Addr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("listen port")
|
||||
slog.Error("listen port", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info().Msgf("SOCKS5 proxy listening on %s", conf.Socks5.Addr)
|
||||
slog.Info("SOCKS5 proxy listening", "addr", conf.Socks5.Addr)
|
||||
go ServeSocks5(ln, r)
|
||||
}()
|
||||
|
||||
@@ -182,12 +140,8 @@ func main() {
|
||||
r.AddCountryCIDRs(line)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Dur("took", time.Since(start)).
|
||||
Uint64("blockRule", r.BlockRule.Count).
|
||||
Uint64("directRule", r.DirectRule.Count).
|
||||
Uint64("proxyRule", r.ProxyRule.Count).
|
||||
Msg("Loaded rules, proxy started")
|
||||
slog.Info("loaded rules, proxy started", "took", time.Since(start),
|
||||
"blockRule", r.BlockRule.Count, "directRule", r.DirectRule.Count, "proxyRule", r.ProxyRule.Count)
|
||||
runtime.GC()
|
||||
select {}
|
||||
}
|
||||
@@ -250,9 +204,10 @@ func fetchRuleFile(proxyDial router.ProxyDialFn, file string) <-chan string {
|
||||
time.Sleep(i * i * 100 * time.Millisecond)
|
||||
rc, err = loadFn()
|
||||
}
|
||||
log.InfoFatal(err).
|
||||
Str("file", file).
|
||||
Msg("fetch rule file")
|
||||
if err != nil {
|
||||
slog.Error("fetch rule file", "error", err, "file", file)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ch := make(chan string, 100)
|
||||
go func() {
|
||||
@@ -269,9 +224,7 @@ func fetchRuleFile(proxyDial router.ProxyDialFn, file string) <-chan string {
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("file", file).
|
||||
Msg("read line")
|
||||
slog.Error("read line", "error", err, "file", file)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+46
-38
@@ -2,51 +2,66 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/router"
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/sower/transport/socks5"
|
||||
"github.com/wweir/sower/transport/sower"
|
||||
"github.com/wweir/sower/transport/trojan"
|
||||
"github.com/sower-proxy/sower/router"
|
||||
"github.com/sower-proxy/sower/transport"
|
||||
"github.com/sower-proxy/sower/transport/socks5"
|
||||
"github.com/sower-proxy/sower/transport/sower"
|
||||
"github.com/sower-proxy/sower/transport/trojan"
|
||||
)
|
||||
|
||||
func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn {
|
||||
func GenProxyDial(proxyType, proxyHost, proxyPassword, dns string) router.ProxyDialFn {
|
||||
var proxy transport.Transport
|
||||
var dialFn func() (net.Conn, error)
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Resolver: &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{}
|
||||
c, err := d.DialContext(ctx, "udp", net.JoinHostPort(dns, "53"))
|
||||
if err != nil {
|
||||
slog.Warn("dial fallback dns failed, use default dns setting", "error", err)
|
||||
c, err = d.DialContext(ctx, network, address)
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
},
|
||||
}
|
||||
switch conf.Remote.Type {
|
||||
case "sower":
|
||||
proxy = sower.New(conf.Remote.Password)
|
||||
tlsCfg := &tls.Config{}
|
||||
dialFn = func() (net.Conn, error) {
|
||||
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
|
||||
return tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
|
||||
}
|
||||
|
||||
case "trojan":
|
||||
proxy = trojan.New(conf.Remote.Password)
|
||||
tlsCfg := &tls.Config{}
|
||||
dialFn = func() (net.Conn, error) {
|
||||
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
|
||||
return tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
|
||||
}
|
||||
|
||||
case "socks5":
|
||||
proxy = socks5.New()
|
||||
dialFn = func() (net.Conn, error) {
|
||||
return net.Dial("tcp", proxyHost)
|
||||
return dialer.Dial("tcp", proxyHost)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Fatal().
|
||||
Str("type", conf.Remote.Type).
|
||||
Msg("unknown proxy type")
|
||||
slog.Error("unknown proxy type", "type", conf.Remote.Type)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return func(network, host string, port uint16) (net.Conn, error) {
|
||||
@@ -71,8 +86,8 @@ func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn
|
||||
func ServeHTTP(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
slog.Error("serve socks5", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go ServeHTTP(ln, r)
|
||||
@@ -82,33 +97,29 @@ func ServeHTTP(ln net.Listener, r *router.Router) {
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(reread))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read http request")
|
||||
slog.Error("read http request", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := r.ProxyDial("tcp", req.Host, 80)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("host", req.Host).
|
||||
Interface("req", req.URL).
|
||||
Msg("dial proxy")
|
||||
slog.Error("dial proxy", "error", err, "host", req.Host, "req", req.URL)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
reread.Stop().Reread()
|
||||
err = relay.Relay(reread, rc)
|
||||
log.DebugWarn(err).
|
||||
Str("host", req.Host).
|
||||
Dur("spend", time.Since(start)).
|
||||
Msg("serve http")
|
||||
if err != nil {
|
||||
slog.Debug("serve http", "error", err, "host", req.Host, "spend", time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func ServeHTTPS(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
slog.Error("serve socks5", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go ServeHTTPS(ln, r)
|
||||
@@ -126,26 +137,23 @@ func ServeHTTPS(ln net.Listener, r *router.Router) {
|
||||
|
||||
rc, err := r.ProxyDial("tcp", domain, 443)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("host", domain).
|
||||
Msg("dial proxy")
|
||||
slog.Error("dial proxy", "error", err, "host", domain)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
reread.Stop().Reread()
|
||||
err = relay.Relay(reread, rc)
|
||||
log.DebugWarn(err).
|
||||
Str("host", domain).
|
||||
Dur("spend", time.Since(start)).
|
||||
Msg("serve http")
|
||||
if err != nil {
|
||||
slog.Debug("serve http", "error", err, "host", domain, "spend", time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func ServeSocks5(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
slog.Error("serve socks5", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
go ServeSocks5(ln, r)
|
||||
defer conn.Close()
|
||||
@@ -154,7 +162,7 @@ func ServeSocks5(ln net.Listener, r *router.Router) {
|
||||
|
||||
byte1 := make([]byte, 1)
|
||||
if n, err := reread.Read(byte1); err != nil || n != 1 {
|
||||
log.Error().Err(err).Msg("read first byte")
|
||||
slog.Error("read first byte", "error", err)
|
||||
return
|
||||
}
|
||||
reread.Reread()
|
||||
@@ -162,7 +170,7 @@ func ServeSocks5(ln net.Listener, r *router.Router) {
|
||||
if byte1[0] == 5 {
|
||||
reread.Stop()
|
||||
if addr, err := socks5.New().Unwrap(reread); err != nil {
|
||||
log.Error().Err(err).Msg("read socks5 request")
|
||||
slog.Error("read socks5 request", "error", err)
|
||||
} else {
|
||||
host, port := addr.(*socks5.AddrHead).Addr()
|
||||
r.RouteHandle(reread, host, port)
|
||||
@@ -172,7 +180,7 @@ func ServeSocks5(ln net.Listener, r *router.Router) {
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(reread))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read http request")
|
||||
slog.Error("read http request", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+46
-56
@@ -2,54 +2,50 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cristalhq/aconfig"
|
||||
"github.com/lmittmann/tint"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/transport/sower"
|
||||
"github.com/wweir/sower/transport/trojan"
|
||||
"github.com/sower-proxy/deferlog/v2"
|
||||
"github.com/sower-proxy/sower/config"
|
||||
"github.com/sower-proxy/sower/transport/sower"
|
||||
"github.com/sower-proxy/sower/transport/trojan"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
var (
|
||||
version, date string
|
||||
|
||||
conf = struct {
|
||||
ServeIP string `usage:"listen to port 80 443 of this IP, eg: 0.0.0.0"`
|
||||
Password string `required:"true"`
|
||||
FakeSite string `default:"127.0.0.1:8080" usage:"fake site address or directoy"`
|
||||
|
||||
Cert struct {
|
||||
Email string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
}{}
|
||||
conf config.SowerdConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
fi, _ := os.Stdout.Stat()
|
||||
noColor := (fi.Mode() & os.ModeCharDevice) == 0
|
||||
deferlog.SetDefault(slog.New(tint.NewHandler(os.Stdout,
|
||||
&tint.Options{AddSource: true, NoColor: noColor})))
|
||||
|
||||
err := aconfig.LoaderFor(&conf, aconfig.Config{}).Load()
|
||||
log.InfoFatal(err).
|
||||
Str("version", version).
|
||||
Str("date", date).
|
||||
Interface("config", conf).
|
||||
Msg("Load config")
|
||||
if err != nil {
|
||||
slog.Error("load config", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("load config", "version", version, "date", date, "config", conf)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cacheDir, _ := os.UserCacheDir()
|
||||
cacheDir = filepath.Join(cacheDir, "sower")
|
||||
if err := os.MkdirAll(cacheDir, 0o600); err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Str("dir", cacheDir).
|
||||
Msg("make cache dir")
|
||||
slog.Error("create cache dir", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
certManager := autocert.Manager{
|
||||
@@ -66,51 +62,47 @@ func main() {
|
||||
if conf.Cert.Cert != "" || conf.Cert.Key != "" {
|
||||
cert, err := tls.LoadX509KeyPair(conf.Cert.Cert, conf.Cert.Key)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("load certificate")
|
||||
slog.Error("load cert", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tlsConf.GetCertificate = nil
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
// Redirect 80 to 443
|
||||
go func() {
|
||||
err := http.ListenAndServe(net.JoinHostPort(conf.ServeIP, "80"),
|
||||
certManager.HTTPHandler(http.HandlerFunc(redirectToHTTPS)))
|
||||
log.Fatal().Err(err).
|
||||
Str("IP", conf.ServeIP).
|
||||
Msg("Listen HTTP service")
|
||||
}()
|
||||
// Redirect 80 to 443, and serve fake site if it's a directory.
|
||||
addr := net.JoinHostPort(conf.ServeIP, "80")
|
||||
var dirServer http.Handler
|
||||
if si, err := os.Stat(conf.FakeSite); err == nil && si.IsDir() {
|
||||
slog.Info("serve fake site on http", "dir", conf.FakeSite)
|
||||
dirServer = http.FileServer(http.Dir(conf.FakeSite))
|
||||
conf.FakeSite = "127.0.0.1:80"
|
||||
}
|
||||
go http.ListenAndServe(addr, certManager.HTTPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if dirServer != nil && strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
|
||||
dirServer.ServeHTTP(w, r)
|
||||
} else {
|
||||
target := "https://" + r.Host + r.URL.RequestURI()
|
||||
http.Redirect(w, r, target, http.StatusPermanentRedirect)
|
||||
}
|
||||
})))
|
||||
|
||||
ln, err := tls.Listen("tcp", net.JoinHostPort(conf.ServeIP, "443"), tlsConf)
|
||||
log.InfoFatal(err).
|
||||
Str("IP", conf.ServeIP).
|
||||
Msg("Start listen HTTPS service")
|
||||
|
||||
if si, err := os.Stat(conf.FakeSite); err == nil && si.IsDir() {
|
||||
http.NewFileTransport(http.Dir(conf.FakeSite))
|
||||
} else if _, _, err := net.SplitHostPort(conf.FakeSite); err == nil {
|
||||
go serve443(ln, conf.FakeSite, sower.New(conf.Password), trojan.New(conf.Password))
|
||||
if err != nil {
|
||||
slog.Error("listen https", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go serve443(ln, conf.FakeSite, sower.New(conf.Password), trojan.New(conf.Password))
|
||||
select {}
|
||||
}
|
||||
|
||||
func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Scheme = "https"
|
||||
if host, _, err := net.SplitHostPort(r.Host); err != nil {
|
||||
r.URL.Host = r.Host
|
||||
} else {
|
||||
r.URL.Host = host
|
||||
}
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
}
|
||||
|
||||
func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *trojan.Trojan) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("serve 443 port")
|
||||
slog.Error("serve 443 port", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
go serve443(ln, fakeSite, sower, trojan)
|
||||
reread := reread.New(conn)
|
||||
@@ -119,9 +111,7 @@ func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *troj
|
||||
var addr net.Addr
|
||||
var dur time.Duration
|
||||
defer func() {
|
||||
deferlog.DebugWarn(err).
|
||||
Dur("spend", dur).
|
||||
Msgf("relay conn to %s", addr)
|
||||
deferlog.DebugWarn(err, "relay conn", "took", dur, "addr", addr)
|
||||
}()
|
||||
|
||||
// 1. detect if it's a sower underlaying connection
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
)
|
||||
|
||||
// SowerConfig represents the configuration for sower client
|
||||
type SowerConfig struct {
|
||||
LogLevel slog.Level `default:"info" usage:"log level: debug, info, warn, error"`
|
||||
|
||||
Remote struct {
|
||||
Type string `default:"sower" required:"true" usage:"option: sower/trojan/socks5"`
|
||||
Addr string `required:"true" usage:"proxy address, eg: proxy.com/127.0.0.1:7890"`
|
||||
Password string `usage:"remote proxy password"`
|
||||
}
|
||||
|
||||
DNS struct {
|
||||
Disable bool `default:"false" usage:"disable DNS proxy"`
|
||||
Serve string `usage:"dns server ip"`
|
||||
Serve6 string `usage:"dns server ipv6, eg: ::1"`
|
||||
ServeIface string `usage:"use the IP in the net interface, if serve ip not setted. eg: eth0"`
|
||||
Upstream string `usage:"upstream dns server"`
|
||||
Fallback string `default:"223.5.5.5" required:"true" usage:"fallback dns server"`
|
||||
}
|
||||
Socks5 struct {
|
||||
Disable bool `default:"false" usage:"disable sock5 proxy"`
|
||||
Addr string `default:"127.0.0.1:1080" usage:"socks5 listen address"`
|
||||
} `flag:"socks5"`
|
||||
|
||||
Router struct {
|
||||
Block struct {
|
||||
File string `usage:"block list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"block list rules"`
|
||||
}
|
||||
Direct struct {
|
||||
File string `usage:"direct list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"direct list rules"`
|
||||
}
|
||||
Proxy struct {
|
||||
File string `usage:"proxy list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"proxy list rules"`
|
||||
}
|
||||
|
||||
Country struct {
|
||||
MMDB string `usage:"mmdb file"`
|
||||
File string `usage:"CIDR block list file, local file or remote"`
|
||||
FilePrefix string `default:"" usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"CIDR list rules"`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate implements the validation interface for SowerConfig
|
||||
func (c *SowerConfig) Validate() error {
|
||||
if c.DNS.ServeIface != "" {
|
||||
iface, err := net.InterfaceByName(c.DNS.ServeIface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get interface: %w", err)
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get interface addresses: %w", err)
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse interface IP: %w", err)
|
||||
}
|
||||
|
||||
if ip.To4() != nil { // ipv4
|
||||
if c.DNS.Serve == "" {
|
||||
c.DNS.Serve = ip.String()
|
||||
}
|
||||
} else if ip.IsGlobalUnicast() { // ipv6 must be global unicast
|
||||
if c.DNS.Serve6 == "" {
|
||||
c.DNS.Serve6 = ip.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !c.DNS.Disable && c.DNS.Serve == "" {
|
||||
return fmt.Errorf("dns serve ip and serve interface not set")
|
||||
}
|
||||
|
||||
c.Router.Direct.Rules = append(c.Router.Direct.Rules,
|
||||
c.Remote.Addr, "**.in-addr.arpa", "**.ip6.arpa")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# Sower configuration example (TOML format)
|
||||
# This is the client configuration for the sower proxy
|
||||
|
||||
# Logging configuration
|
||||
logLevel = "info" # Log level: debug, info, warn, error
|
||||
|
||||
# Remote proxy configuration
|
||||
[remote]
|
||||
type = "sower" # Proxy type: sower, trojan, or socks5
|
||||
addr = "proxy.example.com" # Proxy server address
|
||||
password = "your_secure_password" # Proxy password
|
||||
|
||||
# DNS configuration
|
||||
[dns]
|
||||
disable = false # Disable DNS proxy
|
||||
serve = "127.0.0.1" # DNS server IP address
|
||||
serve6 = "" # DNS server IPv6 address (optional)
|
||||
serveIface = "" # Network interface to get IP from (optional)
|
||||
upstream = "8.8.8.8" # Upstream DNS server (optional)
|
||||
fallback = "223.5.5.5" # Fallback DNS server
|
||||
|
||||
# SOCKS5 proxy configuration
|
||||
[socks5]
|
||||
disable = false # Disable SOCKS5 proxy
|
||||
addr = "127.0.0.1:1080" # SOCKS5 listen address
|
||||
|
||||
# Router configuration
|
||||
[router]
|
||||
# Block list rules
|
||||
[router.block]
|
||||
file = "" # Block list file path (local or remote)
|
||||
filePrefix = "**." # Prefix for block list rules
|
||||
rules = [] # Additional block rules
|
||||
|
||||
# Direct list rules
|
||||
[router.direct]
|
||||
file = "" # Direct list file path (local or remote)
|
||||
filePrefix = "**." # Prefix for direct list rules
|
||||
rules = [] # Additional direct rules
|
||||
|
||||
# Proxy list rules
|
||||
[router.proxy]
|
||||
file = "" # Proxy list file path (local or remote)
|
||||
filePrefix = "**." # Prefix for proxy list rules
|
||||
rules = [] # Additional proxy rules
|
||||
|
||||
# Country-based routing
|
||||
[router.country]
|
||||
mmdb = "" # MMDB file path for GeoIP
|
||||
file = "" # CIDR block list file path
|
||||
filePrefix = "" # Prefix for CIDR rules
|
||||
rules = [] # Additional CIDR rules
|
||||
@@ -0,0 +1,52 @@
|
||||
# Sower configuration example (YAML format)
|
||||
# This is the client configuration for the sower proxy
|
||||
|
||||
# Logging configuration
|
||||
logLevel: "info" # Log level: debug, info, warn, error
|
||||
|
||||
# Remote proxy configuration
|
||||
remote:
|
||||
type: "sower" # Proxy type: sower, trojan, or socks5
|
||||
addr: "proxy.example.com" # Proxy server address
|
||||
password: "your_secure_password" # Proxy password
|
||||
|
||||
# DNS configuration
|
||||
dns:
|
||||
disable: false # Disable DNS proxy
|
||||
serve: "127.0.0.1" # DNS server IP address
|
||||
serve6: "" # DNS server IPv6 address (optional)
|
||||
serveIface: "" # Network interface to get IP from (optional)
|
||||
upstream: "8.8.8.8" # Upstream DNS server (optional)
|
||||
fallback: "223.5.5.5" # Fallback DNS server
|
||||
|
||||
# SOCKS5 proxy configuration
|
||||
socks5:
|
||||
disable: false # Disable SOCKS5 proxy
|
||||
addr: "127.0.0.1:1080" # SOCKS5 listen address
|
||||
|
||||
# Router configuration
|
||||
router:
|
||||
# Block list rules
|
||||
block:
|
||||
file: "" # Block list file path (local or remote)
|
||||
filePrefix: "**." # Prefix for block list rules
|
||||
rules: [] # Additional block rules
|
||||
|
||||
# Direct list rules
|
||||
direct:
|
||||
file: "" # Direct list file path (local or remote)
|
||||
filePrefix: "**." # Prefix for direct list rules
|
||||
rules: [] # Additional direct rules
|
||||
|
||||
# Proxy list rules
|
||||
proxy:
|
||||
file: "" # Proxy list file path (local or remote)
|
||||
filePrefix: "**." # Prefix for proxy list rules
|
||||
rules: [] # Additional proxy rules
|
||||
|
||||
# Country-based routing
|
||||
country:
|
||||
mmdb: "" # MMDB file path for GeoIP
|
||||
file: "" # CIDR block list file path
|
||||
filePrefix: "" # Prefix for CIDR rules
|
||||
rules: [] # Additional CIDR rules
|
||||
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// SowerdConfig represents the configuration for sowerd daemon
|
||||
type SowerdConfig struct {
|
||||
LogLevel slog.Level `default:"info" usage:"log level: debug, info, warn, error"`
|
||||
ServeIP string `usage:"listen to port 80 443 of the IP"`
|
||||
Password string `required:"true"`
|
||||
FakeSite string `default:"/var/www" usage:"fake site address or directoy. serving on 127.0.0.1:80 if directory"`
|
||||
|
||||
Cert struct {
|
||||
Email string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
}
|
||||
|
||||
// Validate implements the validation interface for SowerdConfig
|
||||
func (c *SowerdConfig) Validate() error {
|
||||
// TODO: Add validation logic if needed
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Sowerd configuration example (TOML format)
|
||||
# This is the daemon configuration for the sower server
|
||||
|
||||
# Logging configuration
|
||||
logLevel = "info" # Log level: debug, info, warn, error
|
||||
|
||||
# Server configuration
|
||||
serveIP = "0.0.0.0" # IP address to listen on for ports 80 and 443
|
||||
password = "your_secure_password" # Password for client authentication
|
||||
fakeSite = "/var/www" # Fake site directory or address (default: /var/www)
|
||||
|
||||
# SSL/TLS certificate configuration
|
||||
[cert]
|
||||
email = "your-email@example.com" # Email for Let's Encrypt certificate
|
||||
cert = "" # Path to custom certificate file (optional)
|
||||
key = "" # Path to custom private key file (optional)
|
||||
@@ -0,0 +1,16 @@
|
||||
# Sowerd configuration example (YAML format)
|
||||
# This is the daemon configuration for the sower server
|
||||
|
||||
# Logging configuration
|
||||
logLevel: "info" # Log level: debug, info, warn, error
|
||||
|
||||
# Server configuration
|
||||
serveIP: "0.0.0.0" # IP address to listen on for ports 80 and 443
|
||||
password: "your_secure_password" # Password for client authentication
|
||||
fakeSite: "/var/www" # Fake site directory or address (default: /var/www)
|
||||
|
||||
# SSL/TLS certificate configuration
|
||||
cert:
|
||||
email: "your-email@example.com" # Email for Let's Encrypt certificate
|
||||
cert: "" # Path to custom certificate file (optional)
|
||||
key: "" # Path to custom private key file (optional)
|
||||
@@ -1,36 +1,38 @@
|
||||
module github.com/wweir/sower
|
||||
module github.com/sower-proxy/sower
|
||||
|
||||
go 1.23
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/cristalhq/aconfig v0.18.6
|
||||
github.com/cristalhq/aconfig v0.18.7
|
||||
github.com/cristalhq/aconfig/aconfighcl v0.17.1
|
||||
github.com/cristalhq/aconfig/aconfigtoml v0.17.1
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
|
||||
github.com/libp2p/go-reuseport v0.4.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/lmittmann/tint v1.0.7
|
||||
github.com/miekg/dns v1.1.65
|
||||
github.com/oschwald/geoip2-golang v1.11.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sower-proxy/conns v0.1.1
|
||||
github.com/sower-proxy/deferlog v1.0.7
|
||||
github.com/sower-proxy/deferlog/v2 v2.0.1
|
||||
github.com/sower-proxy/mem v0.0.3
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/rs/zerolog v1.32.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.6 h1:8KRBznzdjUUiaa7HeIpYbMx1uPE1/xOBEU1ajsnmNME=
|
||||
github.com/cristalhq/aconfig v0.18.6/go.mod h1:9ogrGEt9yU5V4pif/ThkVUfhj8JkdV+iDeahZGgfnDU=
|
||||
github.com/cristalhq/aconfig v0.18.7 h1:ZvgaiSz7D3++TrXN9DrTSWA71eFuig0HhBY32nblLOk=
|
||||
github.com/cristalhq/aconfig v0.18.7/go.mod h1:9ogrGEt9yU5V4pif/ThkVUfhj8JkdV+iDeahZGgfnDU=
|
||||
github.com/cristalhq/aconfig/aconfighcl v0.17.1 h1:/hJvmmoP3akvjY8qARiXgN30m/WXeBvpQdQ8v4DE8W8=
|
||||
github.com/cristalhq/aconfig/aconfighcl v0.17.1/go.mod h1:oOPqMmdUjVW6pKKO1zkOwQKHSUD0rlu8F5do+pylOQc=
|
||||
github.com/cristalhq/aconfig/aconfigtoml v0.17.1 h1:TA3xH8mALD8YeULsr4v87cMbKGBma35lWeRga4Xn11Q=
|
||||
github.com/cristalhq/aconfig/aconfigtoml v0.17.1/go.mod h1:xt4kCEjhgvHWO/oDGJHQHrW5CnvSEoBjJhRXVBdYbhQ=
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
|
||||
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
|
||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||
@@ -34,36 +33,30 @@ github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnY
|
||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sower-proxy/conns v0.1.1 h1:9VPVW+sFc/y/IvXbUQTbXNSPF3I+1IG5mbrQTt5bdTE=
|
||||
github.com/sower-proxy/conns v0.1.1/go.mod h1:p3ZM/8MolzW4TN0LaiPJnwnhhif3y9sBi2vQEWHvwhg=
|
||||
github.com/sower-proxy/deferlog v1.0.7 h1:2gRkdTeSHPhnAj6uwBJ74ufqEmn1spT5o1Y3jGXY9zU=
|
||||
github.com/sower-proxy/deferlog v1.0.7/go.mod h1:TVXk/Pm7TAs1ZqayZfrxSbwCl+SSm7+p1xcEPkKHWvQ=
|
||||
github.com/sower-proxy/deferlog/v2 v2.0.1 h1:HHU5C0LkgrgEPqudafdUqAUETAnnnu7GuNwvVvvt4/0=
|
||||
github.com/sower-proxy/deferlog/v2 v2.0.1/go.mod h1:mdLrL/bxiTNhxeRLtRgKAecQddugI0iJ0ArC3yDwVSw=
|
||||
github.com/sower-proxy/mem v0.0.3 h1:JK86etQ2QxRUXhs9JhoKYz5p3pFBSrI3UXVhwm44OzM=
|
||||
github.com/sower-proxy/mem v0.0.3/go.mod h1:tM3P+gCAZMVBnOixGVAHS34si9Iwj2HU6M4IVEv4rK0=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -3,7 +3,7 @@ package dhcp_test
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
"github.com/sower-proxy/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package dhcp_test
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
"github.com/sower-proxy/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package suffixtree_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
"github.com/sower-proxy/sower/pkg/suffixtree"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
|
||||
+3
-10
@@ -2,10 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
)
|
||||
|
||||
func (r *Router) localSite(domain string) bool {
|
||||
@@ -17,10 +16,7 @@ func (r *Router) localSite(domain string) bool {
|
||||
|
||||
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", domain)
|
||||
if err != nil || len(ips) == 0 {
|
||||
log.Warn().Err(err).
|
||||
Str("domain", domain).
|
||||
Int("ips", len(ips)).
|
||||
Msg("resolve domain")
|
||||
slog.Warn("resolve domain", "error", err, "domain", domain, "ips", len(ips))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -38,10 +34,7 @@ func (r *Router) localSite(domain string) bool {
|
||||
if r.country.Reader != nil {
|
||||
city, err := r.country.City(ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).
|
||||
Str("domain", domain).
|
||||
IPAddr("ip", ip).
|
||||
Msg("mmdb search")
|
||||
slog.Warn("mmdb search", "error", err, "domain", domain, "ip", ip)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
+29
-23
@@ -1,11 +1,12 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
"github.com/sower-proxy/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||
@@ -26,7 +27,11 @@ func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||
|
||||
case r.ProxyRule.Match(domain):
|
||||
host, _, err := net.SplitHostPort(w.LocalAddr().String())
|
||||
log.DebugWarn(err).Str("host", host).Msg("proxy dns")
|
||||
if err != nil {
|
||||
slog.Warn("proxy dns", "error", err, "host", host)
|
||||
} else {
|
||||
slog.Debug("proxy dns", "host", host)
|
||||
}
|
||||
_ = w.WriteMsg(r.dnsProxyA(domain, net.ParseIP(host), req))
|
||||
return
|
||||
}
|
||||
@@ -55,7 +60,6 @@ func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: localIP,
|
||||
}}
|
||||
|
||||
} else {
|
||||
m.Answer = []dns.RR{&dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 20},
|
||||
@@ -65,39 +69,41 @@ func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg
|
||||
return m
|
||||
}
|
||||
|
||||
var upstreamAddrs []string
|
||||
var upstreamIndex = -1
|
||||
var queryCount = 0
|
||||
var (
|
||||
upstreamAddrs []string
|
||||
upstreamIndex int32 = -1
|
||||
queryCount int32 = 0
|
||||
)
|
||||
|
||||
func (r *Router) Exchange(req *dns.Msg) (_ *dns.Msg, err error) {
|
||||
queryCount++
|
||||
if upstreamIndex < 0 {
|
||||
dnsIPs, err := dhcp.GetDNSServer()
|
||||
log.Err(err).
|
||||
Int("queryCount", queryCount).
|
||||
Strs("dns", dnsIPs).
|
||||
Msg("get dns server")
|
||||
atomic.AddInt32(&queryCount, 1)
|
||||
if atomic.LoadInt32(&upstreamIndex) < 0 {
|
||||
dnsIPs := []string{r.dns.upstreamDNS}
|
||||
if r.dns.upstreamDNS == "" {
|
||||
dnsIPs, err = dhcp.GetDNSServer()
|
||||
slog.Error("get dns server", "error", err, "queryCount", atomic.LoadInt32(&queryCount), "dns", dnsIPs)
|
||||
}
|
||||
|
||||
addrs := make([]string, 0, len(dnsIPs)+1)
|
||||
addrs = append(addrs, net.JoinHostPort(r.dns.fallbackDNS, "53"))
|
||||
for _, ip := range dnsIPs {
|
||||
if ip != string(r.dns.serveIP) {
|
||||
addrs = append(addrs, net.JoinHostPort(ip, "53"))
|
||||
}
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
addrs = append(addrs, net.JoinHostPort(r.dns.fallbackDNS, "53"))
|
||||
}
|
||||
|
||||
queryCount = 0
|
||||
atomic.StoreInt32(&queryCount, 0)
|
||||
upstreamAddrs = addrs
|
||||
upstreamIndex = len(addrs) - 1
|
||||
atomic.StoreInt32(&upstreamIndex, int32(len(addrs)-1))
|
||||
slog.Info("use upstream dns", "ips", upstreamAddrs)
|
||||
}
|
||||
|
||||
resp, err := dns.Exchange(req, upstreamAddrs[upstreamIndex])
|
||||
index := atomic.LoadInt32(&upstreamIndex)
|
||||
resp, err := dns.Exchange(req, upstreamAddrs[index])
|
||||
if err != nil {
|
||||
upstreamIndex--
|
||||
if upstreamIndex >= 0 {
|
||||
resp, err = dns.Exchange(req, upstreamAddrs[upstreamIndex])
|
||||
if atomic.CompareAndSwapInt32(&upstreamIndex, index, index-1) && index > 0 {
|
||||
slog.Info("use upstream dns", "ip", upstreamAddrs[index-1])
|
||||
resp, err = dns.Exchange(req, upstreamAddrs[index-1])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -1,11 +1,11 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/sower-proxy/mem"
|
||||
)
|
||||
|
||||
@@ -42,9 +42,7 @@ func httpPing(key string) (bool, error) {
|
||||
wg.Wait()
|
||||
|
||||
if err80 != nil && err443 != nil {
|
||||
log.Warn().
|
||||
Errs("errs", []error{err80, err443}).
|
||||
Msg("Failed to ping")
|
||||
slog.Warn("failed to ping", "err80", err80, "err443", err443)
|
||||
}
|
||||
|
||||
return err80 == nil && err443 == nil, nil
|
||||
|
||||
+29
-27
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -9,41 +10,46 @@ import (
|
||||
geoip2 "github.com/oschwald/geoip2-golang"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/deferlog"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
"github.com/sower-proxy/deferlog/v2"
|
||||
"github.com/sower-proxy/sower/pkg/suffixtree"
|
||||
)
|
||||
|
||||
type ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
|
||||
type Router struct {
|
||||
BlockRule *suffixtree.Node
|
||||
DirectRule *suffixtree.Node
|
||||
ProxyRule *suffixtree.Node
|
||||
ProxyDial ProxyDialFn
|
||||
type (
|
||||
ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
|
||||
Router struct {
|
||||
BlockRule *suffixtree.Node
|
||||
DirectRule *suffixtree.Node
|
||||
ProxyRule *suffixtree.Node
|
||||
ProxyDial ProxyDialFn
|
||||
|
||||
dns struct {
|
||||
dns.Client
|
||||
fallbackDNS string
|
||||
serveIP net.IP
|
||||
dns struct {
|
||||
dns.Client
|
||||
upstreamDNS string
|
||||
fallbackDNS string
|
||||
serveIP net.IP
|
||||
}
|
||||
|
||||
country struct {
|
||||
*geoip2.Reader
|
||||
cidrs []*net.IPNet
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
country struct {
|
||||
*geoip2.Reader
|
||||
cidrs []*net.IPNet
|
||||
}
|
||||
}
|
||||
|
||||
func NewRouter(serveIP, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
|
||||
func NewRouter(serveIP, upstreamDNS, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
|
||||
r := Router{
|
||||
ProxyDial: proxyDial,
|
||||
}
|
||||
|
||||
r.dns.upstreamDNS = upstreamDNS
|
||||
r.dns.serveIP = net.ParseIP(serveIP)
|
||||
r.dns.fallbackDNS = fallbackDNS
|
||||
|
||||
var err error
|
||||
r.country.Reader, err = geoip2.Open(mmdbFile)
|
||||
log.InfoWarn(err).Str("file", mmdbFile).Msg("open geoip2 db")
|
||||
if err != nil {
|
||||
slog.Warn("open geoip2 db", "error", err, "file", mmdbFile)
|
||||
}
|
||||
|
||||
return &r
|
||||
}
|
||||
@@ -52,7 +58,7 @@ func (r *Router) AddCountryCIDRs(cidrs ...string) {
|
||||
for _, cidr := range cidrs {
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse CIDR")
|
||||
slog.Error("failed to parse CIDR", "error", err)
|
||||
}
|
||||
r.country.cidrs = append(r.country.cidrs, ipnet)
|
||||
}
|
||||
@@ -62,11 +68,7 @@ func (r *Router) AddCountryCIDRs(cidrs ...string) {
|
||||
func (r *Router) RouteHandle(conn net.Conn, domain string, port uint16) (err error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
deferlog.DebugWarn(err).
|
||||
Str("domain", domain).
|
||||
Uint16("port", port).
|
||||
Dur("spend", time.Since(start)).
|
||||
Msg("serve socks5")
|
||||
deferlog.DebugWarn(err, "route handle", "domain", domain, "port", port, "took", time.Since(start))
|
||||
}()
|
||||
|
||||
addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10))
|
||||
|
||||
@@ -6,15 +6,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/transport/sower"
|
||||
"github.com/wweir/sower/transport/trojan"
|
||||
"github.com/sower-proxy/sower/transport/sower"
|
||||
"github.com/sower-proxy/sower/transport/trojan"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Logger = log.Logger.With().Caller().Logger()
|
||||
}
|
||||
|
||||
func testPipe(tran Transport) (net.Addr, error) {
|
||||
r, w := net.Pipe()
|
||||
defer r.Close()
|
||||
|
||||
Reference in New Issue
Block a user