mirror of
https://github.com/wweir/sower.git
synced 2026-10-20 00:15:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1574623507 | ||
|
|
ee6335fa47 | ||
|
|
f7214bfdb4 | ||
|
|
291ae3f85a | ||
|
|
07c51d42ff | ||
|
|
90c497b8da | ||
|
|
28bd12d220 | ||
|
|
c0f39fc2e0 | ||
|
|
bcc6d7c504 | ||
|
|
5d1827b2da | ||
|
|
c38d519f92 | ||
|
|
dd4444b71c | ||
|
|
899d64a92c | ||
|
|
7d77f4a85e | ||
|
|
4681dd9845 | ||
|
|
85226890c5 | ||
|
|
1a24286bbc | ||
|
|
fd5c34dca1 | ||
|
|
8cd4fe57ba | ||
|
|
545b153f50 | ||
|
|
697e2aa1d5 | ||
|
|
2fe1708d3f | ||
|
|
cbe943a020 | ||
|
|
d7e25d6565 | ||
|
|
acb3f58526 | ||
|
|
50684272c9 | ||
|
|
b0428177de | ||
|
|
75e7f1aab7 | ||
|
|
f256ab686d | ||
|
|
0c0f0699c1 | ||
|
|
8532a2e16c | ||
|
|
bad338033c | ||
|
|
a57f85ab1b | ||
|
|
aed2afbe6d | ||
|
|
2becd42176 | ||
|
|
d3664dc2b9 | ||
|
|
a6fd60b5a2 | ||
|
|
d6dbfd4129 | ||
|
|
d31bbf9226 | ||
|
|
ac4e4266bc | ||
|
|
f9f0ca4ee7 | ||
|
|
98c3934762 | ||
|
|
f001f7dab7 | ||
|
|
36ef329b68 | ||
|
|
64778f68ac | ||
|
|
53748cfa85 | ||
|
|
d35115b7b3 | ||
|
|
559c5fde6b | ||
|
|
eb8080df18 | ||
|
|
45ca063a7e | ||
|
|
6add7afd19 | ||
|
|
f13687506e | ||
|
|
199e4cf295 | ||
|
|
b3ad9f6657 | ||
|
|
f9e9fe366f | ||
|
|
103699e25e | ||
|
|
f80755eae5 | ||
|
|
5b217b794d | ||
|
|
26055c5203 | ||
|
|
029611c078 | ||
|
|
89adc6a2c9 | ||
|
|
8c95c59e5e | ||
|
|
4f8376c041 | ||
|
|
10172f8be2 | ||
|
|
2831cdf284 | ||
|
|
06558ac635 | ||
|
|
3cf4deddb7 | ||
|
|
679263be9a | ||
|
|
21d8ec4ec7 | ||
|
|
4916245522 | ||
|
|
11c2808320 | ||
|
|
21ddd79e4d | ||
|
|
9a158d4088 | ||
|
|
88990cddd9 | ||
|
|
ca1b2650ab | ||
|
|
8ff1046ebf | ||
|
|
dfe81f6f23 | ||
|
|
972875ec36 | ||
|
|
89b7a41d46 | ||
|
|
8f598dbc17 | ||
|
|
192b4f798b | ||
|
|
2adefe0dfc | ||
|
|
7b0c13ef1c | ||
|
|
63bb934148 | ||
|
|
cacc732a4e | ||
|
|
17d079dcb2 | ||
|
|
413a93f6f7 | ||
|
|
6c5f5cd74f | ||
|
|
e948541efe | ||
|
|
ffa2128274 | ||
|
|
b53b7874b6 | ||
|
|
f3117edfa0 | ||
|
|
ba8980da73 | ||
|
|
e3ec74b74f | ||
|
|
6e67193894 | ||
|
|
af0c5f895c | ||
|
|
4883e240cc | ||
|
|
18ee9ca993 | ||
|
|
4fff2a1f3b | ||
|
|
3e82890c33 | ||
|
|
01d66d737b | ||
|
|
e342dadaf1 | ||
|
|
7ba9f1a23c | ||
|
|
7240208cb3 | ||
|
|
a27748d835 | ||
|
|
39797d077b | ||
|
|
b6df69358a | ||
|
|
ae525f9b40 | ||
|
|
29eb0e6e1c | ||
|
|
aef941b17e | ||
|
|
9bc5a32074 | ||
|
|
8194083f54 | ||
|
|
c20903137d | ||
|
|
2f3c98b8d3 | ||
|
|
bc99bda1b3 | ||
|
|
ceedef4203 | ||
|
|
712b689708 | ||
|
|
d5260d66df | ||
|
|
bde27f64ec | ||
|
|
04193eb608 | ||
|
|
41a1adc707 | ||
|
|
c9191ec928 | ||
|
|
14da5b711e | ||
|
|
59257c0673 | ||
|
|
7db92a19be | ||
|
|
fbae2999dc | ||
|
|
92991da6a0 | ||
|
|
b60b5fceb3 | ||
|
|
756a8c8d17 | ||
|
|
e02da26936 | ||
|
|
c5adcc4421 | ||
|
|
c20c3621c6 | ||
|
|
055c1a2901 | ||
|
|
fc2ea8b8c0 | ||
|
|
3e6b9e96ca | ||
|
|
2264d73c45 | ||
|
|
99ba06b2d4 | ||
|
|
a8e026ff7f | ||
|
|
9e377e4a2d | ||
|
|
107174fb6b | ||
|
|
3f8f5c2ef1 | ||
|
|
918b964b78 | ||
|
|
34722a460c | ||
|
|
8734486c48 | ||
|
|
8d0ee2a9ea | ||
|
|
98247845ad | ||
|
|
72dacaf1de | ||
|
|
56d5090841 | ||
|
|
821cbc302e | ||
|
|
f6eddb9f96 | ||
|
|
344531c301 | ||
|
|
df897f72c6 | ||
|
|
dd2fe95900 | ||
|
|
08a5d4363b | ||
|
|
e712dcb78c | ||
|
|
e9f5fc4789 | ||
|
|
f76f31d054 | ||
|
|
e0de537042 | ||
|
|
b050e312b2 | ||
|
|
d78a757200 | ||
|
|
b2282a58a2 | ||
|
|
0fb2271cac | ||
|
|
a8089af235 | ||
|
|
6e4a6c794e | ||
|
|
1d620b8191 | ||
|
|
9586e7bc8e | ||
|
|
ec4286e6d4 | ||
|
|
c01f217939 | ||
|
|
c62e3a3284 | ||
|
|
578d9162aa | ||
|
|
002ac49e65 | ||
|
|
ee47f02cb5 | ||
|
|
b1127e8f9e | ||
|
|
282969b696 | ||
|
|
2daaa8f3ef | ||
|
|
d6776345a0 | ||
|
|
0e03605c37 | ||
|
|
9ca58982b7 | ||
|
|
60452da6b1 |
@@ -0,0 +1,17 @@
|
||||
# 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" ]
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Go
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: test and build
|
||||
run: |
|
||||
make
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: build matrix
|
||||
run: |
|
||||
cp conf/sower.toml sower.toml
|
||||
|
||||
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
|
||||
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf sower-darwin-amd64.tar.gz sower sower.toml
|
||||
|
||||
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
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@master
|
||||
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 }}
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
*.out
|
||||
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
._*
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode
|
||||
.idea
|
||||
sower
|
||||
cert.pem
|
||||
**/cert.pem
|
||||
privkey.pem
|
||||
**/privkey.pem
|
||||
sower.exe*
|
||||
/sower.toml
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
SERVER:=127.0.0.1:5533
|
||||
|
||||
default: build
|
||||
default: test build
|
||||
|
||||
test:
|
||||
go vet ./...
|
||||
go list ./... | grep -v internal | xargs go test
|
||||
build:
|
||||
GOBIN=$(PWD) go install -v
|
||||
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 .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -logtostderr -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -logtostderr -v 1 &
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
|
||||
@@ -1,48 +1,90 @@
|
||||
# sower
|
||||
Yet another cross platform transparent proxy tool
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://github.com/wweir/sower/actions)
|
||||
[](https://hub.docker.com/r/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
## architecture
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
| |
|
||||
| |
|
||||
+------------server-------------+
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parsr http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic connection +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| | |
|
||||
| relay service | dns |
|
||||
| | server |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-------^----------+---^----^---+
|
||||
| | |
|
||||
| | | +----->
|
||||
| +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
block request normal request
|
||||
|
||||
中文介绍见 [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.
|
||||
|
||||
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.
|
||||
|
||||
## Server
|
||||
*If you already have another proxy solution with socks5h support, you can skip server-side.*
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## install
|
||||
1. install server on `server node` by `make server`
|
||||
2. write config file, example: [conf/sower.toml](https://github.com/wweir/sower/blob/master/conf/sower.toml)
|
||||
3. install client on `client node` by `make client`
|
||||
4. add `127.0.0.1` as you first domain server
|
||||
## 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.
|
||||
|
||||
## todo
|
||||
- [ ] authenticate
|
||||
- [ ] broker
|
||||
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
|
||||
|
||||
### HTTP(S)_PROXY
|
||||
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
|
||||
|
||||
### 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### 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"
|
||||
```
|
||||
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
+138
-55
@@ -2,80 +2,163 @@ package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net"
|
||||
"strconv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/fsnotify"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
DnsServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
ClientIPNet net.IP `toml:"-"`
|
||||
BlockList []string `toml:"blocklist"`
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
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"`
|
||||
DetectTimeout string `toml:"detect_timeout"`
|
||||
|
||||
ProxyList []string `toml:"proxy_list"`
|
||||
DirectList []string `toml:"direct_list"`
|
||||
DynamicList []string `toml:"dynamic_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
|
||||
|
||||
installCmd 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"`
|
||||
Server server `toml:"server"`
|
||||
Client client `toml:"client"`
|
||||
}{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
|
||||
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)")
|
||||
flag.StringVar(&Conf.DnsServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
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")
|
||||
flag.IntVar(&Conf.Client.Router.DetectLevel, "level", 2, "dynamic rule detect level: 0~4")
|
||||
flag.StringVar(&Conf.Client.Router.DetectTimeout, "timeout", "300ms", "dynamic rule detect timeout")
|
||||
uninstallFlag := flag.Bool("uninstall", false, "uninstall service")
|
||||
_init() // execute platform init logic
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
switch {
|
||||
case *uninstallFlag:
|
||||
uninstall()
|
||||
os.Exit(0)
|
||||
case installCmd != "":
|
||||
install()
|
||||
os.Exit(0)
|
||||
default:
|
||||
runAsService()
|
||||
}
|
||||
|
||||
if Conf.ConfigFile == "" {
|
||||
defer log.Infow("starting", "config", &Conf)
|
||||
if Conf.file == "" {
|
||||
return
|
||||
}
|
||||
if err := OnRefreash[0](); err != nil {
|
||||
panic(err)
|
||||
|
||||
for i := range loadConfigFns {
|
||||
if err := loadConfigFns[i].fn(); err != nil {
|
||||
log.Fatalw("load config", "config", Conf.file, "step", loadConfigFns[i].step, "err", err)
|
||||
}
|
||||
}
|
||||
watchConfigFile()
|
||||
}
|
||||
|
||||
var OnRefreash = []func() error{func() error {
|
||||
if _, err := toml.DecodeFile(Conf.ConfigFile, &Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
|
||||
// for glog
|
||||
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
|
||||
// watchConfigFile changes, fsnotify take too much cpu time, DIY
|
||||
func watchConfigFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
// 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 {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
if err := watcher.Add(Conf.ConfigFile); err != nil {
|
||||
glog.Fatalln(err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
return toml.NewDecoder(f).Decode(&Conf)
|
||||
}}}
|
||||
|
||||
// flushCh to avoid parallel persist
|
||||
var flushCh = make(chan struct{})
|
||||
var flushOnce = sync.Once{}
|
||||
|
||||
// PersistRule persist dynamic rule into config file
|
||||
func PersistRule(domain string) {
|
||||
flushOnce.Do(func() {
|
||||
go flushConfDaemon()
|
||||
})
|
||||
|
||||
log.Infow("persist dynamic rule into config", "domain", domain)
|
||||
Conf.Client.Router.DynamicList = append(Conf.Client.Router.DynamicList, 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.DynamicList =
|
||||
util.NewReverseSecSlice(Conf.Client.Router.DynamicList).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)
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
glog.Fatalln(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// +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 = ""
|
||||
|
||||
func _init() {
|
||||
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\"'")
|
||||
}
|
||||
|
||||
func runAsService() {}
|
||||
|
||||
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 -w " + 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
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// +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 = ""
|
||||
|
||||
func _init() {
|
||||
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'")
|
||||
}
|
||||
|
||||
func runAsService() {}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// +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 = execDir
|
||||
|
||||
func _init() {
|
||||
flag.StringVar(&Conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
|
||||
flag.StringVar(&installCmd, "install", "", "put any character to install as a service, eg: true")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+55
-70
@@ -1,70 +1,55 @@
|
||||
server_port="5533"
|
||||
server_addr="127.0.0.1:5533" # replce it to server "IP"
|
||||
server_addr="remote-server:5533" # replce it to remote server
|
||||
dns_server="114.114.114.114"
|
||||
# client_ip="192.168.0.1"
|
||||
blocklist=[
|
||||
"*.google.com", # google
|
||||
"ocsp.pki.goog",
|
||||
"g.co",
|
||||
"*.goo.gl",
|
||||
"*.googleusercontent.com",
|
||||
"*.googleapis.com",
|
||||
"gmail.com",
|
||||
"*.youtube.com", # youtube
|
||||
"*.ytimg.com",
|
||||
"*.ggpht.com",
|
||||
"*.googlevideo.com",
|
||||
"*.vimeo.com",
|
||||
"*.facebook.com", # facebook
|
||||
"*.xx.fbcdn.net",
|
||||
"connect.facebook.net",
|
||||
"*.twitter.com", # twitter
|
||||
"t.co",
|
||||
"*.twimg.com",
|
||||
"www.linkedin.com",
|
||||
"www.reddit.com", # reddit
|
||||
"www.redditmedia.com",
|
||||
"*.blogspot.com", # blogspot
|
||||
"www.blogger.com",
|
||||
"www.feedburner.com",
|
||||
"img1.blogblog.com",
|
||||
"*.aws.amazon.com", # amazon
|
||||
"m.media-amazon.com",
|
||||
"*.awsstatic.com",
|
||||
"*.cloudfront.net", # atlassian
|
||||
"*.medium.com",
|
||||
"accounts-static.cdn.mozilla.net", # firefox
|
||||
"*.services.mozilla.com",
|
||||
"pocket-image-cache.com", # pocket
|
||||
"*.golang.org", # golang
|
||||
"go.googlesource.com",
|
||||
"godoc.org",
|
||||
"gist.github.com", # github
|
||||
"*.k8s.io", # k8s
|
||||
"k8s.gcr.io",
|
||||
"*.wikipedia.org", # wikipeida
|
||||
"*.wikimedia.org",
|
||||
"*.wikisource.org",
|
||||
"lookup-api.apple.com",
|
||||
"lookup-api.apple.com.edgekey.net",
|
||||
"e16991.b.akamaiedge.net",
|
||||
"*.web.telegram.org", # telegram
|
||||
"core.telegram.org",
|
||||
"telegram.me",
|
||||
"t.me",
|
||||
"*.slack.com", #slack
|
||||
"*.slack-edge.com",
|
||||
"js.driftt.com",
|
||||
"cloud.feedly.com", # feedly
|
||||
"static.grammarly.com", # grammarly
|
||||
"apkpure.com", # apkpure
|
||||
"download.apkpure.com",
|
||||
"*.nytimes.com", # New York times
|
||||
"*.nyt.com",
|
||||
"bandwagonhost.com", # bandwagonhost
|
||||
"www.bwh1.net",
|
||||
"*.akadns.net", # suggestions
|
||||
"*.haxx.se",
|
||||
]
|
||||
verbose=1
|
||||
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 = 2 # 0~4, the bigger the harder to add
|
||||
detect_timeout = "300ms"
|
||||
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
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
func StartDNS(dnsServer string) {
|
||||
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 {
|
||||
r.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).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
|
||||
}
|
||||
|
||||
if len(conf.Conf.BlockList) == 0 {
|
||||
bestTry(w, r, r.Question[0].Name, dnsServer)
|
||||
} else {
|
||||
manual(w, r, r.Question[0].Name, dnsServer)
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
msg, _ := dns.Exchange(r, dnsServer+":53")
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
if len(msg.Answer) == 0 { // expose any response
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var ip string
|
||||
switch msg.Answer[0].(type) {
|
||||
case *dns.A:
|
||||
ip = msg.Answer[0].(*dns.A).A.String()
|
||||
case *dns.AAAA:
|
||||
ip = "[" + msg.Answer[0].(*dns.AAAA).AAAA.String() + "]"
|
||||
default:
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
|
||||
glog.V(2).Infoln(ip+":80", err)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
if rule.Match(strings.TrimSuffix(domain, ".")) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
}
|
||||
|
||||
msg, _ := dns.Exchange(r, dnsServer+":53") // expose any response
|
||||
if msg == nil {
|
||||
glog.V(1).Infof("get dns of %s fail", domain)
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
|
||||
if conf.Conf.Verbose != 0 && len(msg.Answer) != 0 {
|
||||
go func() {
|
||||
_, err := net.DialTimeout("tcp", domain+":http", 2*time.Second)
|
||||
if err == nil || !strings.Contains(err.Error(), "timeout") {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = net.DialTimeout("tcp", domain+":https", 3*time.Second)
|
||||
if err == nil || !strings.Contains(err.Error(), "timeout") {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(1).Infof("SUGGEST check (%s) http(s) service: %s", domain, err)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: conf.Conf.ClientIPNet,
|
||||
}}
|
||||
return m
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Node map[string]*Node
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
return &Node{Node: map[string]*Node{}}
|
||||
}
|
||||
func NewNodeFromRule(rules ...string) *Node {
|
||||
node := NewNode()
|
||||
for i := range rules {
|
||||
node.Add(strings.Split(rules[i], "."))
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return n.string("")
|
||||
}
|
||||
func (n *Node) string(prefix string) (out string) {
|
||||
for key, val := range n.Node {
|
||||
out += prefix + key + "\n" + val.string(prefix+" ")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Node) Add(secs []string) {
|
||||
length := len(secs)
|
||||
switch length {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
n.Node[secs[length-1]] = NewNode()
|
||||
default:
|
||||
subNode, ok := n.Node[secs[length-1]]
|
||||
if !ok {
|
||||
subNode = NewNode()
|
||||
n.Node[secs[length-1]] = subNode
|
||||
}
|
||||
subNode.Add(secs[:length-1])
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Match(addr string) bool {
|
||||
return n.matchSecs(strings.Split(addr, "."))
|
||||
}
|
||||
|
||||
func (n *Node) matchSecs(secs []string) bool {
|
||||
length := len(secs)
|
||||
if length == 0 {
|
||||
return len(n.Node) == 0
|
||||
}
|
||||
|
||||
if n, ok := n.Node[secs[length-1]]; ok {
|
||||
return n.matchSecs(secs[:length-1])
|
||||
}
|
||||
|
||||
_, ok := n.Node["*"]
|
||||
return ok
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
var rule *Node
|
||||
|
||||
func init() {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,21 +1,14 @@
|
||||
module github.com/wweir/sower
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
||||
github.com/lucas-clemente/quic-go v0.10.0
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.1.1
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
|
||||
github.com/libp2p/go-reuseport v0.0.1
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/pelletier/go-toml v1.6.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
|
||||
)
|
||||
|
||||
@@ -1,54 +1,102 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
||||
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
|
||||
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
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.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
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/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 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.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.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/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-20171107155558-ca99b135e50f h1:QatZ4lsJBY3x1+Imst9g95+vUl7m52dqM9Pi4aSMW8w=
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f/go.mod h1:BNLmYJ8oMJPIPpNx5968jCyUhwEU1XT3YsuOqtbo5qo=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736 h1:x6LiUrnHR4CCqzRzB0zqiB9hzVFcRhSKrVlZDGW0FcM=
|
||||
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
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.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
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-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/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-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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/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=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/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=
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy"
|
||||
"github.com/wweir/sower/router"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.Infoln("Starting:", conf.Conf)
|
||||
switch {
|
||||
case conf.Conf.Server.Upstream != "":
|
||||
proxy.StartServer(conf.Conf.Server.Upstream, conf.Conf.Password, conf.ConfigDir,
|
||||
conf.Conf.Server.CertFile, conf.Conf.Server.KeyFile, conf.Conf.Server.CertEmail)
|
||||
|
||||
if conf.Conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.Conf.ServerPort)
|
||||
} else {
|
||||
go dns.StartDNS(conf.Conf.DnsServer)
|
||||
proxy.StartClient(conf.Conf.ServerAddr)
|
||||
case conf.Conf.Client.Address != "":
|
||||
route := &router.Route{
|
||||
ProxyAddress: conf.Conf.Client.Address,
|
||||
ProxyPassword: conf.Conf.Password,
|
||||
DetectLevel: conf.Conf.Client.Router.DetectLevel,
|
||||
DetectTimeout: conf.Conf.Client.Router.DetectTimeout,
|
||||
DirectList: conf.Conf.Client.Router.DirectList,
|
||||
ProxyList: conf.Conf.Client.Router.ProxyList,
|
||||
DynamicList: conf.Conf.Client.Router.DynamicList,
|
||||
PersistFn: conf.PersistRule,
|
||||
}
|
||||
|
||||
go proxy.StartHTTPProxy(conf.Conf.Client.HTTPProxy, conf.Conf.Client.Address,
|
||||
[]byte(conf.Conf.Password), route.ShouldProxy)
|
||||
|
||||
enableDNSSolution := conf.Conf.Client.DNSServeIP != ""
|
||||
if enableDNSSolution {
|
||||
go proxy.StartDNS(conf.Conf.Client.DNSServeIP, conf.Conf.Client.DNSUpstream,
|
||||
route.ShouldProxy)
|
||||
}
|
||||
|
||||
proxy.StartClient(conf.Conf.Client.Address, conf.Conf.Password, enableDNSSolution,
|
||||
conf.Conf.Client.PortForward, route.ShouldProxy)
|
||||
|
||||
default:
|
||||
fmt.Println()
|
||||
flag.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
Tee bool // read
|
||||
}
|
||||
|
||||
func (t *TeeConn) Reset() {
|
||||
t.offset = 0
|
||||
}
|
||||
|
||||
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.Tee {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
|
||||
teeConn = &TeeConn{Conn: conn, Tee: true}
|
||||
defer func() {
|
||||
teeConn.Reset()
|
||||
teeConn.Tee = false
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if n, err := teeConn.Read(buf); err != nil || n != 1 {
|
||||
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %d %v", err, n, buf)
|
||||
}
|
||||
|
||||
teeConn.Reset()
|
||||
|
||||
// https
|
||||
if buf[0] == 0x16 { // SSL handleshake
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
|
||||
// http
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func extractSNI(r io.Reader) (string, int, error) {
|
||||
handshake, tlsver, err := handshakeRecord(r)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading TLS record: %s", err)
|
||||
}
|
||||
|
||||
sni, err := parseHello(handshake)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
if len(sni) == 0 {
|
||||
// ClientHello did not present an SNI extension. Valid packet,
|
||||
// no hostname.
|
||||
return "", tlsver, nil
|
||||
}
|
||||
|
||||
hostname, err := parseSNI(sni)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("parsing SNI extension: %s", err)
|
||||
}
|
||||
return hostname, tlsver, nil
|
||||
}
|
||||
|
||||
// Extract the indicated hostname, if any, from the given SNI
|
||||
// extension bytes.
|
||||
func parseSNI(b []byte) (string, error) {
|
||||
b, _, err := vector(b, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var ret []byte
|
||||
for len(b) >= 3 {
|
||||
typ := b[0]
|
||||
ret, b, err = vector(b[1:], 2)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("truncated SNI extension")
|
||||
}
|
||||
|
||||
if typ == sniHostnameID {
|
||||
return string(ret), nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return "", fmt.Errorf("trailing garbage at end of SNI extension")
|
||||
}
|
||||
|
||||
// No DNS-based SNI present.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const sniExtensionID = 0
|
||||
const sniHostnameID = 0
|
||||
|
||||
// Parse a TLS handshake record as a ClientHello message and extract
|
||||
// the SNI extension bytes, if any.
|
||||
func parseHello(b []byte) ([]byte, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("zero length handshake record")
|
||||
}
|
||||
if b[0] != 1 {
|
||||
return nil, fmt.Errorf("non-ClientHello handshake record type %d", b[0])
|
||||
}
|
||||
|
||||
// We're expecting a stricter TLS parser to run after we've
|
||||
// proxied, so we ignore any trailing bytes that might be present
|
||||
// (e.g. another handshake message).
|
||||
b, _, err := vector(b[1:], 3)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
|
||||
// ClientHello must be at least 34 bytes to reach the first vector
|
||||
// length byte. The actual minimal size is larger than that, but
|
||||
// vector() will correctly handle truncated packets.
|
||||
if len(b) < 34 {
|
||||
return nil, errors.New("ClientHello packet too short")
|
||||
}
|
||||
|
||||
if b[0] != 3 {
|
||||
return nil, fmt.Errorf("ClientHello has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
switch b[1] {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, fmt.Errorf("TLS record has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
|
||||
// Skip over version and random struct
|
||||
b = b[34:]
|
||||
|
||||
// We don't technically care about SessionID, but we care that the
|
||||
// framing is well-formed all the way up to the SNI field, so that
|
||||
// we are sure that we're pulling the same SNI bytes as the
|
||||
// eventual TLS implementation.
|
||||
vec, b, err := vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello SessionID: %s", err)
|
||||
}
|
||||
if len(vec) > 32 {
|
||||
return nil, fmt.Errorf("ClientHello SessionID too long (%db)", len(vec))
|
||||
}
|
||||
|
||||
// Likewise, we're just checking the bare minimum of framing.
|
||||
vec, b, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CipherSuites: %s", err)
|
||||
}
|
||||
if len(vec) < 2 || len(vec)%2 != 0 {
|
||||
return nil, fmt.Errorf("ClientHello CipherSuites invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
vec, b, err = vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CompressionMethods: %s", err)
|
||||
}
|
||||
if len(vec) < 1 {
|
||||
return nil, fmt.Errorf("ClientHello CompressionMethods invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
// Finally, we reach the extensions.
|
||||
if len(b) == 0 {
|
||||
// No extensions. This is not an error, it just means we have
|
||||
// no SNI payload.
|
||||
return nil, nil
|
||||
}
|
||||
b, vec, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extensions: %s", err)
|
||||
}
|
||||
if len(vec) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(vec))
|
||||
}
|
||||
|
||||
for len(b) >= 4 {
|
||||
typ := binary.BigEndian.Uint16(b[:2])
|
||||
vec, b, err = vector(b[2:], 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extension %d: %s", typ, err)
|
||||
}
|
||||
if typ == sniExtensionID {
|
||||
// Found the SNI extension, return its payload. We don't
|
||||
// care about anything in the packet beyond this point.
|
||||
return vec, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(b))
|
||||
}
|
||||
|
||||
// Successfully parsed all extensions, but there was no SNI.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const maxTLSRecordLength = 16384
|
||||
|
||||
// Read one TLS record, which must be for the handshake protocol, from r.
|
||||
func handshakeRecord(r io.Reader) ([]byte, int, error) {
|
||||
var hdr struct {
|
||||
Type uint8
|
||||
Major, Minor uint8
|
||||
Length uint16
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
|
||||
return nil, 0, fmt.Errorf("reading TLS record header: %s", err)
|
||||
}
|
||||
|
||||
if hdr.Type != 22 {
|
||||
return nil, 0, fmt.Errorf("TLS record is not a handshake")
|
||||
}
|
||||
|
||||
if hdr.Major != 3 {
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
switch hdr.Minor {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
|
||||
if hdr.Length > maxTLSRecordLength {
|
||||
return nil, 0, fmt.Errorf("TLS record length is greater than %d", maxTLSRecordLength)
|
||||
}
|
||||
|
||||
ret := make([]byte, hdr.Length)
|
||||
if _, err := io.ReadFull(r, ret); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ret, int(hdr.Minor), nil
|
||||
}
|
||||
|
||||
func vector(b []byte, lenBytes int) ([]byte, []byte, error) {
|
||||
if len(b) < lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
var l int
|
||||
for _, b := range b[:lenBytes] {
|
||||
l = (l << 8) + int(b)
|
||||
}
|
||||
if len(b) < l+lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
return b[lenBytes : l+lenBytes], b[l+lenBytes:], nil
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
func StartClient(server string) {
|
||||
connCh := listenLocal([]string{":80", ":443"})
|
||||
reDialCh := make(chan net.Conn, 10)
|
||||
var conn net.Conn
|
||||
|
||||
for {
|
||||
sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil)
|
||||
if err != nil {
|
||||
if sess, err = quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, nil); err != nil {
|
||||
glog.Errorf("connect to remote(%s) fail:%s\n", server, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
}
|
||||
glog.Infof("new session from (%s) to (%s)", sess.LocalAddr(), sess.RemoteAddr())
|
||||
|
||||
for { // session rotate logic
|
||||
select {
|
||||
case conn = <-connCh:
|
||||
case conn = <-reDialCh:
|
||||
}
|
||||
|
||||
// sync action to reuse sigle sess
|
||||
if !openStream(conn, sess, reDialCh) {
|
||||
sess.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openStream(conn net.Conn, sess quic.Session, reDialCh chan<- net.Conn) bool {
|
||||
glog.V(2).Infoln("new request from", conn.RemoteAddr())
|
||||
|
||||
okCh := make(chan struct{})
|
||||
go func() {
|
||||
stream, err := sess.OpenStream()
|
||||
if err != nil {
|
||||
glog.Warningf("start stream to (%s) fail:%s\n", sess.RemoteAddr(), err)
|
||||
reDialCh <- conn
|
||||
close(okCh)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
select {
|
||||
case okCh <- struct{}{}:
|
||||
default:
|
||||
close(okCh)
|
||||
return
|
||||
}
|
||||
close(okCh)
|
||||
|
||||
if err := conn.(*net.TCPConn).SetKeepAlive(true); err != nil {
|
||||
glog.Warningln(err)
|
||||
}
|
||||
relay(&streamConn{stream, sess}, conn)
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case _, ok := <-okCh: // false means close on error
|
||||
return ok
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(ports []string) <-chan net.Conn {
|
||||
connCh := make(chan net.Conn, 10)
|
||||
for i := range ports {
|
||||
go func(port string) {
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}(ports[i])
|
||||
}
|
||||
|
||||
glog.Infoln("listening ports:", ports)
|
||||
return connCh
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/libp2p/go-reuseport"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var xid = make([]byte, 4)
|
||||
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
|
||||
|
||||
func GetDefaultDNSServer() (string, error) {
|
||||
iface, err := PickInternetInterface()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "pick interface")
|
||||
}
|
||||
|
||||
rand.Read(xid)
|
||||
pack := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, net.IPv4(0, 0, 0, 0), xid, true, []dhcp4.Option{
|
||||
{Code: dhcp4.OptionRequestedIPAddress, Value: []byte(iface.IP.To4())},
|
||||
{Code: dhcp4.End},
|
||||
})
|
||||
|
||||
var conn net.PacketConn
|
||||
if runtime.GOOS == "windows" {
|
||||
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
} else {
|
||||
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
|
||||
if _, err := conn.WriteTo([]byte(pack), broadcastAddr); err != nil {
|
||||
return "", errors.Wrap(err, "write broadcast")
|
||||
}
|
||||
|
||||
buf := make([]byte, 1500 /*MTU*/)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "read dhcp offer")
|
||||
}
|
||||
|
||||
pack = dhcp4.Packet(buf[:n])
|
||||
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
|
||||
if len(dnsBytes) < 4 {
|
||||
return "", errors.New("no DNS setting in upstream network device")
|
||||
}
|
||||
|
||||
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
got, err := dhcp.GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// +build !windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range ifaces {
|
||||
if len(ifaces[i].HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, _ := ifaces[i].Addrs()
|
||||
for _, addr := range addrs {
|
||||
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
|
||||
return &Iface{ifaces[i].HardwareAddr, ip}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
got, err := dhcp.PickInternetInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// +build windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
list, err := getAdapterList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
if list.DhcpEnabled == 1 && list.LeaseObtained != 0 {
|
||||
ip := bytes.TrimRight(list.IpAddressList.IpAddress.String[:], string([]byte{0}))
|
||||
return &Iface{net.HardwareAddr(list.Address[:list.AddressLength]), net.ParseIP(string(ip))}, nil
|
||||
}
|
||||
|
||||
if list.Next != nil {
|
||||
list = list.Next
|
||||
} else {
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAdapterList() (*syscall.IpAdapterInfo, error) {
|
||||
b := make([]byte, 1000)
|
||||
l := uint32(len(b))
|
||||
a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
|
||||
// contains IPv4 address list only. We should use another API
|
||||
// for fetching IPv6 stuff from the kernel.
|
||||
err := syscall.GetAdaptersInfo(a, &l)
|
||||
if err == syscall.ERROR_BUFFER_OVERFLOW {
|
||||
b = make([]byte, l)
|
||||
a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
err = syscall.GetAdaptersInfo(a, &l)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("GetAdaptersInfo", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dhcp
|
||||
|
||||
import "net"
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/proxy/dhcp"
|
||||
"github.com/wweir/utils/log"
|
||||
)
|
||||
|
||||
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("detect 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 msg, err := dns.Exchange(r, relayServer); err != nil || msg == nil {
|
||||
server, err := pickRelayAddr(relayServer)
|
||||
if err != nil {
|
||||
log.Errorw("detect upstream dns", "err", err)
|
||||
} else if relayServer != server {
|
||||
relayServer = server
|
||||
log.Infow("detect upstream dns", "addr", relayServer)
|
||||
}
|
||||
|
||||
} else {
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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) {
|
||||
addr, _ = util.WithDefaultPort(addr, "80")
|
||||
return transport.Dial(serverAddr, addr, password, shouldProxy)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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(serverAddr, target, password, shouldProxy)
|
||||
if err != nil {
|
||||
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
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 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(serverAddr, target, passwordData, shouldProxy)
|
||||
if err != nil {
|
||||
log.Warnw("dial", "addr", serverAddr, "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(":80", "", ParseHTTP, shouldProxy)
|
||||
go relayToRemote(":443", "", 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.ToProxyConn(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)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/wweir/sower/parser"
|
||||
)
|
||||
|
||||
func StartServer(port string) {
|
||||
ln, err := quic.ListenAddr(":"+port, mockTlsPem(), nil)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
for {
|
||||
sess, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
go acceptSession(sess)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptSession(sess quic.Session) {
|
||||
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
|
||||
defer sess.Close()
|
||||
|
||||
for {
|
||||
stream, err := sess.AcceptStream()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
go acceptStream(stream, sess)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptStream(stream quic.Stream, sess quic.Session) {
|
||||
glog.V(1).Infoln("new stream from ", sess.RemoteAddr())
|
||||
defer stream.Close()
|
||||
|
||||
conn, addr, err := parser.ParseAddr(&streamConn{stream, sess})
|
||||
if err != nil {
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infoln(addr)
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil {
|
||||
glog.Warningln(err)
|
||||
}
|
||||
relay(rc, conn)
|
||||
}
|
||||
+36
-46
@@ -1,74 +1,64 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
type streamConn struct {
|
||||
quic.Stream
|
||||
sess quic.Session
|
||||
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 (s *streamConn) LocalAddr() net.Addr {
|
||||
return s.sess.LocalAddr()
|
||||
}
|
||||
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
func (s *streamConn) RemoteAddr() net.Addr {
|
||||
return s.sess.RemoteAddr()
|
||||
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(conn1, conn2, wg, exitFlag)
|
||||
redirect(conn2, conn1, wg, exitFlag)
|
||||
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)
|
||||
|
||||
func redirect(conn1, conn2 net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
|
||||
if _, err := io.Copy(conn2, conn1); err != nil && (atomic.LoadInt32(exitFlag) == 0) {
|
||||
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", conn1.RemoteAddr(), conn1.LocalAddr(), conn2.LocalAddr(), conn2.RemoteAddr(), err)
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
// wakeup blocked goroutine
|
||||
now := time.Now()
|
||||
src.SetDeadline(now)
|
||||
dst.SetDeadline(now)
|
||||
}
|
||||
|
||||
// wakeup all conn goroutine
|
||||
atomic.AddInt32(exitFlag, 1)
|
||||
now := time.Now()
|
||||
conn1.SetDeadline(now)
|
||||
conn2.SetDeadline(now)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func mockTlsPem() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
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, timeout time.Duration) error {
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(domain, p.String()), timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return p.PingWithConn(domain, conn, timeout)
|
||||
}
|
||||
|
||||
// PingWithConn try connect to a http(s) server with domain though the http addr
|
||||
func (p Port) PingWithConn(domain string, conn net.Conn, timeout time.Duration) error {
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
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
|
||||
_, err := conn.Read(make([]byte, 1))
|
||||
if err == nil || err == io.EOF {
|
||||
return 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()
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/utils/log"
|
||||
"github.com/wweir/utils/mem"
|
||||
)
|
||||
|
||||
// Route implement a router for each request
|
||||
type Route struct {
|
||||
shouldProxy bool
|
||||
port Port
|
||||
once sync.Once
|
||||
cache *mem.Cache
|
||||
|
||||
ProxyAddress string
|
||||
ProxyPassword string
|
||||
password []byte
|
||||
|
||||
DetectLevel int // dynamic detect proxy level
|
||||
DetectTimeout string // dynamic detect timeout
|
||||
timeout time.Duration
|
||||
|
||||
DirectList []string
|
||||
directRule *util.Node
|
||||
ProxyList []string
|
||||
proxyRule *util.Node
|
||||
DynamicList []string
|
||||
dynamicRule *util.Node
|
||||
PersistFn func(string)
|
||||
}
|
||||
|
||||
// ShouldProxy check if the domain shoule request though proxy
|
||||
func (r *Route) ShouldProxy(domain 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...)
|
||||
r.dynamicRule = util.NewNodeFromRules(r.DynamicList...)
|
||||
|
||||
if timeout, err := time.ParseDuration(r.DetectTimeout); err != nil {
|
||||
r.timeout = 300 * time.Millisecond
|
||||
log.Warnw("parse detect timeout", "err", err, "default", r.timeout)
|
||||
} else {
|
||||
r.timeout = timeout
|
||||
}
|
||||
})
|
||||
|
||||
// break deadlook, for wildcard
|
||||
if strings.Count(domain, ".") > 4 {
|
||||
return false
|
||||
}
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
|
||||
if domain == r.ProxyAddress {
|
||||
return false
|
||||
}
|
||||
if r.directRule.Match(domain) {
|
||||
return false
|
||||
}
|
||||
if r.proxyRule.Match(domain) {
|
||||
return true
|
||||
}
|
||||
if r.dynamicRule.Match(domain) {
|
||||
return true
|
||||
}
|
||||
|
||||
r.cache.Remember(r, domain)
|
||||
return r.dynamicRule.Match(domain)
|
||||
}
|
||||
|
||||
// Get implement for cache
|
||||
func (r *Route) Get(key interface{}) (err error) {
|
||||
domain := key.(string)
|
||||
|
||||
httpScore, httpsScore := r.detect(domain)
|
||||
log.Infow("detect", "domain", domain, "http", httpScore, "https", httpsScore)
|
||||
|
||||
if httpScore+httpsScore >= r.DetectLevel {
|
||||
r.dynamicRule.Add(domain)
|
||||
if r.PersistFn != nil {
|
||||
r.PersistFn(domain)
|
||||
}
|
||||
}
|
||||
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 [...]*Route{
|
||||
{shouldProxy: true, port: HTTP},
|
||||
{shouldProxy: true, port: HTTPS},
|
||||
{shouldProxy: false, port: HTTP},
|
||||
{shouldProxy: false, port: HTTPS},
|
||||
} {
|
||||
wg.Add(1)
|
||||
go func(ping *Route) {
|
||||
defer wg.Done()
|
||||
|
||||
target := net.JoinHostPort(domain, ping.port.String())
|
||||
conn, err := transport.Dial(r.ProxyAddress, target, r.password,
|
||||
func(string) bool { return r.shouldProxy })
|
||||
if err != nil {
|
||||
log.Errorw("sower dial", "addr", r.ProxyAddress, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ping.port.PingWithConn(domain, conn, r.timeout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case r.shouldProxy && ping.port == HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
case r.shouldProxy && ping.port == HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
|
||||
atomic.AddInt32(httpsScore, 1)
|
||||
}
|
||||
case !r.shouldProxy && ping.port == HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
case !r.shouldProxy && ping.port == HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
|
||||
atomic.AddInt32(httpsScore, -1)
|
||||
}
|
||||
}
|
||||
}(ping)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return int(*httpScore), int(*httpsScore)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// checksum(>=0x80) + port + target_length + target + data
|
||||
// data(HTTP/HTTPS, first byte < 0x7F)
|
||||
type head struct {
|
||||
Checksum byte
|
||||
Port uint16
|
||||
AddrLen uint8
|
||||
}
|
||||
|
||||
func ToProxyConn(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 DialTlsProxyConn(address, tgtHost string, tgtPort uint16, tlsCfg *tls.Config, password []byte) (net.Conn, error) {
|
||||
conn, err := tls.Dial("tcp", address, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
data := []byte(tgtHost)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func Dial(address, target string, password []byte, shouldProxy func(string) bool) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !shouldProxy(host) {
|
||||
return net.Dial("tcp", target)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
address, _ = util.WithDefaultPort(address, "443")
|
||||
// tls.Config is same as golang http pkg default behavior
|
||||
return DialTlsProxyConn(address, host, uint16(p), &tls.Config{}, password)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
type test struct {
|
||||
arg string
|
||||
want bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node *util.Node
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
|
||||
[]test{
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"parent",
|
||||
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"a.b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
{"c.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"iamp.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"a.b.wweir.cc", true},
|
||||
{"a.fuzz.com", true},
|
||||
{"b.fuzz.com", false},
|
||||
{"www.wweir.com", true},
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, test := range tt.tests {
|
||||
if got := tt.node.Match(test.arg); got != test.want {
|
||||
t.Errorf("Node.Match(%s) = %v, want %v", test.arg, got, test.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user