Compare commits

...
37 Commits
Author SHA1 Message Date
wweir b1f152cc0c fix: dns upstream lookback case 2022-04-05 23:42:33 +08:00
wweir c0b77da05a fix(#22): ** preifx match none 2022-02-21 08:38:13 +08:00
wweir bad7ae702a chore: separate packages 2021-12-06 14:44:24 +08:00
wweir 05a1a3249a chore: update dependency 2021-12-03 11:01:45 +08:00
wweir 0f029f8c15 refactor: reduce memory usage 2021-11-15 14:21:23 +08:00
wweir 8c9a0bc1dd refactor: reduce memory usage 2021-11-15 11:33:15 +08:00
wweir 465ff316fd refactor: refactor suffix tree to reduce memory usage 2021-11-15 09:18:02 +08:00
wweir a86de61a45 chore: add default config and service scripts 2021-11-10 20:08:46 +08:00
wweir ffca4220d6 chore: fix cicd 2021-11-10 19:27:35 +08:00
wweir 48e9680c03 chore: update dependency 2021-11-10 18:58:24 +08:00
wweir dabcf998c9 fix windows release binary 2021-09-09 08:09:59 +08:00
wweir 133684e5d2 update golang version 2021-08-18 13:16:52 +08:00
wweir a28a211d2d change ping logix 2021-08-15 13:45:28 +08:00
wweir 28696acafb format readme 2021-08-14 20:45:45 +08:00
wweir 38c7391ceb 1. add comments, 2. optimize text message 2021-08-11 06:48:18 +08:00
wweir 65234b1666 migrate deferlog to the separate package 2021-08-09 23:49:51 +08:00
wweir adca6606d2 remove unused code 2021-08-09 22:53:58 +08:00
wweir cfc1afb710 change architecture diagram 2021-08-09 08:30:38 +08:00
wweir 397825556c update the document 2021-08-09 06:53:44 +08:00
wweir 8694ff6edd fix for socks5 IP proxy 2021-08-07 05:59:16 +08:00
wweir a38affd97b fix panic in invalid socks5 case 2021-08-02 14:18:51 +08:00
wweir 74202aad83 optimize sowerd code 2021-07-30 19:13:59 +08:00
wweir 59dfa934a2 optimize code, comment / log / flag 2021-07-29 19:06:34 +08:00
wweir 8bfb26ba3f support use socks5 as upstream proxy 2021-07-29 16:19:55 +08:00
wweir fbc6c93395 extend sower protocol checksum length 2021-07-29 08:51:46 +08:00
wweir 2d269468ea add dns cache for dns proxy 2021-07-29 07:14:49 +08:00
wweir d7c0ba112e fix DNS forword issue, inspired by skydns 2021-07-28 14:59:48 +08:00
wweir d31ba7ef79 add deferlog package 2021-07-28 12:03:31 +08:00
wweir 8d32bca742 change dns solution config 2021-07-21 17:51:48 +08:00
wweir 426799a961 optimize config and log 2021-07-20 15:17:57 +08:00
wweir 5cf8df2a7a change binary name 2021-07-19 16:02:26 +08:00
wweir 7d024f3bfe support rule based config 2021-07-18 07:58:27 +08:00
wweir 585cc01744 update depency 2021-07-18 00:48:39 +08:00
wweir 8307128844 refactor, start v0.8 2021-07-17 08:05:02 +08:00
wweir dab1a78bbf mirate to trojan protocol 2020-07-31 13:51:51 +08:00
wweir 13a6be47a4 fix windows and daewin support 2020-07-13 13:45:33 +08:00
wweir 56b8e35fe7 support config reference 2020-07-13 07:24:40 +08:00
56 changed files with 2159 additions and 2213 deletions
-17
View File
@@ -1,17 +0,0 @@
# Compile
FROM golang:1.14-alpine AS compiler
RUN apk add --no-cache git make
# enable go modules
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 make build
# Build image
FROM scratch
COPY --from=compiler /src/sower /sower
ENTRYPOINT [ "/sower" ]
+81
View File
@@ -0,0 +1,81 @@
remote {
type = "sower"
addr = "proxy.com"
password = "I_am_Passw0rd"
# type="socks5"
# addr="127.0.0.1:7890"
}
dns {
serve = "127.0.0.1"
fallback = "223.5.5.5"
}
socks_5 {
addr = ":1080"
}
router "block" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/adlist/adlist.txt"
rules = []
}
router "direct" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chinalist/chinalist.txt"
rules = [
"imap.*.*",
"imap.*.*.*",
"smtp.*.*",
"smtp.*.*.*",
"pop.*.*",
"pop.*.*.*",
"pop3.*.*",
"pop3.*.*.*",
"**.cn",
]
}
router "proxy" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/gfwlist/gfwlist.txt"
rules = [
"**.google.*",
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com",
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com",
"**.fbcdn.net",
"**.twitter.com",
"**.twimg.com",
"**.blogspot.com",
"**.appspot.com",
"**.wikipedia.org",
"**.wikimedia.org",
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.githubassets.com",
"*.github.*",
"lookup-api.apple.com",
]
}
router "country" {
mmdb = "Country.mmdb" # https://github.com/alecthw/mmdb_china_ip_list
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chnroute/chnroute.txt"
rules = [
"127.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"10.0.0.0/8",
"17.0.0.0/8",
"100.64.0.0/10",
"224.0.0.0/4",
"fe80::/10",
]
}
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>sower</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/sower</string>
<string>-f</string>
<string>/usr/local/etc/sower/sower.hcl</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>WorkingDirectory</key>
<string>/usr/local/etc</string>
</dict>
</plist>
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=sower client service
After=network.target
[Service]
Type=simple
WorkingDirectory=/etc/sower
ExecStart=/usr/local/bin/sower -f /etc/sower/sower.hcl
[Install]
WantedBy=multi-user.target
+1
View File
@@ -0,0 +1 @@
CreateObject("WScript.Shell").Run "C:\Software\Sower\sower.exe -f sower.hcl",0
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=sowerd server service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/sowerd
Environment="FAKE_SITE=127.0.0.1:8080"
Environment="PASSWORD=I_am_Passw0rd"
[Install]
WantedBy=multi-user.target
+3 -1
View File
@@ -8,7 +8,9 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2-beta
- uses: actions/setup-go@v2
with:
go-version: ^1.17
- uses: actions/checkout@v2
- name: test and build
+33 -34
View File
@@ -8,46 +8,45 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2-beta
- uses: actions/setup-go@v2
with:
go-version: ^1.17
- uses: actions/checkout@v2
- name: build matrix
run: |
cp conf/sower.toml sower.toml
mv .github/* . || true
GOOS=linux GOARCH=amd64 make build
tar czvf sower-linux-amd64.tar.gz sower sower.toml
GOOS=linux GOARCH=386 make build
tar czvf sower-linux-386.tar.gz sower sower.toml
GOOS=linux GOARCH=arm make build
tar czvf sower-linux-arm.tar.gz sower sower.toml
GOOS=linux GOARCH=arm64 make build
tar czvf sower-linux-arm64.tar.gz sower sower.toml
GOOS=linux GOARCH=mips make build
tar czvf sower-linux-mips.tar.gz sower sower.toml
GOOS=linux GOARCH=mipsle make build
tar czvf sower-linux-mipsle.tar.gz sower sower.toml
make build GO='GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-linux-amd64.tar.gz sower sowerd sower.hcl sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=arm CGO_ENABLED=0 go'
tar czvf sower-linux-arm.tar.gz sower sowerd sower.hcl sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-linux-arm64.tar.gz sower sowerd sower.hcl sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=mips CGO_ENABLED=0 go'
tar czvf sower-linux-mips.tar.gz sower sowerd sower.hcl sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=mipsle CGO_ENABLED=0 go'
tar czvf sower-linux-mipsle.tar.gz sower sowerd sower.hcl sower.service sowerd.service
make clean
GOOS=darwin GOARCH=amd64 make build
tar czvf sower-darwin-amd64.tar.gz sower sower.toml
make build GO='GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-darwin-amd64.tar.gz sower sowerd sower.hcl sower.plist
make clean
make build GO='GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-darwin-arm64.tar.gz sower sowerd sower.hcl sower.plist
make clean
GOOS=windows GOARCH=amd64 make build
tar czvf sower-windows-amd64.tar.gz sower.exe sower.toml
GOOS=windows GOARCH=386 make build
tar czvf sower-windows-386.tar.gz sower.exe sower.toml
make build GO='GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go'
mv sower sower.exe
mv sowerd sowerd.exe
tar czvf sower-windows-amd64.tar.gz sower.exe sowerd.exe sower.hcl sower_run.vbs
make clean
- name: Release
uses: softprops/action-gh-release@master
- uses: ncipollo/release-action@v1
with:
files: |
sower-linux-amd64.tar.gz
sower-linux-386.tar.gz
sower-linux-arm.tar.gz
sower-linux-arm64.tar.gz
sower-linux-mips.tar.gz
sower-linux-mipsle.tar.gz
sower-darwin-amd64.tar.gz
sower-windows-amd64.tar.gz
sower-windows-386.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
artifacts: "*.tar.gz"
token: ${{ secrets.GITHUB_TOKEN }}
+13 -3
View File
@@ -17,6 +17,16 @@
*.swo
.vscode
.idea
sower
sower.exe*
/sower.toml
**.env
**.mmdb
/cmd/client/client
/cmd/server/server
/sower
/sowerd
/*.yml
/*.yaml
/*.toml
/*.hcl
/*.tf
+21 -8
View File
@@ -1,12 +1,25 @@
MAKEFLAGS += --jobs all
GO:=CGO_ENABLED=0 go
default: test build
test:
go vet ./...
go list ./... | grep -v internal | xargs go test
build:
go build -ldflags "-w -s \
-X conf.version=$(shell git describe --tags --always) \
-X conf.date=$(shell date +%Y-%m-%d)"
image:
docker build -t sower -f .github/Dockerfile .
${GO} vet ./...
${GO} test ./...
build: sower sowerd
.PHONY: sower
sower:
${GO} build -ldflags "\
-X main.version=$(shell git describe --tags --always) \
-X main.date=$(shell date +%Y-%m-%d)" \
-o sower ./cmd/sower
.PHONY: sowerd
sowerd:
${GO} build -ldflags "\
-X main.version=$(shell git describe --tags --always) \
-X main.date=$(shell date +%Y-%m-%d)" \
-o sowerd ./cmd/sowerd
clean:
rm -f sower sowerd sower.exe sowerd.exe
+44 -63
View File
@@ -1,90 +1,71 @@
# 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)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/wweir/sower.svg?style=popout)](https://hub.docker.com/r/wweir/sower)
[![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)
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
The sower is a cross-platform intelligent transparent proxy tool.
The first time you visit a new website, the sower will detect if the domain is accessible and add it in the dynamic detect list. So, you do not need to care about the rules, sower will handle it intelligently.
Sower provider both http_proxy/https_proxy and DNS-based proxy. All these kinds of proxy support intelligent router. You can also port-forward any TCP request to remote, such as SSH / SMTP / POP3.
You can enjoy it by setting http_proxy or your DNS without any other settings.
The sower is a cross-platform intelligent transparent proxy tool. It provide both socks5 proxy and DNS-based proxy. All these kinds of proxy support intelligent router.
If you already have another proxy solution, you can use it's socks5(h) service as a parent proxy to enjoy the sower's intelligent router.
## Installation
To enjoy the sower, you need to deploy sower on both server-side and client-side.
The installation script has been integrated into the sower. You can install sower as system service by running `./sower -install 'xxx'` in terminal on the three most popular operating system platforms: Linux / Windows and masOS.
To enjoy the sower, you need to deploy sower on both server-side(sowerd) and client-side(sower).
## Server
*If you already have another proxy solution with socks5h support, you can skip server-side.*
## Sowerd
At the server-side, the sower runs just like a web server proxy.
It redirects HTTP requests to HTTPS and proxy https requests to the upstream HTTP service.
You can use your certificate or use the auto-generated certificate by the sower.
_If you wanna use sower as secondary proxy to provide intelligent router, you can skip sowerd._
What you must set is the upstream HTTP service. You can set it by parameter `-s`, eg:
``` shell
# sower -s 127.0.0.1:8080
```
At the server-side, the sowerd runs just like a web server proxy. It will occupy two ports `80` / `443`.
## Client
The easiest way to run it is:
``` shell
# sower -c aa.bb.cc # the `aa.bb.cc` can also be `socks5h://127.0.0.1:1080`
```
But a configuration file is recommended to persist dynamic rules in client side.
You can use your own certificate or the certificate automatically applied for by the sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
There are two ways to run the sowerd service:
### HTTP(S)_PROXY
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
1. run the shell command with root permission
### DNS-based proxy
**DNS-based** solution is not recommanded now, for incompatible with TLS 1.3 esni extention. DNS flush logic has been dropped, you should flush your dns cache manually
in Windows and macOS.
```shell
# sowerd -password XXX -fake_site 127.0.0.1:8080
```
You can set the `dns_serve_ip` field in the configuration file to start the DNS-based proxy. You should also set the value of `dns_upstream` as your default DNS in OS.
2. install as a systemd service
If you want to enjoy the full experience provided by the sower, you can take sower as your private DNS on a long-running server and set it as your default DNS in your router.
```service
[Unit]
Description=sower server service
After=network.target
### port-forward
The port-forward can be only setted in configuration file, you can set it in section `client.port_mapping`, eg:
``` toml
[client.port_mapping]
":2222"="aa.bb.cc:22"
```
[Service]
Type=simple
ExecStart=/usr/local/bin/sowerd
Environment="FAKE_SITE=127.0.0.1:8080"
Environment="PASSWORD=XXX"
[Install]
WantedBy=multi-user.target
```
## Sower
A config file is required in sower client side. [Here](https://github.com/wweir/sower/wiki/sower.hcl) is an usable example in China.
`Sower` will take 4 port by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
After do the next three step, you can enjoy the intelligent transparent proxy solution:
1. run the command line with root permission:
```shell
# sower -f sower.hcl
```
2. changing your DNS server to `127.0.0.1` and
3. setting your proxy to `socks5h://127.0.0.1:1080`.
## Architecture
```
relay <--+ +-> target
http service | | service
+-------+-------+----+
| sower server |
+----^-------^-------+
80 443
301 http -+ +----- https
to https | service
protected
by tls
socks5 |
dns <---+ ^ | +--> direct
relay | | | | request
+---+---+----+--+----+
| sower client |
+----^--^----^---^---+
| | | |
dns --+ + + +-- port
80 http(s) forward
443 proxy
```
![Architecture diagram](./sower.drawio.svg)
+242
View File
@@ -0,0 +1,242 @@
package main
import (
"bufio"
"io"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/cristalhq/aconfig"
"github.com/cristalhq/aconfig/aconfighcl"
"github.com/cristalhq/aconfig/aconfigtoml"
"github.com/cristalhq/aconfig/aconfigyaml"
"github.com/miekg/dns"
"github.com/pkg/errors"
"github.com/sower-proxy/deferlog/log"
"github.com/wweir/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"`
Fallback string `default:"223.5.5.5" usage:"fallback dns server"`
}
Socks5 struct {
Disable bool `default:"false" usage:"disable sock5 proxy"`
Addr string `default:":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"`
}
}
}{}
)
func init() {
if err := aconfig.LoaderFor(&conf, aconfig.Config{
AllowUnknownFields: true,
FileFlag: "f",
FileDecoders: map[string]aconfig.FileDecoder{
".yml": aconfigyaml.New(),
".yaml": aconfigyaml.New(),
".toml": aconfigtoml.New(),
".hcl": aconfighcl.New(),
},
}).Load(); err != nil {
log.Fatal().Err(err).
Interface("config", conf).
Msg("Load config")
}
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")
}
func main() {
proxtDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password)
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Fallback, conf.Router.Country.MMDB, proxtDial)
r.SetBlockRules(conf.Router.Block.Rules)
r.SetDirectRules(conf.Router.Direct.Rules)
r.SetProxyRules(conf.Router.Proxy.Rules)
r.SetCountryCIDRs(conf.Router.Country.Rules)
go func() {
if conf.DNS.Disable {
log.Info().Msg("DNS proxy disabled")
return
}
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "80"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTP(lnHTTP, r)
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "443"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTPS(lnHTTPS, r)
log.Info().
Str("listen_on", conf.DNS.Serve).
Msg("DNS proxy started")
if err := dns.ListenAndServe(net.JoinHostPort(conf.DNS.Serve, "53"), "udp", r); err != nil {
log.Fatal().Err(err).Msg("serve dns")
}
}()
go func() {
if conf.Socks5.Disable {
log.Info().Msg("SOCKS5 proxy disabled")
return
}
ln, err := net.Listen("tcp", conf.Socks5.Addr)
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
log.Info().Msgf("SOCKS5 proxy listening on %s", conf.Socks5.Addr)
go ServeSocks5(ln, r)
}()
start := time.Now()
r.SetBlockRules(append(conf.Router.Block.Rules,
loadRules(proxtDial, conf.Router.Block.File, conf.Router.Block.FilePrefix)...))
r.SetDirectRules(append(conf.Router.Direct.Rules,
loadRules(proxtDial, conf.Router.Direct.File, conf.Router.Direct.FilePrefix)...))
r.SetProxyRules(append(conf.Router.Proxy.Rules,
loadRules(proxtDial, conf.Router.Proxy.File, conf.Router.Proxy.FilePrefix)...))
r.SetCountryCIDRs(append(conf.Router.Country.Rules,
loadRules(proxtDial, conf.Router.Country.File, conf.Router.Country.FilePrefix)...))
log.Info().
Dur("spend", time.Since(start)).
Int("blockRule", len(conf.Router.Block.Rules)).
Int("directRule", len(conf.Router.Direct.Rules)).
Int("proxyRule", len(conf.Router.Proxy.Rules)).
Int("countryRule", len(conf.Router.Country.Rules)).
Msg("Loaded rules, proxy started")
runtime.GC()
select {}
}
func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
var loadFn func() (io.ReadCloser, error)
if _, err := url.Parse(file); err == nil {
// load rule file from remote by HTTP
client := &http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
domain, port, _ := net.SplitHostPort(addr)
p, _ := strconv.Atoi(port)
return proxyDial("tcp", domain, uint16(p))
},
},
}
loadFn = func() (io.ReadCloser, error) {
resp, err := client.Get(file)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.Errorf("status code: %d", resp.StatusCode)
}
return resp.Body, nil
}
} else {
// load rule file from local file
loadFn = func() (io.ReadCloser, error) {
return os.Open(file)
}
}
// load rule file, retry 10 times
rc, err := loadFn()
for i := time.Duration(1); i < 10; i++ {
if err == nil {
break
}
// wait: 28.5s
time.Sleep(i * i * 100 * time.Millisecond)
rc, err = loadFn()
}
if err != nil {
log.Fatal().Err(err).
Str("file", file).
Msg("load config file")
}
defer rc.Close()
// parse rule file into rule tree
var lines []string
br := bufio.NewReader(rc)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
break
} else if err != nil {
log.Error().Err(err).
Str("file", file).
Msg("read line")
return nil
}
if strings.TrimSpace(string(line)) == "" {
continue
}
// use line content as suffix
lines = append(lines, linePrefix+string(line))
}
return lines
}
+162
View File
@@ -0,0 +1,162 @@
package main
import (
"bufio"
"crypto/tls"
"net"
"net/http"
"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"
)
func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn {
var proxy transport.Transport
var dialFn func() (net.Conn, error)
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)
}
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)
}
case "socks5":
proxy = socks5.New()
dialFn = func() (net.Conn, error) {
return net.Dial("tcp", proxyHost)
}
default:
log.Fatal().
Str("type", conf.Remote.Type).
Msg("unknown proxy type")
}
return func(network, host string, port uint16) (net.Conn, error) {
if host == "" || port == 0 {
return nil, errors.Errorf("invalid addr(%s:%d)", host, port)
}
conn, err := dialFn()
if err != nil {
return nil, err
}
if err := proxy.Wrap(conn, host, port); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}
func ServeHTTP(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeHTTP(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
req, err := http.ReadRequest(bufio.NewReader(teeconn))
if err != nil {
log.Error().Err(err).Msg("read http request")
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")
return
}
defer rc.Close()
teeconn.Stop().Reread()
relay.Relay(teeconn, rc)
log.Debug().
Str("host", req.Host).
Dur("spend", time.Since(start)).
Msg("serve http")
}
func ServeHTTPS(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeHTTPS(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
var domain string
tls.Server(teeconn, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
},
}).Handshake()
rc, err := r.ProxyDial("tcp", domain, 443)
if err != nil {
log.Error().Err(err).
Str("host", domain).
Msg("dial proxy")
return
}
defer rc.Close()
teeconn.Stop().Reread()
relay.Relay(teeconn, rc)
log.Debug().
Str("host", domain).
Dur("spend", time.Since(start)).
Msg("serve http")
}
func ServeSocks5(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeSocks5(ln, r)
defer conn.Close()
addr, err := socks5.New().Unwrap(conn)
if err != nil {
log.Warn().Err(err).
Msgf("parse socks5 target: %s", addr)
return
}
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(conn, host, port)
}
+143
View File
@@ -0,0 +1,143 @@
package main
import (
"crypto/tls"
"net"
"net/http"
"os"
"path/filepath"
"time"
"github.com/cristalhq/aconfig"
"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"
"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 `required:"true" default:"127.0.0.1:8080" usage:"fake site address"`
Cert struct {
Email string
Cert string
Key string
}
}{}
)
func init() {
err := aconfig.LoaderFor(&conf, aconfig.Config{}).Load()
log.InfoFatal(err).
Str("version", version).
Str("date", date).
Interface("config", conf).
Msg("Load config")
}
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")
}
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Email: conf.Cert.Email,
Cache: autocert.DirCache(cacheDir),
}
tlsConf := &tls.Config{
GetCertificate: certManager.GetCertificate,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", "h2"},
}
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")
}
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")
}()
ln, err := tls.Listen("tcp", net.JoinHostPort(conf.ServeIP, "443"), tlsConf)
log.InfoFatal(err).
Str("IP", conf.ServeIP).
Msg("Start listen HTTPS service")
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")
}
go serve443(ln, fakeSite, sower, trojan)
teeconn := teeconn.New(conn)
defer teeconn.Close()
var addr net.Addr
var dur time.Duration
defer func() {
deferlog.DebugWarn(err).
Dur("spend", dur).
Msgf("relay conn to %s", addr)
}()
// 1. detect if it's a sower underlaying connection
teeconn.Reread()
if addr, err = sower.Unwrap(teeconn); err == nil {
teeconn.Stop()
dur, err = relay.RelayTo(teeconn, addr.String())
return
}
// 2. detect if it's a trojan underlaying connection
teeconn.Reread()
if addr, err = trojan.Unwrap(teeconn); err == nil {
teeconn.Stop()
dur, err = relay.RelayTo(teeconn, addr.String())
return
}
// 3. fallback to fake site
teeconn.Stop().Reread()
dur, err = relay.RelayTo(teeconn, fakeSite)
}
-149
View File
@@ -1,149 +0,0 @@
package conf
import (
"flag"
"os"
"path/filepath"
"sync"
toml "github.com/pelletier/go-toml"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
)
type Client struct {
Address string `toml:"address"`
HTTPProxy string `toml:"http_proxy"`
DNSServeIP string `toml:"dns_serve_ip"`
DNSUpstream string `toml:"dns_upstream"`
PortForward map[string]string `toml:"port_forward"`
Router struct {
DetectLevel int `toml:"detect_level"`
ProxyList []string `toml:"proxy_list"`
DirectList []string `toml:"direct_list"`
} `toml:"router"`
}
type Server struct {
Upstream string `toml:"upstream"`
CertFile string `toml:"cert_file"`
KeyFile string `toml:"key_file"`
CertEmail string `toml:"cert_email"`
}
var (
version, date string
execFile, _ = os.Executable()
execDir, _ = filepath.Abs(filepath.Dir(execFile))
// Conf full config, include common and server / client
conf = struct {
file string
Password string `toml:"password"`
Client Client `toml:"client"`
Server Server `toml:"server"`
}{}
)
func Init() (*Client, *Server, string) {
beforeInitFlag()
defer afterInitFlag()
flag.StringVar(&conf.Password, "password", "", "password")
flag.StringVar(&conf.Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
flag.StringVar(&conf.Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
flag.StringVar(&conf.Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
flag.StringVar(&conf.Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
flag.StringVar(&conf.Client.HTTPProxy, "http_proxy", ":8080", "http proxy, empty to disable")
if !flag.Parsed() {
flag.Parse()
}
defer log.Infow("starting", "config", &conf)
if conf.file == "" {
return &conf.Client, &conf.Server, conf.Password
}
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Fatalw("load config", "config", conf.file, "step", loadConfigFns[i].step, "err", err)
}
}
return &conf.Client, &conf.Server, conf.Password
}
// refreshFns will be executed while init and write new config
var loadConfigFns = []struct {
step string
fn func() error
}{{"load_config", func() error {
f, err := os.OpenFile(conf.file, os.O_RDONLY, 0644)
if err != nil {
return err
}
defer f.Close()
return toml.NewDecoder(f).Decode(&conf)
}}}
// flushCh to avoid parallel persist
var flushCh = make(chan struct{})
var flushOnce = sync.Once{}
// PersistRule persist rule into config file
func PersistRule(domain string) {
flushOnce.Do(func() {
go flushConfDaemon()
})
log.Infow("persist direct rule into config", "domain", domain)
conf.Client.Router.DirectList = append(conf.Client.Router.DirectList, domain)
select {
case flushCh <- struct{}{}:
default:
}
}
func flushConfDaemon() {
for range flushCh {
// safe write file
if conf.file != "" {
f, err := os.OpenFile(conf.file+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
continue
}
conf.Client.Router.DirectList =
util.NewReverseSecSlice(conf.Client.Router.DirectList).Sort().Uniq()
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(&conf); err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
f.Close()
continue
}
f.Close()
if stat, err := os.Stat(conf.file); err != nil {
log.Warnw("get file stat", "file", conf.file, "err", err)
} else {
// There is no common way to transfer ownership for a file
// cross-platform. Drop the ownership support but file mod.
if err = os.Chmod(conf.file+"~", stat.Mode()); err != nil {
log.Warnw("set file mod", "file", conf.file+"~", "err", err)
}
}
if err = os.Rename(conf.file+"~", conf.file); err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
continue
}
}
// reload config
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Errorw("flush config", "step", loadConfigFns[i].step, "err", err)
}
}
}
}
-108
View File
@@ -1,108 +0,0 @@
// +build darwin
package conf
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/wweir/utils/log"
)
const svcPath = "/Library/LaunchDaemons/sower.plist"
const svcFile = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>sower</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>%s %s</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>`
var (
ConfigDir = ""
installCmd = ""
uninstallFlag = false
)
func beforeInitFlag() {
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
ConfigDir = execDir
} else {
dir, _ := os.UserConfigDir()
ConfigDir = filepath.Join("/", dir, "sower")
}
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
} else {
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
}
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f \""+ConfigDir+"/sower.toml\"'")
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
}
func afterInitFlag() {
switch {
case installCmd != "":
install()
case uninstallFlag:
uninstall()
default:
if conf.Client.DNSServeIP != "" {
log.Infow("setting system DNS", "DNS", conf.Client.DNSServeIP)
if err := execute("networksetup -listallnetworkservices | grep -v '*' | xargs -I {} " +
"networksetup -setdnsservers {} " + conf.Client.DNSServeIP); err != nil {
log.Errorf("set DNS fail, please set DNS to %s manually", conf.Client.DNSServeIP)
}
execute("pkill mDNSResponder")
}
return
}
os.Exit(0)
}
func install() {
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
log.Fatalw("write service file", "err", err)
}
execute("launchctl unload " + svcPath)
if err := execute("launchctl load -wF " + svcPath); err != nil {
log.Fatalw("install service", "err", err)
}
}
func uninstall() {
execute("launchctl unload " + svcPath)
os.Remove(svcPath)
os.RemoveAll("/etc/sower")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
}
return nil
}
-112
View File
@@ -1,112 +0,0 @@
// +build linux
package conf
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/wweir/utils/log"
)
const svcPath = "/etc/systemd/system/sower.service"
const svcFile = `[Unit]
Description=Sower client service
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
User=root
WorkingDirectory=/tmp
ExecStart=%s %s
RestartSec=3
Restart=on-failure`
var (
ConfigDir = ""
installCmd = ""
uninstallFlag = false
)
func beforeInitFlag() {
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
ConfigDir = execDir
} else if stat, err := os.Stat("/etc/sower"); err == nil && stat.IsDir() {
ConfigDir = "/etc/sower"
} else {
dir, _ := os.UserConfigDir()
ConfigDir = filepath.Join("/", dir, "sower")
}
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
} else {
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
}
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f "+ConfigDir+"/sower.toml'")
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
}
func afterInitFlag() {
switch {
case installCmd != "":
install()
case uninstallFlag:
uninstall()
default:
if conf.Client.DNSServeIP != "" {
log.Infow("setting system DNS", "DNS", conf.Client.DNSServeIP)
if err := execute(fmt.Sprintf("grep '%s' || sed -i '1inameserver %s' /etc/resolv.conf",
conf.Client.DNSServeIP, conf.Client.DNSServeIP)); err != nil {
log.Errorf("set DNS fail, please set DNS to %s manually", conf.Client.DNSServeIP)
}
}
return
}
os.Exit(0)
}
func install() {
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
log.Fatalw("write service file", "err", err)
}
if err := execute("systemctl daemon-reload"); err != nil {
log.Fatalw("install service", "err", err)
}
if err := execute("systemctl enable sower"); err != nil {
log.Fatalw("install service", "err", err)
}
if err := execute("systemctl start sower"); err != nil {
log.Fatalw("install service", "err", err)
}
}
func uninstall() {
execute("systemctl stop sower")
execute("systemctl disable sower")
os.Remove(svcPath)
os.RemoveAll("/etc/sower")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
}
return nil
}
-188
View File
@@ -1,188 +0,0 @@
// +build windows
package conf
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/wweir/utils/log"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
const name = "sower"
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
var (
ConfigDir = ""
installCmd = false
uninstallFlag = false
)
func beforeInitFlag() {
flag.StringVar(&conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
flag.BoolVar(&installCmd, "install", false, "put any character to install as a service, eg: true")
}
func afterInitFlag() {
switch {
case installCmd:
install()
case uninstallFlag:
uninstall()
default:
runAsService()
return
}
os.Exit(0)
}
func runAsService() {
os.Chdir(filepath.Dir(os.Args[0]))
if active, err := svc.IsAnInteractiveSession(); err != nil {
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
} else if !active {
go func() {
elog, err := eventlog.Open(name)
if err != nil {
log.Fatalw("install service", "err", err)
}
defer elog.Close()
if err := svc.Run(name, &myservice{}); err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
log.Fatalw("install service", "err", err)
}
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
os.Exit(0)
}()
}
}
func install() {
mgrDo(func(m *mgr.Mgr) error {
s, err := m.OpenService(name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", name)
}
s, err = m.CreateService(name, execFile, mgr.Config{
DisplayName: "Sower Proxy",
StartType: windows.SERVICE_AUTO_START,
})
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
}
return s.Start()
})
}
func uninstall() {
serviceDo(func(s *mgr.Service) error {
err := s.Delete()
if err != nil {
return err
}
return eventlog.Remove(name)
})
}
func serviceDo(fn func(*mgr.Service) error) {
mgrDo(func(m *mgr.Mgr) error {
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
return fn(s)
})
}
func mgrDo(fn func(m *mgr.Mgr) error) {
m, err := mgr.Connect()
if err != nil {
log.Fatalw("install service", "err", err)
}
defer m.Disconnect()
if err := fn(m); err != nil {
log.Fatalw("install service", "err", err)
}
}
type myservice struct{}
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
elog, err := eventlog.Open(name)
if err != nil {
log.Errorw("install service", "err", err)
return
}
defer elog.Close()
elog.Info(1, strings.Join(args, "-"))
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
return
case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
case svc.Continue:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
default:
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
}
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
var cmds []string
for _, cmd := range strings.Split(cmd, " ") {
if cmd == "" {
continue
}
if strings.HasPrefix(cmd, "/") {
cmd = strings.Replace(cmd, "/", "-", 1)
}
cmds = append(cmds, cmd)
}
if len(cmds) != 0 {
return nil
}
command := exec.CommandContext(ctx, cmds[0], cmds[1:]...)
command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if out, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("cmd: %s, output: %s, err: %w", cmd, out, err)
}
return nil
}
-54
View File
@@ -1,54 +0,0 @@
password="" # sower password
[client]
address = "" # aa.bb.cc, socks5h://127.0.0.1:1080
http_proxy=":8080"
dns_serve_ip="" # eg: 127.0.0.1. keep empty to disable DNS solution
dns_upstream="" # keep empty to set via dhcp, not effective in any environment
[client.port_forward]
# eg: ":2222"="aa.bb.cc:22"
[client.router]
detect_level = 0 # [-4, 4], the bigger the harder to add
direct_list = [
"**.in-addr.arpa",
"imap.*.*",
"imap.*.*.*",
"smtp.*.*",
"smtp.*.*.*",
"pop.*.*",
"pop.*.*.*",
"**.cn",
]
proxy_list = [
"**.google.*",
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com",
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com",
"**.fbcdn.net",
"**.twitter.com",
"**.twimg.com",
"**.blogspot.com",
"**.appspot.com",
"**.wikipedia.org",
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.githubassets.com",
"*.github.*",
]
[server]
cert_email = "" # eg: user@aa.bb.cc
cert_file = "" # eg: /etc/ssl/server.crt
key_file = "" # eg: /etc/ssl/server.key
upstream = "" # eg: 127.0.0.1:8080
+27 -7
View File
@@ -1,14 +1,34 @@
module github.com/wweir/sower
go 1.14
go 1.17
require (
github.com/cristalhq/aconfig v0.16.8
github.com/cristalhq/aconfig/aconfighcl v0.16.1
github.com/cristalhq/aconfig/aconfigtoml v0.16.1
github.com/cristalhq/aconfig/aconfigyaml v0.16.1
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
github.com/libp2p/go-reuseport v0.0.1
github.com/miekg/dns v1.1.29
github.com/pelletier/go-toml v1.7.0
github.com/libp2p/go-reuseport v0.1.0
github.com/miekg/dns v1.1.46
github.com/oschwald/geoip2-golang v1.6.1
github.com/pkg/errors v0.9.1
github.com/wweir/utils v0.0.0-20200401081845-fc35af3737dc
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4
github.com/sower-proxy/conns v0.0.1
github.com/sower-proxy/deferlog v1.0.1
github.com/sower-proxy/mem v0.0.2
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
)
require (
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
+91 -73
View File
@@ -1,97 +1,115 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cristalhq/aconfig v0.16.1/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.16.8 h1:lg8i0XHgfhvsnjNM5q/ou6jIHDRXlbBybjRP9t2fWuw=
github.com/cristalhq/aconfig v0.16.8/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfighcl v0.16.1 h1:RiziovFKOwB6+H1LCD7DXxrFgoKDEJxxYLSsAOsj+vQ=
github.com/cristalhq/aconfig/aconfighcl v0.16.1/go.mod h1:YvZyVGtty/7yIdyOPPl68u7ENaUMq6ybZAph+n3g4II=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1 h1:q9DJfJqdLvZj7vL3bOIR6QM0ySpA0i6X8CZ8FNob6nc=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1/go.mod h1:VfRDnJBq09TZXiQdnnd4CBsNyj4s7AEDJlCxDSDt5DI=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1 h1:ghmzWFolCuja6BzBvHkNAn2Qsewu6xqdAXQyWnXjvZw=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1/go.mod h1:UgM0LkO4TKbC0s/oUP2p5POM6QiQwNvOFXostf6con0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw=
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/libp2p/go-reuseport v0.1.0 h1:0ooKOx2iwyIkf339WCZ2HN3ujTDbkK0PjC7JVoP1AiM=
github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU=
github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio=
github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oschwald/geoip2-golang v1.6.1 h1:GKxT3yaWWNXSb7vj6D7eoJBns+lGYgx08QO0UcNm0YY=
github.com/oschwald/geoip2-golang v1.6.1/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
github.com/sower-proxy/conns v0.0.1 h1:8tKWQR+nEEH0UBfFSz4M7HAhFFf/o9rQZ/6I0yWjXE8=
github.com/sower-proxy/conns v0.0.1/go.mod h1:jpifOR3jj9HFTE3wXd6sKdd778ZaRC/zoLKkdtvXNB8=
github.com/sower-proxy/deferlog v1.0.1 h1:ZRMpB3BOt0tpGiRIPKzg4/1kQ9UGZWgVM3sA1kAmW+c=
github.com/sower-proxy/deferlog v1.0.1/go.mod h1:wd8Yxz45ZbCgzXEafgDCj7XLlcqYUoEoLyiP1NQOUMk=
github.com/sower-proxy/mem v0.0.2 h1:Z7Oa3Jg4tK1QKIl9IZpqK1W5s43/VBKsu9uHmVyU0IE=
github.com/sower-proxy/mem v0.0.2/go.mod h1:B+TwQdp9ridXfW1D04Oxr81nPwnCACt4K7YKmk4kBgg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ulule/deepcopier v0.0.0-20200117111125-792cfb847af8 h1:iCslye9fWwp1ExDu06BcKM8/gjDRAlX9DBL+T8CjuWU=
github.com/ulule/deepcopier v0.0.0-20200117111125-792cfb847af8/go.mod h1:wUZg40sMUnY+1FU5F9rZwwCruLb8h1bHF8HzI09kgok=
github.com/wweir/utils v0.0.0-20200401081845-fc35af3737dc h1:XvNZju71btZ0/hammNDOu0Jqv7qi0kR0x9KaXYRKOAY=
github.com/wweir/utils v0.0.0-20200401081845-fc35af3737dc/go.mod h1:5PfQ9g8nWDRNkzu2gyjtFo/Xo6+s5ZCpulQADjvGz7M=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"flag"
"fmt"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/proxy"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
)
func main() {
client, server, password := conf.Init()
switch {
case server.Upstream != "":
proxy.StartServer(server.Upstream, password, conf.ConfigDir,
server.CertFile, server.KeyFile, server.CertEmail)
case client.Address != "":
route := &router.Route{
ProxyAddress: client.Address,
ProxyPassword: password,
DetectLevel: client.Router.DetectLevel,
DirectList: client.Router.DirectList,
ProxyList: client.Router.ProxyList,
PersistFn: conf.PersistRule,
}
if client.HTTPProxy != "" {
go proxy.StartHTTPProxy(client.HTTPProxy, client.Address,
[]byte(password), route.GenProxyCheck(true))
}
enableDNSSolution := client.DNSServeIP != ""
if enableDNSSolution {
transport.SetDNS(nil, client.DNSUpstream)
go proxy.StartDNS(client.DNSServeIP, client.DNSUpstream,
route.GenProxyCheck(false))
}
proxy.StartClient(client.Address, password,
client.DNSServeIP, enableDNSSolution,
client.PortForward, route.GenProxyCheck(true))
default:
fmt.Println()
flag.Usage()
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
var xid = make([]byte, 4)
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
func GetDefaultDNSServer() (string, error) {
func GetDNSServer() (string, error) {
iface, err := PickInternetInterface()
if err != nil {
return "", errors.Wrap(err, "pick interface")
+2 -2
View File
@@ -3,11 +3,11 @@ package dhcp_test
import (
"fmt"
"github.com/wweir/sower/dhcp"
"github.com/wweir/sower/pkg/dhcp"
)
func Example_dns() {
got, err := dhcp.GetDefaultDNSServer()
got, err := dhcp.GetDNSServer()
if err != nil {
panic(err)
}
@@ -3,7 +3,7 @@ package dhcp_test
import (
"fmt"
"github.com/wweir/sower/dhcp"
"github.com/wweir/sower/pkg/dhcp"
)
func Example_iface() {
View File
+147
View File
@@ -0,0 +1,147 @@
package suffixtree
import (
"runtime"
"strings"
)
type Node struct {
*node
sep string
}
type node struct {
secs []string
subNodes []*node
}
func NewNodeFromRules(rules ...string) *Node {
n := &Node{&node{}, "."}
for i := range rules {
n.Add(rules[i])
}
n.node = n.node.lite()
runtime.GC()
return n
}
func (n *node) lite() *node {
if n == nil {
return nil
}
lite := &node{
secs: make([]string, 0, len(n.secs)),
subNodes: make([]*node, 0, len(n.subNodes)),
}
lite.secs = append(lite.secs, n.secs...)
for i := range n.subNodes {
lite.subNodes = append(lite.subNodes, n.subNodes[i].lite())
}
return lite
}
func (n *Node) String() string {
return n.string("", " ")
}
func (n *node) string(prefix, indent string) (out string) {
if n == nil {
return
}
for key, val := range n.subNodes {
out += prefix + n.secs[key] + "\n" + val.string(prefix+indent, indent)
}
return
}
func (n *Node) trim(item string) string {
return strings.TrimSuffix(item, n.sep)
}
func (n *Node) Add(item string) {
n.add(strings.Split(n.trim(item), n.sep))
}
func (n *node) add(secs []string) {
length := len(secs)
switch length {
case 0:
case 1:
sec := secs[length-1]
switch sec {
case "", "*", "**":
n.secs = append([]string{sec}, n.secs...)
n.subNodes = append([]*node{nil}, n.subNodes...)
default:
n.secs = append(n.secs, sec)
n.subNodes = append(n.subNodes, nil)
}
default:
sec := secs[length-1]
if sec == "**" { // ** is only allowed in the last sec
sec = "*"
}
idx := n.index(sec)
if idx == -1 {
switch sec {
case "", "*", "**":
idx = 0
n.secs = append([]string{sec}, n.secs...)
n.subNodes = append([]*node{{}}, n.subNodes...)
default:
idx = len(n.secs)
n.secs = append(n.secs, sec)
n.subNodes = append(n.subNodes, &node{})
}
} else if n.subNodes[idx] == nil {
n.subNodes[idx] = &node{}
n.subNodes[idx].add([]string{""})
}
n.subNodes[idx].add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
if n == nil {
return false
}
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if n == nil {
return true
}
return n.index("") != -1 || n.index("**") != -1
}
if idx := n.index(secs[length-1]); idx >= 0 {
if n.subNodes[idx].matchSecs(secs[:length-1], false) {
return true
}
}
if idx := n.index("*"); idx >= 0 {
if n.subNodes[idx].matchSecs(secs[:length-1], true) {
return true
}
}
return n.index("**") >= 0
}
// index return the sec index in node, or -1 if not found
func (n *node) index(sec string) int {
if n == nil {
return -1
}
for s := range n.secs {
if n.secs[s] == sec {
return s
}
}
return -1
}
@@ -1,9 +1,9 @@
package util_test
package suffixtree_test
import (
"testing"
"github.com/wweir/sower/util"
"github.com/wweir/sower/pkg/suffixtree"
)
func TestNode_Match(t *testing.T) {
@@ -13,18 +13,18 @@ func TestNode_Match(t *testing.T) {
}
tests := []struct {
name string
node *util.Node
node *suffixtree.Node
tests []test
}{{
"simple",
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
suffixtree.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
[]test{
{"a.wweir.cc", true},
{"b.wweir.cc", true},
},
}, {
"parent",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -32,7 +32,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz1",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -41,7 +41,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz2",
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
suffixtree.NewNodeFromRules("a.*.cc", "c.wweir.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -50,7 +50,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz3",
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
suffixtree.NewNodeFromRules("*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -59,7 +59,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz4",
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
suffixtree.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -68,6 +68,12 @@ func TestNode_Match(t *testing.T) {
{"b.fuzz.com", false},
{"www.wweir.com", true},
},
}, {
"github",
suffixtree.NewNodeFromRules("**.github.com"),
[]test{
{"github.com", true},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
-118
View File
@@ -1,118 +0,0 @@
package proxy
import (
"net"
"strings"
"sync"
"time"
"github.com/miekg/dns"
"github.com/wweir/sower/dhcp"
"github.com/wweir/utils/log"
)
type msgCache struct {
*dns.Msg
time.Time
}
var cache sync.Map
func StartDNS(redirectIP, relayServer string, shouldProxy func(string) bool) {
serveIP := net.ParseIP(redirectIP)
if redirectIP == "" || serveIP.String() != redirectIP {
log.Fatalw("invalid listen ip", "ip", redirectIP)
}
var err error
if relayServer, err = pickRelayAddr(relayServer); err != nil {
log.Fatalw("pick upstream dns server", "err", err)
}
log.Infow("upstream dns", "addr", relayServer)
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
// *Msg r has an TSIG record and it was validated
if r.IsTsig() != nil && w.TsigStatus() == nil {
lastTsig := r.Extra[len(r.Extra)-1].(*dns.TSIG)
r.SetTsig(lastTsig.Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
}
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
if len(r.Question) == 0 {
return
}
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, ':'); idx > 0 {
domain = domain[:idx] // trim port
}
if shouldProxy(domain) {
w.WriteMsg(localA(r, domain, serveIP))
} else if val, ok := cache.Load(domain); ok && val.(*msgCache).After(time.Now()) {
msg := val.(*msgCache)
for _, rr := range msg.Answer {
rr.Header().Ttl = uint32(time.Until(msg.Time).Seconds())
}
msg.SetReply(r)
w.WriteMsg(msg.Msg)
} else if msg, err := dns.Exchange(r, relayServer); err != nil || msg == nil {
cache.Delete(domain)
if server, err := pickRelayAddr(relayServer); err != nil {
log.Errorw("detect upstream dns", "err", err)
} else if relayServer != server {
relayServer = server
log.Infow("detect upstream dns", "addr", relayServer)
}
} else {
cache.Delete(domain)
if len(msg.Answer) != 0 {
deadline := time.Now().Add(
time.Duration(msg.Answer[0].Header().Ttl) * time.Second)
cache.Store(domain, &msgCache{
Msg: msg,
Time: deadline,
})
}
w.WriteMsg(msg)
}
})
server := &dns.Server{Addr: net.JoinHostPort(redirectIP, "53"), Net: "udp"}
log.Infow("start dns", "addr", server.Addr)
log.Fatalw("dns serve fail", "err", server.ListenAndServe())
}
func pickRelayAddr(relayServer string) (_ string, err error) {
if relayServer == "" {
if relayServer, err = dhcp.GetDefaultDNSServer(); err != nil {
return "", err
}
}
if _, _, err := net.SplitHostPort(relayServer); err != nil {
return net.JoinHostPort(relayServer, "53"), nil
}
return relayServer, nil
}
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
if localIP.To4() != nil {
m.Answer = []dns.RR{&dns.A{
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},
AAAA: localIP,
}}
}
return m
}
-84
View File
@@ -1,84 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
)
// StartHTTPProxy start http reverse proxy.
// The httputil.ReverseProxy do not supply enough support for https request.
func StartHTTPProxy(httpProxyAddr, serverAddr string, password []byte,
shouldProxy func(string) bool) {
proxy := httputil.ReverseProxy{
Director: func(r *http.Request) {},
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return transport.Dial(serverAddr, func(host string) (string, []byte) {
if shouldProxy(host) {
return httpProxyAddr, password
}
return "", nil
})
},
},
}
srv := &http.Server{
Addr: httpProxyAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
httpsProxy(w, r, serverAddr, password, shouldProxy)
} else {
proxy.ServeHTTP(w, r)
}
}),
// Disable HTTP/2.
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
IdleTimeout: 90 * time.Second,
}
log.Infow("start sower http proxy", "http_proxy", httpProxyAddr)
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
}
func httpsProxy(w http.ResponseWriter, r *http.Request,
serverAddr string, password []byte, shouldProxy func(string) bool) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
defer conn.Close()
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
target, _ := util.WithDefaultPort(r.Host, "443")
rc, err := transport.Dial(target, func(host string) (string, []byte) {
if shouldProxy(host) {
return serverAddr, password
}
return "", nil
})
if err != nil {
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
conn.Close()
return
}
defer rc.Close()
relay(conn, rc)
}
-141
View File
@@ -1,141 +0,0 @@
package proxy
import (
"crypto/tls"
"net"
"net/http"
"github.com/wweir/sower/transport"
"github.com/wweir/utils/log"
"golang.org/x/crypto/acme/autocert"
)
func StartClient(serverAddr, password, serveIP string, enableDNS bool,
forwards map[string]string, shouldProxy func(string) bool) {
passwordData := []byte(password)
relayToRemote := func(lnAddr, target string,
parseFn func(net.Conn) (net.Conn, string, error),
shouldProxy func(string) bool) {
ln, err := net.Listen("tcp", lnAddr)
if err != nil {
log.Fatalw("tcp listen", "port", lnAddr, "err", err)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Errorw("tcp accept", "port", lnAddr, "err", err)
continue
}
go func(conn net.Conn) {
defer conn.Close()
if parseFn != nil {
if conn, target, err = parseFn(conn); err != nil {
log.Warnw("parse target", "err", err)
return
}
}
rc, err := transport.Dial(target, func(domain string) (string, []byte) {
if shouldProxy(domain) {
return serverAddr, passwordData
}
return "", nil
})
if err != nil {
host, _, _ := net.SplitHostPort(target)
log.Warnw("dial", "addr", target, "proxy", shouldProxy(host), "err", err)
return
}
defer rc.Close()
relay(conn, rc)
}(conn)
}
}
for from, to := range forwards {
go relayToRemote(from, to, nil, func(string) bool { return true })
}
if enableDNS {
go relayToRemote(net.JoinHostPort(serveIP, "http"), "", ParseHTTP, shouldProxy)
go relayToRemote(net.JoinHostPort(serveIP, "https"), "", ParseHTTPS, shouldProxy)
}
log.Infow("start sower client", "dns solution", enableDNS, "forwards", forwards)
select {}
}
func StartServer(relayTarget, password, cacheDir, certFile, keyFile, email string) {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Email: email,
Cache: autocert.DirCache(cacheDir),
}
tlsConf := &tls.Config{
GetCertificate: certManager.GetCertificate,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", "h2"},
}
if certFile != "" && keyFile != "" {
if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
log.Fatalw("load certificate", "cert", certFile, "key", keyFile, "err", err)
} else {
tlsConf.GetCertificate = nil
tlsConf.Certificates = []tls.Certificate{cert}
}
}
// Try to redirect 80 to 443
go http.ListenAndServe(":80", certManager.HTTPHandler(http.HandlerFunc(
func(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)
})))
log.Infow("start sower server", "relay_to", relayTarget)
ln, err := tls.Listen("tcp", ":443", tlsConf)
if err != nil {
log.Fatalw("tcp listen", "err", err)
}
passwordData := []byte(password)
for {
conn, err := ln.Accept()
if err != nil {
log.Errorw("tcp accept", "err", err)
continue
}
go func(conn net.Conn) {
defer conn.Close()
conn, target := transport.ParseProxyConn(conn, passwordData)
if target == "" {
target = relayTarget
}
rc, err := net.Dial("tcp", target)
if err != nil {
log.Errorw("tcp dial", "addr", target, "err", err)
return
}
defer rc.Close()
relay(conn, rc)
}(conn)
}
}
-64
View File
@@ -1,64 +0,0 @@
package proxy
import (
"bufio"
"crypto/tls"
"io"
"net"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/wweir/sower/util"
)
func ParseHTTP(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
if err != nil {
return teeConn, "", err
}
resp.Host, _ = util.WithDefaultPort(resp.Host, "80")
return teeConn, resp.Host, nil
}
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
var domain string
tls.Server(teeConn, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
},
}).Handshake()
domain, _ = util.WithDefaultPort(domain, "443")
return teeConn, domain, nil
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
wg.Add(2)
go redirect(conn2, conn1, wg, exitFlag)
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
io.Copy(dst, src)
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetDeadline(now)
dst.SetDeadline(now)
}
wg.Done()
}
+54
View File
@@ -0,0 +1,54 @@
package router
import (
"context"
"net"
"time"
"github.com/sower-proxy/deferlog/log"
)
func (r *Router) localSite(domain string) bool {
// parse domain to IP
ip := net.ParseIP(domain)
if ip == nil {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
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")
return false
}
ip = ips[0]
}
// CIDR match
for _, cidr := range r.country.cidrs {
if cidr.Contains(ip) {
return true
}
}
// MMDB match CN
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")
return false
}
if city.Country.IsoCode == "CN" {
return true
}
}
return false
}
+93
View File
@@ -0,0 +1,93 @@
package router
import (
"net"
"time"
"github.com/miekg/dns"
"github.com/sower-proxy/deferlog/log"
)
func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
// https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
if len(req.Question) == 0 {
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeFormatError))
return
}
domain := req.Question[0].Name
// 1. rule_based( block > direct > proxy )
switch {
case r.blockRule.Match(domain):
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeNameError))
return
case r.directRule.Match(domain):
case r.proxyRule.Match(domain):
_ = w.WriteMsg(r.dnsProxyA(domain, r.dns.serveIP, req))
return
}
// 2. direct with cache, do not fallback to proxy to avoid side-effect
c := &dnsCache{Router: r, Req: req}
if err := r.dns.cache.Remember(c, req.Question[0].String()); err != nil {
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeServerFailure))
return
}
c.Resp.SetReply(req)
c.Resp.Compress = true
_ = w.WriteMsg(c.Resp)
}
func (r *Router) dnsFail(req *dns.Msg, rcode int) *dns.Msg {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeServerFailure)
return m
}
func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(req)
if localIP.To4() != nil {
m.Answer = []dns.RR{&dns.A{
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},
AAAA: localIP,
}}
}
return m
}
type dnsCache struct {
*Router
Req, Resp *dns.Msg
}
func (r *dnsCache) Fulfill(question string) (err error) {
conn := <-r.dns.connCh
var rtt time.Duration
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
if err != nil {
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
}
log.DebugWarn(err).
Dur("rtt", rtt).
Str("question", question).
Msg("exchange dns record")
select {
case r.dns.connCh <- conn:
default:
conn.Close()
}
return err
}
-124
View File
@@ -1,124 +0,0 @@
package router
import (
"bytes"
"crypto/tls"
"encoding/binary"
"io"
"net"
"strconv"
"time"
)
// Port ==========================
type Port uint16
const (
HTTP Port = 80
HTTPS Port = 443
)
// Ping try connect to a http(s) server with domain though the http addr
func (p Port) Ping(domain string, dial func(string) (net.Conn, error)) error {
conn, err := dial(net.JoinHostPort(domain, p.String()))
if err != nil {
return err
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(5 * time.Second))
if _, err := conn.Write(p.PingMsg(domain)); err != nil {
return err
}
// err -> nil: read something succ
// err -> io.EOF: no such domain or connection refused
// err -> timeout: tcp package has been dropped
if _, err = conn.Read(make([]byte, 10)); err == io.EOF {
err = nil
}
return err
}
func (p Port) String() string {
return strconv.Itoa(int(p))
}
func (p Port) PingMsg(domain string) []byte {
switch p {
case HTTP:
return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
case HTTPS:
return NewClientHelloSNIMsg(domain)
default:
panic("invalid port")
}
}
// SNI ==========================
type clientHelloSNI struct {
ContentType uint8
Version uint16
Length uint16
handshakeProtocol
}
type handshakeProtocol struct {
HandshakeType uint8
LengthExpand uint8
Length uint16
Version uint16
Random [32]byte
SessionIDLength uint8
CipherSuitesLength uint16
CipherSuite uint16
CompressionMethodsLength uint8
CompressionMethod uint8
ExtensionsLength uint16
extensionServerName
}
type extensionServerName struct {
Type uint16
Length uint16
serverNameIndicationExtension
}
type serverNameIndicationExtension struct {
ServerNameListlength uint16
ServerNameType uint8
ServerNamelength uint16
// ServerName []byte // Disable for fix length
}
func NewClientHelloSNIMsg(domain string) []byte {
length := uint16(len(domain))
msg := &clientHelloSNI{
ContentType: 0x16, // Content Type: Handshake (22)
Version: 0x0301, // Version: TLS 1.0
Length: length + 56,
handshakeProtocol: handshakeProtocol{
HandshakeType: 0x01, // Handshake Type: Client Hello (1)
Length: length + 52,
Version: 0x0303, // Version: TLS 1.2 (0x0303)
Random: [32]byte{}, // [32]byte{},
SessionIDLength: 0x0, // Session ID Length: 0
CipherSuitesLength: 2, // Cipher Suites Length: 84
CipherSuite: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
CompressionMethodsLength: 1, // Compression Methods Length: 1
CompressionMethod: 0x00, // Compression null
ExtensionsLength: length + 9,
extensionServerName: extensionServerName{
Type: 0x0000, // Type: server_name (0)
Length: length + 5,
serverNameIndicationExtension: serverNameIndicationExtension{
ServerNameListlength: length + 3,
ServerNameType: 0x00, // Server Name Type: host_name (0)
ServerNamelength: length,
},
},
},
}
buf := bytes.NewBuffer(make([]byte, 0, length+61))
binary.Write(buf, binary.BigEndian, msg)
buf.WriteString(domain)
return buf.Bytes()
}
-72
View File
@@ -1,72 +0,0 @@
// +build debug
// go test -v -tags debug
package router_test
import (
"crypto/tls"
"net"
"strconv"
"testing"
"time"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
)
var (
proxyAddr string = "socks5://127.0.0.1:1080"
password []byte = nil
)
func TestPort_Ping(t *testing.T) {
direct := func(addr string) (net.Conn, error) {
return net.DialTimeout("tcp", addr, 5*time.Second)
}
proxy := func(addr string) (net.Conn, error) {
conn, err := tls.Dial("tcp", proxyAddr, &tls.Config{})
if err != nil {
return nil, err
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
return transport.ToProxyConn(conn, host, uint16(p), password)
}
type args struct {
domain string
dial func(string) (net.Conn, error)
}
tests := []struct {
name string
p router.Port
args args
wantErr bool
}{
{"", router.HTTP, args{"baidu.com", direct}, false},
{"", router.HTTP, args{"baidu.com", proxy}, false},
{"", router.HTTPS, args{"baidu.com", direct}, false},
{"", router.HTTPS, args{"baidu.com", proxy}, false},
{"", router.HTTP, args{"google.com", direct}, true},
{"", router.HTTP, args{"google.com", proxy}, false},
{"", router.HTTPS, args{"google.com", direct}, true},
{"", router.HTTPS, args{"google.com", proxy}, false},
{"", router.HTTP, args{"smtp.163.com", direct}, true},
{"", router.HTTP, args{"smtp.163.com", proxy}, true},
{"", router.HTTPS, args{"smtp.163.com", direct}, true},
{"", router.HTTPS, args{"smtp.163.com", proxy}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.p.Ping(tt.args.domain, tt.args.dial); (err != nil) != tt.wantErr {
t.Errorf("Port.Ping() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
+54
View File
@@ -0,0 +1,54 @@
package router
import (
"net/http"
"sync"
"time"
"github.com/sower-proxy/deferlog/log"
)
var pingClient = http.Client{
Timeout: 2 * time.Second,
}
func (r *Router) isAccess(domain string, port uint16) bool {
switch port {
case 80:
case 443:
default:
return false
}
p := &ping{}
_ = r.accessCache.Remember(p, domain)
return p.isAccess
}
type ping struct {
isAccess bool
}
func (p *ping) Fulfill(key string) error {
wg := sync.WaitGroup{}
wg.Add(2)
var err80, err443 error
go func() {
_, err80 = pingClient.Head("http://" + key)
wg.Done()
}()
go func() {
_, err443 = pingClient.Head("https://" + key)
wg.Done()
}()
wg.Wait()
if err80 != nil && err443 != nil {
log.Warn().
Errs("errs", []error{err80, err443}).
Msg("Failed to ping")
}
p.isAccess = (err80 == nil && err443 == nil)
return nil
}
+124 -117
View File
@@ -2,142 +2,149 @@ package router
import (
"net"
"strings"
"sync"
"sync/atomic"
"strconv"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
"github.com/wweir/utils/mem"
"github.com/miekg/dns"
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/sower-proxy/mem"
"github.com/wweir/sower/pkg/dhcp"
"github.com/wweir/sower/pkg/suffixtree"
)
// Route implement a router for each request
type Route struct {
once sync.Once
cache *mem.Cache
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
accessCache *mem.Cache
ProxyAddress string
ProxyPassword string
password []byte
dns struct {
dns.Client
fallbackDNS string
serveIP net.IP
connCh chan *dns.Conn
cache *mem.Cache
}
DetectLevel int // dynamic detect proxy level
DirectList []string
directRule *util.Node
ProxyList []string
proxyRule *util.Node
PersistFn func(string)
country struct {
*geoip2.Reader
cidrs []*net.IPNet
}
}
// ShouldProxy check if the domain shoule request though proxy
func (r *Route) GenProxyCheck(sync bool) func(string) bool {
r.once.Do(func() {
r.cache = mem.New(4 * time.Hour)
r.password = []byte(r.ProxyPassword)
r.directRule = util.NewNodeFromRules(r.DirectList...)
r.proxyRule = util.NewNodeFromRules(r.ProxyList...)
})
detect := func(domain string) bool {
go r.cache.Remember(r, domain)
return true
func NewRouter(serveIP, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
r := Router{
ProxyDial: proxyDial,
accessCache: mem.New(time.Hour), // TODO: config
}
if sync {
detect = func(domain string) bool {
r.cache.Remember(r, domain)
if r.proxyRule.Match(domain) {
return true
r.dns.serveIP = net.ParseIP(serveIP)
r.dns.fallbackDNS = fallbackDNS
r.dns.connCh = make(chan *dns.Conn, 1)
r.dns.cache = mem.New(5 * time.Minute) // Tll: 10 minutes
go r.dialDNSConn()
var err error
r.country.Reader, err = geoip2.Open(mmdbFile)
log.InfoWarn(err).Str("file", mmdbFile).Msg("open geoip2 db")
return &r
}
func (r *Router) SetBlockRules(blockList []string) {
r.blockRule = suffixtree.NewNodeFromRules(blockList...)
}
func (r *Router) SetDirectRules(directList []string) {
r.directRule = suffixtree.NewNodeFromRules(directList...)
}
func (r *Router) SetProxyRules(proxyList []string) {
r.proxyRule = suffixtree.NewNodeFromRules(proxyList...)
}
func (r *Router) SetCountryCIDRs(directCIDRs []string) {
r.country.cidrs = make([]*net.IPNet, 0, len(directCIDRs))
for _, cidr := range directCIDRs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
log.Error().Err(err).Msg("Failed to parse CIDR")
}
r.country.cidrs = append(r.country.cidrs, ipnet)
}
}
func (r *Router) dialDNSConn() {
for {
server, err := dhcp.GetDNSServer()
log.Err(err).
Str("DNS", server).
Str("fallback", r.dns.fallbackDNS).
Msg("get DNS server")
if server == "" || server == r.dns.serveIP.String() {
server = r.dns.fallbackDNS
}
for {
conn, err := dns.DialTimeout("udp", net.JoinHostPort(server, "53"), time.Second)
if err != nil {
log.Error().Err(err).Str("ip", server).Msg("dial dns server")
break
}
return !r.directRule.Match(domain)
}
}
return func(domain string) bool {
domain = strings.TrimSuffix(domain, ".")
// break deadlook, for wildcard
if sepCount := strings.Count(domain, "."); sepCount == 0 || sepCount >= 5 {
return false
r.dns.connCh <- conn
}
if r.proxyRule.Match(domain) {
return true
}
if r.directRule.Match(domain) {
return false
}
return detect(domain)
}
}
// Get implement for cache
func (r *Route) Get(key interface{}) (err error) {
domain := key.(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")
}()
httpScore, httpsScore := r.detect(domain)
log.Infow("detect", "domain", domain, "http", httpScore, "https", httpsScore)
addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10))
if httpScore+httpsScore >= r.DetectLevel {
r.directRule.Add(domain)
if r.PersistFn != nil {
r.PersistFn(domain)
}
// 1. rule_based( block > direct > proxy )
// 2. detect_based( CN IP || access site )
// 3. fallback( proxy )
switch {
case r.blockRule.Match(domain):
return nil
case r.directRule.Match(domain):
return r.DirectHandle(conn, addr)
case r.proxyRule.Match(domain):
return r.ProxyHandle(conn, domain, port)
case r.localSite(domain), r.isAccess(domain, port):
return r.DirectHandle(conn, addr)
default:
return r.ProxyHandle(conn, domain, port)
}
}
func (r *Router) ProxyHandle(conn net.Conn, domain string, port uint16) error {
start := time.Now()
rc, err := r.ProxyDial("tcp", domain, port)
if err != nil {
return errors.Wrapf(err, "proxy dial (%s:%d), spend (%s)", domain, port, time.Since(start))
}
defer rc.Close()
relay.Relay(conn, rc)
return nil
}
// detect and caculate direct connection and proxy connection score
func (r *Route) detect(domain string) (http, https int) {
wg := sync.WaitGroup{}
httpScore, httpsScore := new(int32), new(int32)
for _, ping := range [...]struct {
shouldProxy bool
port Port
}{
{shouldProxy: true, port: HTTP},
{shouldProxy: true, port: HTTPS},
{shouldProxy: false, port: HTTP},
{shouldProxy: false, port: HTTPS},
} {
wg.Add(1)
go func(shouldProxy bool, port Port) {
defer wg.Done()
if err := port.Ping(domain, func(domain string) (net.Conn, error) {
return transport.Dial(domain,
func(domain string) (proxyAddr string, password []byte) {
if shouldProxy {
return r.ProxyAddress, r.password
}
return "", nil
})
}); err != nil {
log.Warnw("sower dial", "proxy", shouldProxy, "host", domain, "port", port, "err", err)
return
}
switch {
case shouldProxy && port == HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
atomic.AddInt32(httpScore, -1)
}
case shouldProxy && port == HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
atomic.AddInt32(httpsScore, -1)
}
case !shouldProxy && port == HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
atomic.AddInt32(httpScore, 1)
}
case !shouldProxy && port == HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
atomic.AddInt32(httpsScore, 1)
}
}
}(ping.shouldProxy, ping.port)
}
wg.Wait()
return int(*httpScore), int(*httpsScore)
func (r *Router) DirectHandle(conn net.Conn, addr string) error {
dur, err := relay.RelayTo(conn, addr)
return errors.Wrapf(err, "spend (%s)", dur)
}
+215
View File
@@ -0,0 +1,215 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="351px" height="491px" viewBox="-0.5 -0.5 351 491" content="&lt;mxfile&gt;&lt;diagram id=&quot;u2y3ysnPqCVZKoHCRF4D&quot; name=&quot;第 1 页&quot;&gt;3VlNc9owEP01HJPxB/7gGCBtD2mbGdppcxRY2GpsyyOLAv31XSEZW5ZJKHEIzSWR1itZem/1dmUG7iTbfGSoSD7TCKcDx4o2A3c6cJyRH8JfYdhKQxAE0hAzEkmTXRtm5A9WRktZVyTCpebIKU05KXTjguY5XnDNhhija91tSVP9rQWKsWGYLVBqWn+QiCfSGjpBbf+ESZxUb7b9kXySocpZ7aRMUETXDZN7O3AnjFIuW9lmglOBXYWLHPfhwNP9whjO+TEDxDMx4jdKV2pzamF8W+2W0VUeYTHAGrjjdUI4nhVoIZ6ugV6wJTxLoWdDU02HGcebg2uy9zuFCME0w5xtwUUNcDwFjooOO1D9dY21W/kkDZyHI2VEit94P3cNATQUCt2IDA1APn37dj8zUMERhITqUsYTGtMcpbe1dazjVvvcUVootH5hzrcqvtGKUx1LwIttf4rx117VfWg+m27U5LK3Vb0DgMPy6Yot1PoVzxyxGCsvV5rEzp7kieEUcfJbPwwvwdw2QS/p4rH0wLYqSs4wyt6EgA3hEn/HU92HCnFo1/CLzino9we1GnpPCby1PkqhfpRcv3VCJP1qVE3YDWNo23ArhEN5+D2u1/2emn85Yx0N+z0eFyD+29If/J/0e/556N8L72vRHxj6EBEmMjvYSEl4Z3TcoTmUHRqjKCVxDu0FkIMZGESiIpDYb9SDjESRDB5ckj9ovptP0KoggMm98cCbdhPtH8x+qhJRE2rJvisFWteW7QcapldqplNDonKhy2WJeYuefyXErBpKugZAHT8FLMZz0Yr5DqS2ha1SKN1gPXkkKkDMJY1zVGJhgEMrmOmhBuk4ggYxh2uQlnAObbMG2fs0axDb6qEGscO3kLujlKv3GuGAojg6/k4b1gPKdYq4jN5XbjkXQ6OzMeR2q010cSrhDI9UibAHkQguViNUGmxeLV6n4PnXOsWzPI0vz23djp9RoZb/i+uasCOw5bXHR5kI13xein+wKBhIYrxbXMHoZnsZFU9Vl7244Lmyrl3bt/WKZ/iyiqdaUGtELwWQbzA3Fp+VdiWQRh04fQdzafBVJqgQzVWW3iw4bVKzo/GeQl1LqKBoTjmnWQd3nLY0DYqnlOR4sv/u9WTWOF7o2mK/v1g0P8l06Fz7vnFSMWR+kTprfj4+PYvOPWYEtiiO2vEpe3QuwXxe4ZxX+kDgXpby2mZNMf0yq+4fgotE/M2FUFjLlK7Nc30J+ludjV4EeBiEoQb6Jd04RwZfX4Ei9j7k1W1H+xnltXp1A9klehTBDwBhA1fYEX+9CBc6XEpJFlOXnNFHgDsV/E1zmuOe0lnru5gdmnj7HXi3xfEkvG0Db6X77xlxN/SeRTzoB3Ho1r+fSaWpf4R0b/8C&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="0" y="0" width="350" height="490" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 170 240 L 170 156.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 151.12 L 173.5 158.12 L 170 156.37 L 166.5 158.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 195px; margin-left: 170px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
HTTPS
</div>
</div>
</div>
</foreignObject>
<text x="170" y="198" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
HTTPS
</text>
</switch>
</g>
<path d="M 100 240 L 100 190 L 36.37 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 31.12 190 L 38.12 186.5 L 36.37 190 L 38.12 193.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 190px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
socks5 upstream
</div>
</div>
</div>
</foreignObject>
<text x="90" y="193" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
socks5 upstream
</text>
</switch>
</g>
<path d="M 240 240 L 240 190 L 303.63 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 308.88 190 L 301.88 193.5 L 303.63 190 L 301.88 186.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 192px; margin-left: 252px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
direct visit
</div>
</div>
</div>
</foreignObject>
<text x="252" y="195" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
direct visit
</text>
</switch>
</g>
<rect x="30" y="240" width="280" height="100" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 290px; margin-left: 31px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
sower
<br/>
<br/>
rules and detect based router
</div>
</div>
</div>
</foreignObject>
<text x="170" y="294" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
sower...
</text>
</switch>
</g>
<path d="M 170 70 L 170 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 31.12 L 173.5 38.12 L 170 36.37 L 166.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 240 70 L 240 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 240 31.12 L 243.5 38.12 L 240 36.37 L 236.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="30" y="70" width="280" height="80" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 110px; margin-left: 31px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
sowerd
</div>
</div>
</div>
</foreignObject>
<text x="170" y="114" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
sowerd
</text>
</switch>
</g>
<path d="M 255 390 L 255 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 357px; margin-left: 251px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
socks5 intelligent proxy
</div>
</div>
</div>
</foreignObject>
<text x="251" y="360" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
socks5 intelligent proxy
</text>
</switch>
</g>
<ellipse cx="255" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 255 405 L 255 430 M 255 410 L 240 410 M 255 410 L 270 410 M 255 430 L 240 450 M 255 430 L 270 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 255px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Browser  Users
</div>
</div>
</div>
</foreignObject>
<text x="255" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Brows...
</text>
</switch>
</g>
<path d="M 85 390 L 85 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 360px; margin-left: 91px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
DNS route the net flow
</div>
</div>
</div>
</foreignObject>
<text x="91" y="363" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
DNS route the net flow
</text>
</switch>
</g>
<ellipse cx="85" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 85 405 L 85 430 M 85 410 L 70 410 M 85 410 L 100 410 M 85 430 L 70 450 M 85 430 L 100 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 85px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Other Users
</div>
</div>
</div>
</foreignObject>
<text x="85" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Other...
</text>
</switch>
</g>
<rect x="210" y="10" width="60" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 240px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
fake site
</div>
</div>
</div>
</foreignObject>
<text x="240" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
fake site
</text>
</switch>
</g>
<rect x="135" y="10" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 170px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
target site
</div>
</div>
</div>
</foreignObject>
<text x="170" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
target site
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

-68
View File
@@ -1,68 +0,0 @@
package transport
import (
"crypto/md5"
"encoding/binary"
"io"
"net"
"strconv"
"github.com/wweir/sower/util"
)
// checksum(>=0x80) + port + target_length + target + data
// data(HTTP, first byte < 0x7F)
type head struct {
Checksum byte
Port uint16
AddrLen uint8
}
func ParseProxyConn(conn net.Conn, password []byte) (net.Conn, string) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
h := &head{}
if err := binary.Read(teeConn, binary.BigEndian, h); err != nil || h.Checksum < 0x80 {
return teeConn, ""
}
buf := make([]byte, int(h.AddrLen))
if _, err := io.ReadFull(teeConn, buf); err != nil {
return teeConn, ""
}
if h.Checksum != sumChecksum(buf, password) {
return teeConn, ""
}
teeConn.Reset()
return teeConn, net.JoinHostPort(string(buf), strconv.Itoa(int(h.Port)))
}
func ToProxyConn(conn net.Conn, tgtHost string, tgtPort uint16, password []byte) (net.Conn, error) {
h := &head{
Checksum: sumChecksum([]byte(tgtHost), password),
Port: tgtPort,
AddrLen: uint8(len(tgtHost)),
}
if err := binary.Write(conn, binary.BigEndian, h); err != nil {
conn.Close()
return nil, err
}
var data = []byte(tgtHost)
var err error
for n, nn := 0, 0; nn < len(data); nn += n {
if n, err = conn.Write(data[nn:]); err != nil {
conn.Close()
return nil, err
}
}
return conn, nil
}
func sumChecksum(target, password []byte) byte {
return md5.Sum(append(target, password...))[0] | 0x80
}
-125
View File
@@ -1,125 +0,0 @@
package transport
import (
"encoding/binary"
"fmt"
"io"
"net"
"strings"
)
func IsSocks5Schema(addr string) (string, bool) {
if strings.HasPrefix(addr, "socks5://") {
return strings.TrimPrefix(addr, "socks5://"), true
}
if strings.HasPrefix(addr, "socks5h://") {
return strings.TrimPrefix(addr, "socks5h://"), true
}
return addr, false
}
func ToSocks5(c net.Conn, host string, port uint16) (net.Conn, error) {
return &conn{
init: make(chan struct{}),
Conn: c,
domain: host,
port: port,
}, nil
}
type conn struct {
init chan struct{}
domain string
port uint16
net.Conn
}
func (c *conn) Read(b []byte) (n int, err error) {
<-c.init
return c.Conn.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
select {
case <-c.init:
return c.Conn.Write(b)
default:
}
{
req := &authReq{
VER: 5,
NMETHODS: 1,
METHODS: [1]byte{0}, // NO AUTHENTICATION REQUIRED
}
if err := binary.Write(c.Conn, binary.BigEndian, req); err != nil {
return 0, err
}
}
{
resp := &authResp{}
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
return 0, err
}
}
{
portBuf := make([]byte, 2)
binary.BigEndian.PutUint16(portBuf, c.port)
req := &request{
req: req{
VER: 5, // socks5
CMD: 1, // CONNECT
RSV: 0, // RESERVED
ATYP: 3, // DOMAINNAME
},
DST_ADDR: append([]byte{byte(len(c.domain))}, []byte(c.domain)...),
DST_PORT: portBuf,
}
if _, err := c.Conn.Write(req.Bytes()); err != nil {
return 0, err
}
}
{
resp := &response{}
if err := binary.Read(c.Conn, binary.BigEndian, &(resp.resp)); err != nil {
return 0, err
}
switch resp.REP {
case 0x00:
default:
return 0, fmt.Errorf("socks5 handshake fail, return code: %d", resp.REP)
}
switch resp.ATYP {
case 0x01: // IPv4
resp.DST_ADDR = make([]byte, net.IPv4len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
case 0x03: // domain name
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[:1]); err != nil {
return 0, err
}
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[1:1+int(resp.DST_ADDR[0])]); err != nil {
return 0, err
}
case 0x04:
resp.DST_ADDR = make([]byte, net.IPv6len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
}
resp.DST_PORT = make([]byte, 2)
if _, err := io.ReadFull(c.Conn, resp.DST_PORT); err != nil {
return 0, err
}
}
close(c.init)
return c.Conn.Write(b)
}
+125
View File
@@ -0,0 +1,125 @@
package socks5
import (
"encoding/binary"
"io"
"net"
)
// https://tools.ietf.org/html/rfc1928
// 1. client send auth request
type authReq struct {
VER byte
NMETHODS uint8
METHODS []byte
}
func (req *authReq) Fulfill(r io.Reader) error {
buf := make([]byte, 2)
if n, err := r.Read(buf); err != nil || n != 2 {
return err
}
req.VER = buf[0]
req.NMETHODS = buf[1]
req.METHODS = make([]byte, int(req.NMETHODS))
if n, err := r.Read(req.METHODS); err != nil || n != len(req.METHODS) {
return err
}
return nil
}
func (r *authReq) IsValid() bool {
return r.VER == 5 && r.METHODS[0] == 0
}
// 2. server response auth request
type authResp struct {
VER byte
METHOD byte
}
// 3. client request with target address
type reqHead struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *reqHead) IsValid() bool {
return r.VER == 5 && r.CMD == 1
}
// 4. server response with the address that assigned to connect to target address
type respHead struct {
VER byte
REP byte
RSV byte
ATYP byte
BND_ADDR [net.IPv4len]byte
BND_PORT uint16
}
type addrType interface {
Fulfill(r io.Reader) error
Addr() (domain string, port uint16)
}
// ATYP:
// 0x01 -> net.IPv4len
// 0x03 -> first byte is length
// 0x04 -> net.IPv6len
type addrTypeIPv4 struct {
DST_ADDR [4]byte
DST_PORT uint16
}
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
}
type addrTypeDomain struct {
DST_ADDR_LEN uint8
DST_ADDR []byte
DST_PORT uint16
}
func (a *addrTypeDomain) Fulfill(r io.Reader) error {
buf := make([]byte, 1)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
a.DST_ADDR_LEN = uint8(buf[0])
buf = make([]byte, a.DST_ADDR_LEN+2)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
a.DST_ADDR = buf[:int(a.DST_ADDR_LEN)]
a.DST_PORT = binary.BigEndian.Uint16(buf[int(a.DST_ADDR_LEN):])
return nil
}
func (a *addrTypeDomain) Addr() (string, uint16) {
return string(a.DST_ADDR[:]), a.DST_PORT
}
type addrTypeIPv6 struct {
DST_ADDR [16]byte
DST_PORT uint16
}
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
}
+113
View File
@@ -0,0 +1,113 @@
package socks5
import (
"bytes"
"encoding/binary"
"net"
"strconv"
"github.com/pkg/errors"
)
type AddrHead struct {
addrType
}
func (h *AddrHead) Network() string { return "tcp" }
func (h *AddrHead) String() string {
host, port := h.Addr()
return net.JoinHostPort(host, strconv.Itoa(int(port)))
}
// Socks5 is a SOCKS5 proxy. It implements the teeconn.Conn interface.
// It is used to be a second relay of other proxy tools.
// user -> sower -socks5-> third-party proxy -> target
type Socks5 struct{}
func New() *Socks5 {
return &Socks5{}
}
var noAuthResp = authResp{VER: 5, METHOD: 0}
var succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
func (s *Socks5) Unwrap(conn net.Conn) (net.Addr, error) {
{ //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 := binary.Write(conn, binary.BigEndian, noAuthResp); err != nil {
return nil, errors.Wrap(err, "write auth")
}
}
var addr addrType
{ // head
head := new(reqHead)
if err := binary.Read(conn, binary.BigEndian, head); err != nil || !head.IsValid() {
return nil, errors.Errorf("read head: %v, err: %s", head, err)
}
switch head.ATYP {
case 0x01: // IPv4
addr = &addrTypeIPv4{}
case 0x03: // domain name
addr = &addrTypeDomain{}
case 0x04: // IPv6
addr = &addrTypeIPv6{}
default:
return nil, errors.New("invalid ATYP")
}
if err := addr.Fulfill(conn); err != nil {
return nil, errors.Wrap(err, "read target")
}
if err := binary.Write(conn, binary.BigEndian, succHeadResp); err != nil {
return nil, errors.Wrap(err, "write head")
}
}
return &AddrHead{
addrType: addr,
}, nil
}
var noAuthReq = struct {
VER byte
NMETHODS uint8
METHODS byte
}{5, 1, 0}
var domainHead = reqHead{VER: 5, CMD: 1, RSV: 0, ATYP: 3}
func (s *Socks5) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
{ // auth
if err := binary.Write(conn, binary.BigEndian, &noAuthReq); err != nil {
return errors.WithStack(err)
}
resp := &authResp{}
if err := binary.Read(conn, binary.BigEndian, resp); err != nil {
return errors.WithStack(err)
}
}
{ // head
buf := bytes.NewBuffer(make([]byte, 0, binary.Size(domainHead)+1+len(tgtHost)+2))
_ = binary.Write(buf, binary.BigEndian, domainHead)
buf.WriteByte(uint8(len(tgtHost)))
buf.WriteString(tgtHost)
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort)})
if _, err := conn.Write(buf.Bytes()); err != nil {
return errors.WithStack(err)
}
head := respHead{}
if err := binary.Read(conn, binary.BigEndian, &head); err != nil {
return errors.WithStack(err)
}
}
return nil
}
-44
View File
@@ -1,44 +0,0 @@
package transport
// https://tools.ietf.org/html/rfc1928
type authReq struct {
VER byte
NMETHODS byte
METHODS [1]byte // 1 to 255, fix to no authentication
}
type authResp struct {
VER byte
METHOD byte
}
type request struct {
req
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type req struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *request) Bytes() []byte {
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
out = append(out, r.DST_ADDR...)
return append(out, r.DST_PORT...)
}
type response struct {
resp
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type resp struct {
VER byte
REP byte
RSV byte
ATYP byte
}
+81
View File
@@ -0,0 +1,81 @@
package sower
import (
"bytes"
"crypto/md5"
"encoding/binary"
"net"
"strconv"
"github.com/pkg/errors"
)
// https://en.wikipedia.org/wiki/Domain_Name_System
const maxDomainLength = 253
var headSize = binary.Size(new(Head))
// action(>=0x80) + checksum + port + target + data
// data(HTTP, first byte < 0x7F)
type Head struct {
Cmd byte
Checksum uint64
Port uint16
TgtAddr [maxDomainLength]byte
}
func (h *Head) Network() string { return "tcp" }
func (h *Head) String() string {
idx := bytes.IndexRune(h.TgtAddr[:], 0)
addr := string(h.TgtAddr[:idx])
return net.JoinHostPort(addr, strconv.Itoa(int(h.Port)))
}
type Sower struct {
password []byte
}
func New(password string) *Sower {
return &Sower{
password: []byte(password),
}
}
func (s *Sower) Unwrap(conn net.Conn) (net.Addr, error) {
buf := make([]byte, headSize)
if n, err := conn.Read(buf); err != nil || n != headSize {
return nil, errors.Errorf("n: %d, err: %s", n, err)
}
h := &Head{}
_ = binary.Read(bytes.NewReader(buf), binary.BigEndian, h)
switch h.Cmd {
case 0x80:
default:
return nil, errors.Errorf("invalid command: %d", h.Cmd)
}
if h.Checksum != sumChecksum(h.TgtAddr, s.password) {
return nil, errors.New("auth fail")
}
return h, nil
}
func (s *Sower) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
tgtAddr := [maxDomainLength]byte{}
copy(tgtAddr[:len(tgtHost)], []byte(tgtHost))
return binary.Write(conn, binary.BigEndian, &Head{
Cmd: 0x80,
Checksum: sumChecksum(tgtAddr, s.password),
Port: tgtPort,
TgtAddr: tgtAddr,
})
}
func sumChecksum(target [maxDomainLength]byte, password []byte) uint64 {
checksum := md5.Sum(append(target[:], password...))
checksumVal, _ := binary.Uvarint(checksum[:])
return checksumVal
}
+10
View File
@@ -0,0 +1,10 @@
package transport
import (
"net"
)
type Transport interface {
Unwrap(conn net.Conn) (net.Addr, error)
Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error
}
+46
View File
@@ -0,0 +1,46 @@
package transport
import (
"net"
"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"
)
func init() {
log.Logger = log.Logger.With().Caller().Logger()
}
func testPipe(tran Transport) (net.Addr, error) {
r, w := net.Pipe()
defer r.Close()
go func(w net.Conn) {
defer w.Close()
tran.Wrap(w, "sower", 443)
}(w)
return tran.Unwrap(teeconn.New(r))
}
func Test_Transports(t *testing.T) {
if addr, err := testPipe(newSower()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
t.Errorf("test sower, unexpected address: %s, err: %s", addr, err)
}
if addr, err := testPipe(newTrojan()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
t.Errorf("test trojan, unexpected address: %s, err: %s", addr, err)
}
}
func newSower() *sower.Sower {
return sower.New("123")
}
func newTrojan() *trojan.Trojan {
return trojan.New("123")
}
+174
View File
@@ -0,0 +1,174 @@
package trojan
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"io"
"net"
"strconv"
"github.com/pkg/errors"
)
// +-----------------------+---------+----------------+---------+----------+
// | hex(SHA224(password)) | CRLF | Trojan Request | CRLF | Payload |
// +-----------------------+---------+----------------+---------+----------+
// | 56 | X'0D0A' | Variable | X'0D0A' | Variable |
// +-----------------------+---------+----------------+---------+----------+
// +-----+------+----------+----------+
// | CMD | ATYP | DST.ADDR | DST.PORT |
// +-----+------+----------+----------+
// | 1 | 1 | Variable | 2 |
// +-----+------+----------+----------+
// o CMD
// o CONNECT X'01'
// o UDP :X'03'
// o ATYP
// o IP V4 : X'01'
// o domain: X'03'
// o IP V6 : X'04'
const headLen = 56 + 2 + 1 + 1
type staticHead struct {
Passwd [56]byte
CRLF [2]byte
CMD uint8
ATYP uint8
}
type ipv4Addr struct {
ADDR [4]byte
PORT uint16
CRLF [2]byte
}
func (*ipv4Addr) Network() string { return "tcp" }
func (a *ipv4Addr) String() string {
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
}
type ipv6Addr struct {
ADDR [16]byte
PORT uint16
CRLF [2]byte
}
func (*ipv6Addr) Network() string { return "tcp" }
func (a *ipv6Addr) String() string {
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
}
type domain struct {
ADDR string
PORT uint16
CRLF [2]byte
}
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 {
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 {
return errors.Wrap(err, "read doamin failed")
}
a.ADDR = string(buf[:addrLen])
a.PORT = uint16(buf[addrLen])<<8 + uint16(buf[addrLen+1])
return nil
}
type Trojan struct {
headPasswd []byte
headIPv4 []byte
headIPv6 []byte
headDomain []byte
}
func New(password string) *Trojan {
t := &Trojan{
headPasswd: make([]byte, 56),
}
passSum := sha256.Sum224([]byte(password))
hex.Encode(t.headPasswd, passSum[:])
t.headIPv4 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x01)
t.headIPv6 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x04)
t.headDomain = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x03)
return t
}
func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) {
buf := make([]byte, headLen)
// do not use io.ReadFull to avoid hang
if n, err := conn.Read(buf); err != nil || n != headLen {
return nil, errors.Errorf("n: %d, err: %s", n, err)
}
head := &staticHead{}
if err := binary.Read(bytes.NewBuffer(buf), binary.BigEndian, head); err != nil {
return nil, errors.Wrap(err, "read head")
}
if !bytes.Equal(head.Passwd[:], []byte(t.headPasswd)) {
return nil, errors.New("auth fail")
}
head.CMD, head.ATYP = buf[58], buf[59]
switch head.ATYP {
case 0x01: //ipv4
addr := &ipv4Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x04: //ipv6
addr := &ipv6Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x03: // domain
addr := &domain{}
err := addr.Fulfill(conn)
return addr, errors.Wrap(err, "read addr")
default:
return nil, errors.New("invalid ATYP")
}
}
func (t *Trojan) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
buf := bytes.NewBuffer(make([]byte, 0, headLen+1+len(tgtHost)+4))
ip := net.ParseIP(tgtHost)
switch {
case len(ip.To4()) != 0:
buf.Write(t.headIPv4)
buf.Write([]byte(ip.To4()))
case len(ip) != 0:
buf.Write(t.headIPv6)
buf.Write([]byte(ip.To16()))
default:
buf.Write(t.headDomain)
buf.WriteByte(byte(len(tgtHost)))
buf.WriteString(tgtHost)
}
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort), 0x0D, 0x0A})
if n, err := conn.Write(buf.Bytes()); err != nil || n != len(buf.Bytes()) {
return errors.Errorf("n: %d, err: %s", n, err)
}
return nil
}
-89
View File
@@ -1,89 +0,0 @@
package transport
import (
"context"
"crypto/tls"
"net"
"strconv"
"github.com/wweir/sower/dhcp"
"github.com/wweir/utils/log"
)
var (
persistDNS string
dnsAddr string
resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, network, dnsAddr)
},
}
)
func SetDNS(err error, dnsIP string) {
if dnsIP != "" {
persistDNS = dnsIP
dnsAddr = net.JoinHostPort(dnsIP, "53")
return
} else if persistDNS != "" {
return
}
if e, ok := err.(*net.DNSError); !ok /*nil*/ || !e.IsNotFound {
if dnsIP, err = dhcp.GetDefaultDNSServer(); err != nil {
dnsIP, err = dhcp.GetDefaultDNSServer() // retry
}
if err != nil {
log.Errorw("get dns via dhcp", "err", err, "current_dns", dnsAddr)
} else {
dnsAddr = net.JoinHostPort(dnsIP, "53")
}
}
}
// Dial dial targetAddr with possiable proxy address
func Dial(targetAddr string, dialAddr func(domain string) (proxyAddr string, password []byte)) (net.Conn, error) {
host, port, err := net.SplitHostPort(targetAddr)
if err != nil {
return nil, err
}
address, password := dialAddr(host)
if address == "" {
ips, err := resolver.LookupIPAddr(context.Background(), host)
if err != nil { //retry
ips, err = resolver.LookupIPAddr(context.Background(), host)
}
if err != nil {
SetDNS(err, "")
return nil, err
}
return net.Dial("tcp", net.JoinHostPort(ips[0].String(), port))
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
if addr, ok := IsSocks5Schema(address); ok {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
if conn, err = ToSocks5(conn, host, uint16(p)); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
conn, err := tls.DialWithDialer(&net.Dialer{Resolver: resolver},
"tcp", net.JoinHostPort(address, "443"), &tls.Config{})
if err != nil {
return nil, err
}
return ToProxyConn(conn, host, uint16(p), password)
}
-52
View File
@@ -1,52 +0,0 @@
package util
import (
"sort"
"strings"
)
type ReverseSecSlice struct {
sort.StringSlice
}
func NewReverseSecSlice(a []string) *ReverseSecSlice {
return &ReverseSecSlice{sort.StringSlice(a)}
}
func (p *ReverseSecSlice) Sort() *ReverseSecSlice {
sort.Sort(p)
return p
}
func (p *ReverseSecSlice) Uniq() []string {
olds := []string(p.StringSlice)
last := ""
strs := make([]string, 0, len(olds))
for _, str := range olds {
if str != last {
strs = append(strs, str)
}
last = str
}
return strs
}
func (p *ReverseSecSlice) Less(i, j int) bool {
secsI := strings.Split(p.StringSlice[i], ".")
secsJ := strings.Split(p.StringSlice[j], ".")
lenI := len(secsI) - 1
lenJ := len(secsJ) - 1
length := lenI
if lenI > lenJ {
length = lenJ
}
for idx := 0; idx <= length; idx++ {
if secsI[lenI-idx] == secsJ[lenJ-idx] {
continue
}
return secsI[lenI-idx] < secsJ[lenJ-idx]
}
return lenI < lenJ
}
-65
View File
@@ -1,65 +0,0 @@
package util
import (
"sort"
"testing"
)
func TestReverseSecSlice_Less(t *testing.T) {
type fields struct {
StringSlice sort.StringSlice
}
type args struct {
i int
j int
}
slice := fields{sort.StringSlice([]string{
"a.b.c",
"a.b.c",
"d.b.c",
"d.a.b.c",
"",
"",
})}
tests := []struct {
name string
fields fields
args args
want bool
}{{
name: "equal",
fields: slice,
args: args{0, 1},
want: false,
}, {
name: "less",
fields: slice,
args: args{0, 2},
want: true,
}, {
name: "length",
fields: slice,
args: args{0, 3},
want: true,
}, {
name: "single_empty",
fields: slice,
args: args{0, 4},
want: false,
}, {
name: "all_empty",
fields: slice,
args: args{4, 5},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ReverseSecSlice{
StringSlice: tt.fields.StringSlice,
}
if got := p.Less(tt.args.i, tt.args.j); got != tt.want {
t.Errorf("ReverseSecSlice.Less() = %v, want %v", got, tt.want)
}
})
}
}
-108
View File
@@ -1,108 +0,0 @@
package util
import (
"strings"
"sync"
)
type Node struct {
node
sep string
*sync.RWMutex
}
type node struct {
node map[string]*node
}
func NewNodeFromRules(rules ...string) *Node {
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
for i := range rules {
n.Add(rules[i])
}
return n
}
func (n *Node) String() string {
n.RLock()
defer n.RUnlock()
return n.string("", " ")
}
func (n *node) string(prefix, indent string) (out string) {
for key, val := range n.node {
out += prefix + key + "\n" + val.string(prefix+indent, indent)
}
return
}
func (n *Node) trim(item string) string {
return strings.TrimSuffix(item, n.sep)
}
func (n *Node) Add(item string) {
n.Lock()
defer n.Unlock()
n.add(strings.Split(n.trim(item), n.sep))
}
func (n *node) add(secs []string) {
length := len(secs)
switch length {
case 0:
return
case 1:
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
default:
sec := secs[length-1]
if sec == "**" {
sec = "*"
}
subNode, ok := n.node[sec]
if !ok {
subNode = &node{node: map[string]*node{}}
n.node[sec] = subNode
}
subNode.add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
if n == nil {
return false
}
n.RLock()
defer n.RUnlock()
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if _, ok := n.node[""]; ok {
return true
}
if _, ok := n.node["**"]; ok {
return true
}
if _, ok := n.node["*"]; ok {
return !fuzzNode
}
return false
}
if n, ok := n.node[secs[length-1]]; ok {
if n.matchSecs(secs[:length-1], false) {
return true
}
}
if n, ok := n.node["*"]; ok {
if n.matchSecs(secs[:length-1], true) {
return true
}
}
if _, ok := n.node["**"]; ok {
return true
}
return false
}
-50
View File
@@ -1,50 +0,0 @@
package util
import (
"io"
"net"
)
type TeeConn struct {
net.Conn
buf []byte
offset int
stop bool // read
EnableWrite bool
}
func (t *TeeConn) Reread() {
t.offset = 0
}
func (t *TeeConn) Reset() {
t.buf = []byte{}
t.offset = 0
}
func (t *TeeConn) Stop() {
t.offset = 0
t.stop = true
}
func (t *TeeConn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return
}
n, err = t.Conn.Read(b)
if !t.stop {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, err
}
func (t *TeeConn) Write(b []byte) (n int, err error) {
if t.stop || t.EnableWrite {
return t.Conn.Write(b)
}
return 0, io.EOF
}
-11
View File
@@ -1,11 +0,0 @@
package util
import "net"
func WithDefaultPort(addr string, port string) (address, host string) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return net.JoinHostPort(addr, port), addr
}
return addr, host
}