Compare commits

...
17 Commits
Author SHA1 Message Date
wweir 677b0593b8 refactor: update sowerd main.go configuration handling 2025-10-28 20:04:27 +08:00
wweir af61d38b9e refactor: extract configuration to dedicated config package 2025-10-27 11:28:42 +08:00
wweir f7a288af4c refactor: extract configuration to dedicated config package
- Move configuration structures from cmd/sower/main.go and cmd/sowerd/main.go to config package
- Add config.SowerConfig and config.SowerdConfig types
- Implement Validate() method for configuration validation
- Update Go module dependencies to latest versions
- Improve code organization and maintainability
2025-09-07 00:10:56 +08:00
wweir 91b7f8e8c1 refactor: rename module from github.com/wweir/sower to github.com/sower-proxy/sower
- Update go.mod module declaration
- Update all import paths in Go files
- Update GitHub links in README.md
- All tests pass and build succeeds
2025-09-06 08:27:11 +08:00
wweir 53b1de41d0 Update sower.service to use -c option for config file 2025-08-16 11:03:31 +08:00
温习江湖 6c9b9d9597 Merge pull request #32 from sower-proxy/dependabot/go_modules/golang.org/x/net-0.38.0
chore(deps): bump golang.org/x/net from 0.35.0 to 0.38.0
2025-07-12 06:20:08 +08:00
dependabot[bot] 1d8f0fdabe chore(deps): bump golang.org/x/net from 0.35.0 to 0.38.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-26 11:30:40 +00:00
wweir c347e15fe2 fix(stop): avoid unexpected stop 2025-05-14 13:17:18 +08:00
wweir 62176ffb65 feat: support fake site serve directory 2025-04-29 16:18:56 +08:00
wweir 5fe202314d feat: change log package to log/slog 2025-04-29 14:24:35 +08:00
wweir fc7183d70e feat: support force setting upstream dns server 2025-03-18 18:06:33 +08:00
wweir 4a9197a4ec fix: upstream all failed case 2024-11-03 00:40:23 +08:00
wweir 9eaf27fdcc fix(dns): serve interface parser 2024-10-12 10:26:29 +08:00
wweir ea6c2a29ad feat: reduce dependency on host DNS configuration 2024-10-12 10:10:52 +08:00
wweir 77c4154c7c fix: http_proxy 2024-10-12 07:20:39 +08:00
wweir eadb2f0bdf fix: sync example config with codes 2024-09-23 19:09:22 +08:00
wweir 19b61945ad fix: ipv6 support 2024-09-10 14:11:12 +08:00
30 changed files with 692 additions and 441 deletions
+3 -2
View File
@@ -10,9 +10,10 @@ remote {
dns {
disable = false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve_ips = ["127.0.0.1"]
serve = "127.0.0.1"
# serve6 = "<ipv6>"
# 监听的网络接口
serve_iface = "lo"
# serve_iface = "lo"
# 必填,优先使用 DHCP 中的 DNS 服务器,如果没有则使用该地址
fallback = "223.5.5.5"
}
+1 -1
View File
@@ -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
+2 -1
View File
@@ -8,7 +8,8 @@ password = "I_am_Passw0rd"
[dns]
disable = false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve_ips = ["127.0.0.1"]
serve = "127.0.0.1"
# serve6 = <ipv6>
# 监听的网络接口
# serve_iface = "lo"
+3 -3
View File
@@ -8,10 +8,10 @@ remote: # 远程、上游连接方式
dns:
disable: false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve_ips:
- "127.0.0.1"
serve: "127.0.0.1"
# serve6: <ipv6>
# 监听的网络接口
serve_iface: "lo"
# serve_iface: "lo"
# 必填,优先使用 DHCP 中的 DNS 服务器,如果没有则使用该地址
fallback: "223.5.5.5"
+55
View File
@@ -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. 最小权限原则
+1 -1
View File
@@ -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
+7 -7
View File
@@ -1,12 +1,12 @@
# sower
[![GitHub release](http://img.shields.io/github/release/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/releases)
[![Actions Status](https://github.com/wweir/sower/workflows/Go/badge.svg)](https://github.com/wweir/sower/actions)
[![GitHub issue](https://img.shields.io/github/issues/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/wweir/sower.svg?style=popout)](LICENSE)
[![GitHub release](http://img.shields.io/github/release/sower-proxy/sower.svg?style=popout)](https://github.com/sower-proxy/sower/releases)
[![Actions Status](https://github.com/sower-proxy/sower/workflows/Go/badge.svg)](https://github.com/sower-proxy/sower/actions)
[![GitHub issue](https://img.shields.io/github/issues/sower-proxy/sower.svg?style=popout)](https://github.com/sower-proxy/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/sower-proxy/sower.svg?style=popout)](https://github.com/sower-proxy/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/sower-proxy/sower.svg?style=popout)](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)`.
+61 -98
View File
@@ -3,7 +3,9 @@ package main
import (
"bufio"
"compress/gzip"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"os"
@@ -16,66 +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 {
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" required:"true" 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(),
@@ -83,49 +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 && conf.DNS.Serve == "" {
conf.DNS.Serve = ip.String()
}
if ip.To16() != nil && conf.DNS.Serve6 == "" {
conf.DNS.Serve = ip.String()
}
}
if err := conf.Validate(); err != nil {
slog.Error("validate config", "error", err)
os.Exit(1)
}
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) != "" {
@@ -134,33 +91,44 @@ func main() {
if strings.TrimSpace(conf.DNS.Serve6) != "" {
ips = append(ips, conf.DNS.Serve6)
}
for _, ip := range ips {
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(ip, "80"))
log.DebugFatal(err).Str("listen_on", ip).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", ip).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)
}()
@@ -172,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 {}
}
@@ -240,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() {
@@ -259,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
}
+83 -99
View File
@@ -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/teeconn"
"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/conns/reread"
"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,53 +86,49 @@ 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)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
req, err := http.ReadRequest(bufio.NewReader(teeconn))
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()
teeconn.Stop().Reread()
err = relay.Relay(teeconn, rc)
log.DebugWarn(err).
Str("host", req.Host).
Dur("spend", time.Since(start)).
Msg("serve http")
reread.Stop().Reread()
err = relay.Relay(reread, rc)
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)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
var domain string
tls.Server(teeconn, &tls.Config{
tls.Server(reread, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
@@ -126,94 +137,67 @@ 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()
teeconn.Stop().Reread()
err = relay.Relay(teeconn, rc)
log.DebugWarn(err).
Str("host", domain).
Dur("spend", time.Since(start)).
Msg("serve http")
reread.Stop().Reread()
err = relay.Relay(reread, rc)
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()
teeConn := teeconn.New(conn)
reread := reread.New(conn)
buf1 := make([]byte, 1)
if _, err := teeConn.Read(buf1); err != nil {
log.Warn().Err(err).
Msg("read socks5 version")
byte1 := make([]byte, 1)
if n, err := reread.Read(byte1); err != nil || n != 1 {
slog.Error("read first byte", "error", err)
return
}
reread.Reread()
if byte1[0] == 5 {
reread.Stop()
if addr, err := socks5.New().Unwrap(reread); err != nil {
slog.Error("read socks5 request", "error", err)
} else {
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(reread, host, port)
}
return
}
if buf1[0] != 5 {
teeConn.Reread()
serveHttpProxy(teeConn, r)
return
}
teeConn.Stop().Reread()
addr, err := socks5.New().Unwrap(teeConn)
req, err := http.ReadRequest(bufio.NewReader(reread))
if err != nil {
log.Warn().Err(err).
Msgf("parse socks5 target: %s", addr)
slog.Error("read http request", "error", err)
return
}
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(teeConn, host, port)
}
func serveHttpProxy(teeConn *teeconn.Conn, r *router.Router) {
req, err := http.ReadRequest(bufio.NewReader(teeConn))
host, port, err := router.ParseHostPort(req.Host, req.URL)
if err != nil {
log.Warn().Err(err).
Msg("read http request")
reread.Stop().Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
return
}
teeConn.Stop()
switch req.Method {
case "CONNECT":
if _, err := teeConn.Write([]byte(req.Proto + " 200 Connection established\r\n\r\n")); err != nil {
log.Warn().Err(err).
Msg("write https proxy response")
return
}
host, port, err := router.ParseHostPort(req.Host, 443)
if err != nil {
log.Warn().Err(err).
Msg("parse https_proxy target")
return
}
rc, err := r.ProxyDial("tcp", host, port)
if err != nil {
log.Warn().Err(err).
Str("host", host).
Msg("dial proxy")
return
}
defer rc.Close()
relay.Relay(teeConn, rc)
default:
teeConn.Reread()
r.RouteHandle(teeConn, req.Host, 80)
if req.Method == http.MethodConnect {
// https
reread.Stop().Reset()
reread.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
} else {
// http
reread.Stop().Reread()
}
r.RouteHandle(reread, host, port)
}
+60 -70
View File
@@ -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/teeconn"
"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/conns/reread"
"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, 0600); err != nil {
log.Fatal().Err(err).
Str("dir", cacheDir).
Msg("make cache dir")
if err := os.MkdirAll(cacheDir, 0o600); err != nil {
slog.Error("create cache dir", "error", err)
os.Exit(1)
}
certManager := autocert.Manager{
@@ -66,83 +62,77 @@ 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)
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
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
teeconn.Reread()
if addr, err = sower.Unwrap(teeconn); err == nil {
teeconn.Stop()
reread.Reread()
if addr, err = sower.Unwrap(reread); err == nil {
reread.Stop()
dur, err = relay.RelayTo(teeconn, addr.String())
dur, err = relay.RelayTo(reread, addr.String())
return
}
// 2. detect if it's a trojan underlaying connection
teeconn.Reread()
if addr, err = trojan.Unwrap(teeconn); err == nil {
teeconn.Stop()
reread.Reread()
if addr, err = trojan.Unwrap(reread); err == nil {
reread.Stop()
dur, err = relay.RelayTo(teeconn, addr.String())
dur, err = relay.RelayTo(reread, addr.String())
return
}
// 3. fallback to fake site
teeconn.Stop().Reread()
dur, err = relay.RelayTo(teeconn, fakeSite)
reread.Stop().Reread()
dur, err = relay.RelayTo(reread, fakeSite)
}
+95
View File
@@ -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
}
+52
View File
@@ -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
+52
View File
@@ -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
+25
View File
@@ -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
}
+16
View File
@@ -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)
+16
View File
@@ -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)
+22 -21
View File
@@ -1,37 +1,38 @@
module github.com/wweir/sower
module github.com/sower-proxy/sower
go 1.22
toolchain go1.22.3
go 1.24.6
require (
github.com/cristalhq/aconfig v0.18.5
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.59
github.com/oschwald/geoip2-golang v1.9.0
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.0.3
github.com/sower-proxy/deferlog v1.0.7
github.com/sower-proxy/conns v0.1.1
github.com/sower-proxy/deferlog/v2 v2.0.1
github.com/sower-proxy/mem v0.0.3
golang.org/x/crypto v0.23.0
golang.org/x/crypto v0.37.0
)
require (
github.com/BurntSushi/toml v1.3.2 // 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.20 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/rs/zerolog v1.32.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.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
)
+40 -47
View File
@@ -1,69 +1,62 @@
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/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.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
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=
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
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/sower-proxy/conns v0.0.3 h1:3jbkvPhDwC+ph/NZi7Gywks9WakHK4ept7CkjTiNUJ8=
github.com/sower-proxy/conns v0.0.3/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/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/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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))
+10 -4
View File
@@ -2,17 +2,23 @@ package router
import (
"net"
"net/url"
"strconv"
)
func ParseHostPort(hostport string, defaultPort uint16) (string, uint16, error) {
func ParseHostPort(hostport string, u *url.URL) (string, uint16, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if err.(*net.AddrError).Err == "missing port in address" {
switch u.Scheme {
case "http":
return hostport, 80, nil
case "https":
return hostport, 443, nil
}
}
return "", 0, err
}
if port == "" {
return host, defaultPort, nil
}
portInt, err := strconv.ParseUint(port, 10, 16)
if err != nil {
+6 -2
View File
@@ -17,7 +17,7 @@ type authReq struct {
func (req *authReq) Fulfill(r io.Reader) error {
buf := make([]byte, 2)
if n, err := r.Read(buf); err != nil || n != 2 {
if n, err := io.ReadFull(r, buf); err != nil || n != 2 {
return err
}
@@ -25,7 +25,7 @@ func (req *authReq) Fulfill(r io.Reader) error {
req.NMETHODS = buf[1]
req.METHODS = make([]byte, int(req.NMETHODS))
if n, err := r.Read(req.METHODS); err != nil || n != len(req.METHODS) {
if n, err := io.ReadFull(r, req.METHODS); err != nil || n != len(req.METHODS) {
return err
}
@@ -70,6 +70,7 @@ type addrType interface {
}
// ATYP:
//
// 0x01 -> net.IPv4len
// 0x03 -> first byte is length
// 0x04 -> net.IPv6len
@@ -81,6 +82,7 @@ type addrTypeIPv4 struct {
func (a *addrTypeIPv4) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv4) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
@@ -108,6 +110,7 @@ func (a *addrTypeDomain) Fulfill(r io.Reader) error {
return nil
}
func (a *addrTypeDomain) Addr() (string, uint16) {
return string(a.DST_ADDR[:]), a.DST_PORT
}
@@ -120,6 +123,7 @@ type addrTypeIPv6 struct {
func (a *addrTypeIPv6) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv6) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
+7 -5
View File
@@ -28,14 +28,16 @@ func New() *Socks5 {
return &Socks5{}
}
var noAuthResp = authResp{VER: 5, METHOD: 0}
var succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
var (
noAuthResp = authResp{VER: 5, METHOD: 0}
succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
)
func (s *Socks5) Unwrap(conn net.Conn) (net.Addr, error) {
{ //auth
{ // auth
auth := new(authReq)
if err := auth.Fulfill(conn); err != nil && !auth.IsValid() {
return nil, errors.Errorf("read head: %v, err: %s", auth, err)
if err := auth.Fulfill(conn); err != nil || !auth.IsValid() {
return nil, errors.Errorf("read auth head: %v, err: %s", auth, err)
}
if err := binary.Write(conn, binary.BigEndian, noAuthResp); err != nil {
+4 -9
View File
@@ -5,16 +5,11 @@ import (
"strings"
"testing"
"github.com/sower-proxy/conns/teeconn"
"github.com/sower-proxy/deferlog/log"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
"github.com/sower-proxy/conns/reread"
"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()
@@ -24,7 +19,7 @@ func testPipe(tran Transport) (net.Addr, error) {
tran.Wrap(w, "sower", 443)
}(w)
return tran.Unwrap(teeconn.New(r))
return tran.Unwrap(reread.New(r))
}
func Test_Transports(t *testing.T) {
+5 -4
View File
@@ -71,15 +71,16 @@ func (*domain) Network() string { return "tcp" }
func (a *domain) String() string {
return net.JoinHostPort(a.ADDR, strconv.Itoa(int(a.PORT)))
}
func (a *domain) Fulfill(r io.Reader) error {
buf := make([]byte, 1)
if n, err := r.Read(buf); err != nil || n != 1 {
if n, err := io.ReadFull(r, buf); err != nil || n != 1 {
return errors.New("read domain length failed")
}
addrLen := int(buf[0])
buf = make([]byte, addrLen+4)
if n, err := r.Read(buf); err != nil || n != addrLen+4 {
if n, err := io.ReadFull(r, buf); err != nil || n != addrLen+4 {
return errors.Wrap(err, "read doamin failed")
}
@@ -127,12 +128,12 @@ func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) {
head.CMD, head.ATYP = buf[58], buf[59]
switch head.ATYP {
case 0x01: //ipv4
case 0x01: // ipv4
addr := &ipv4Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x04: //ipv6
case 0x04: // ipv6
addr := &ipv6Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")