mirror of
https://github.com/wweir/sower.git
synced 2026-10-20 00:15:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -0,0 +1,51 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11.4
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- setup_remote_docker # install docker in docker
|
||||
- run:
|
||||
name: Prepare Environment
|
||||
command: |
|
||||
cp conf/sower.toml .
|
||||
cp deploy/* .
|
||||
mkdir artifacts
|
||||
- run:
|
||||
name: build linux Binary
|
||||
command: |
|
||||
make build
|
||||
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
- run:
|
||||
name: build macOS Binary
|
||||
command: |
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_darwin_amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
- run:
|
||||
name: build Windows Binary
|
||||
command: |
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_windows_amd64.tar.gz sower sower.toml
|
||||
- run:
|
||||
name: Publish Release on GitHub
|
||||
command: |
|
||||
if git log -1 --pretty='%s' | grep -oE '(^|\s)@release(\s|$)'; then
|
||||
GHR_VERSION="$(curl -s "https://api.github.com/repos/tcnksm/ghr/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
curl -SLf https://github.com/tcnksm/ghr/releases/download/"$GHR_VERSION"/ghr_"$GHR_VERSION"_linux_amd64.tar.gz | tar xzC /go/bin --strip-components=1
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
|
||||
fi
|
||||
- run:
|
||||
name: build docker image
|
||||
command: |
|
||||
if git log -1 --pretty='%s' | grep -oE '(^|\s)@release(\s|$)'; then
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
make image
|
||||
VERSION=$(git describe --tags)
|
||||
docker tag sower wweir/sower:${VERSION%-*-*}
|
||||
docker push wweir/sower:${VERSION%-*-*}
|
||||
docker tag sower wweir/sower:latest
|
||||
docker push wweir/sower:latest
|
||||
fi
|
||||
@@ -20,3 +20,4 @@ cert.pem
|
||||
**/cert.pem
|
||||
privkey.pem
|
||||
**/privkey.pem
|
||||
/sower.toml
|
||||
|
||||
@@ -6,17 +6,25 @@ generate:
|
||||
go generate ./...
|
||||
|
||||
build:
|
||||
GOBIN=$(PWD) go install -v
|
||||
go build -v -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
docker build -t sower -f deploy/Dockerfile .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
sudo $(PWD)/sower -f conf/sower.toml
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -n QUIC -logtostderr -v 1
|
||||
$(PWD)/sower -n TCP -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -n QUIC -logtostderr -v 1 &
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
$(PWD)/sower -n TCP -v 1 &
|
||||
sudo $(PWD)/sower -f conf/sower.toml &
|
||||
@sleep 1
|
||||
curl 127.0.0.1
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
# sower
|
||||
Yet another cross platform transparent proxy tool
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://circleci.com/gh/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
## architecture
|
||||
The sower is a cross-platform transparent proxy tool base on DNS solution.
|
||||
|
||||
If you wanna enjoy the sower, you need to deploy sower on both server and client side.
|
||||
On client side, sower listening UDP `53` and TCP `80`/`443` ports, so that you need run it with privileged.
|
||||
On server side, it just listening to a port (default `5533`), parse and relay the request to target server.
|
||||
|
||||
Sower also provides an http(s) proxy, which listens to port `8080` by default. You can turn it off or use another port at any time.
|
||||
|
||||
## Architecture
|
||||
For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-sower-%E6%8A%80%E6%9C%AF%E5%89%96%E6%9E%90/)
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
@@ -11,38 +24,63 @@ Yet another cross platform transparent proxy tool
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parsr http(s) target url | |
|
||||
| | parse http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| |
|
||||
| shadow service |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic connection +---------->
|
||||
quic / KCP / TCP +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| | |
|
||||
| shadow service | |
|
||||
| relay service | dns |
|
||||
| | server |
|
||||
| | service |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-------^----------+---^----^---+
|
||||
| | |
|
||||
| | | +----->
|
||||
| +----------+ | |
|
||||
+-^-----^----------+---^----^---+
|
||||
| | | |
|
||||
| | | | +----->
|
||||
http(s) proxy | +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
block request normal request
|
||||
blocked request normal request
|
||||
|
||||
```
|
||||
|
||||
## 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
|
||||
## Installation
|
||||
### Auto deploy
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
|
||||
## todo
|
||||
- [ ] authenticate
|
||||
- [ ] broker
|
||||
```shell
|
||||
$ bash -c "$(curl -s https://raw.githubusercontent.com/wweir/sower/master/deploy/install)"
|
||||
```
|
||||
|
||||
Then modify the configuration file as needed and set `127.0.0.1` as your first domain name server. In most situation, you just need to modify `/etc/resolv.conf`.
|
||||
|
||||
If you wanna uninstall sower, change `install` into `uninstall` and rerun the command.
|
||||
|
||||
### Manually deploy
|
||||
1. Download the precompiled file from https://github.com/wweir/sower/releases
|
||||
2. Decompression the file into a folder
|
||||
3. Run `./sower -h` for help
|
||||
5. Config domain name server
|
||||
4. Config auto start
|
||||
|
||||
### Docker deploy
|
||||
The auto build docker images are [wweir/sower](https://hub.docker.com/r/wweir/sower).
|
||||
|
||||
It is very simple to use it on the server side. Map the port and run it directly.
|
||||
|
||||
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
|
||||
|
||||
|
||||
## Todo
|
||||
- [x] authenticate
|
||||
- [ ] broker
|
||||
- [x] CI/CD
|
||||
- [ ] relay optimization
|
||||
- [ ] deploy script for all normal platform
|
||||
- [ ] dns rule dynamic configuration
|
||||
+14
-8
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -17,15 +18,17 @@ import (
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
Cipher string `toml:"cipher"`
|
||||
Password string `toml:"password"`
|
||||
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
|
||||
DnsServer string `toml:"dns_server"`
|
||||
DNSServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
ClientIPNet net.IP `toml:"-"`
|
||||
ClearDnsCache string `toml:"clear_dns_cache"`
|
||||
ClearDNSCache string `toml:"clear_dns_cache"`
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
@@ -33,19 +36,22 @@ var Conf = struct {
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "QUIC", "proxy net type (QUIC|KCP|TCP)")
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type (QUIC|KCP|TCP)")
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)")
|
||||
flag.StringVar(&Conf.Password, "p", "12345678", "password")
|
||||
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.HTTPProxy, "H", "", "http proxy listen addr")
|
||||
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")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
if Conf.ConfigFile == "" {
|
||||
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if err := OnRefreash[0](); err != nil {
|
||||
@@ -61,9 +67,9 @@ var OnRefreash = []func() error{func() error {
|
||||
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
|
||||
|
||||
// clear dns cache
|
||||
if Conf.ClearDnsCache != "" {
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil {
|
||||
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run(); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-10
@@ -1,12 +1,18 @@
|
||||
net_type="KCP"
|
||||
net_type="TCP"
|
||||
cipher="AES_128_GCM"
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
server_addr="remote-server:5533" # replce it to remote server
|
||||
dns_server="114.114.114.114"
|
||||
# client_ip="192.168.0.1"
|
||||
# clear_dns_cache="sudo killall -HUP mDNSResponder"
|
||||
# server_addr="remote-server:5533" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="223.5.5.5" # Alibaba public dns
|
||||
client_ip="127.0.0.1"
|
||||
# clear_dns_cache="killall -HUP mDNSResponder"
|
||||
blocklist=[
|
||||
"*.google.com", # google
|
||||
"*.google.com.hk",
|
||||
"*.google.ca",
|
||||
"*.google.jp",
|
||||
"*.google.tw",
|
||||
"ocsp.pki.goog",
|
||||
"g.co",
|
||||
"*.goo.gl",
|
||||
@@ -39,13 +45,15 @@ blocklist=[
|
||||
"synchrony-cdn.atlassian.com",
|
||||
"avatar-cdn.atlassian.com",
|
||||
"*.medium.com",
|
||||
"*.prod.public.atl-paas.net",
|
||||
"accounts-static.cdn.mozilla.net", # firefox
|
||||
"*.services.mozilla.com",
|
||||
"pocket-image-cache.com", # pocket
|
||||
"*.golang.org", # golang
|
||||
"go.googlesource.com",
|
||||
"*.googlesource.com",
|
||||
"godoc.org",
|
||||
"gist.github.com", # github
|
||||
"*.github.io",
|
||||
"*.k8s.io", # k8s
|
||||
"k8s.gcr.io",
|
||||
"*.wikipedia.org", # wikipeida
|
||||
@@ -56,7 +64,6 @@ blocklist=[
|
||||
"e16991.b.akamaiedge.net",
|
||||
"*.web.telegram.org", # telegram
|
||||
"core.telegram.org",
|
||||
"telegram.org",
|
||||
"telegram.me",
|
||||
"t.me",
|
||||
"*.slack.com", #slack
|
||||
@@ -64,12 +71,11 @@ blocklist=[
|
||||
"js.driftt.com",
|
||||
"cloud.feedly.com", # feedly
|
||||
"static.grammarly.com", # grammarly
|
||||
"denali-static.grammarly.com",
|
||||
"apkpure.com", # apkpure
|
||||
"download.apkpure.com",
|
||||
"*.nytimes.com", # New York times
|
||||
"*.nyt.com",
|
||||
"bandwagonhost.com", # bandwagonhost
|
||||
"www.bwh1.net",
|
||||
"*.akadns.net", # suggestions
|
||||
"*.haxx.se",
|
||||
"shadowsocks.org",
|
||||
@@ -78,6 +84,7 @@ blocklist=[
|
||||
"*.qualtrics.com",
|
||||
"*.starfieldtech.com",
|
||||
"*.typekit.net",
|
||||
"www.v2ray.com",
|
||||
"*.v2ray.com",
|
||||
"homebrew.bintray.com",
|
||||
]
|
||||
verbose=0
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Compile
|
||||
FROM golang:1.11-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /go-mod
|
||||
COPY . .
|
||||
|
||||
# do not worry about downloading dependency, sower will fix this.
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
|
||||
# Build image
|
||||
FROM scratch
|
||||
|
||||
COPY --from=compiler /go-mod/sower /sower
|
||||
ENTRYPOINT [ "/sower" ]
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>cc.wweir.sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/sower</string>
|
||||
<string>-f</string>
|
||||
<string>/usr/local/etc/sower.toml</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e
|
||||
[[ -d /tmp/sower ]] || mkdir /tmp/sower
|
||||
cd /tmp/sower
|
||||
|
||||
echo ======== Install sower to your system ========
|
||||
succ_message(){
|
||||
echo
|
||||
echo ======== Installed sower to your system ========
|
||||
echo The config file is: /usr/local/etc/sower.toml
|
||||
echo please set 127.0.0.1 as the first domain name server manually.
|
||||
}
|
||||
|
||||
# main logic
|
||||
VERSION="$(curl -s "https://api.github.com/repos/wweir/sower/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_darwin_amd64.tar.gz | tar xzv
|
||||
|
||||
mv sower /usr/local/bin/
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/# clear_dns_cache/clear_dns_cache/" sower.toml
|
||||
mv sower.toml /usr/local/etc/
|
||||
|
||||
echo
|
||||
echo Register auto start service, root privilege is needed!
|
||||
echo
|
||||
sudo mv cc.wweir.sower.plist /Library/LaunchDaemons/
|
||||
sudo chown root:wheel /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo launchctl load -w /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"Linux")
|
||||
if [[ "$(cat /proc/1/comm)" != "systemd" ]]; then
|
||||
echo do not support auto deploy on SysVinit
|
||||
exit 1
|
||||
fi
|
||||
printf "Server side or client side, which do you wanna install [c/s]: "
|
||||
read SIDE
|
||||
echo
|
||||
|
||||
case "$SIDE" in
|
||||
"c")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_linux_amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
|
||||
sudo mv sower-client.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-client
|
||||
sudo systemctl start sower-client
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"s")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_linux_amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
sudo mv sower-server.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-server
|
||||
sudo systemctl start sower-server
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "invalid: $SIDE" && exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Sower client service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -f /usr/local/etc/sower.toml
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Sower server service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -n TCP -v 1
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -x -e
|
||||
|
||||
# main logic
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
sudo launchctl unload /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo rm -rf /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
"Linux")
|
||||
sudo systemctl disable sower-client ||true
|
||||
sudo systemctl disable sower-server || true
|
||||
sudo rm -rf /etc/systemd/system/sower-client.service /etc/systemd/system/sower-server.service
|
||||
sudo rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
+10
-10
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer string) {
|
||||
func StartDNS(dnsServer, listenIP string, ipNet net.IP) {
|
||||
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 {
|
||||
@@ -30,17 +30,17 @@ func StartDNS(dnsServer string) {
|
||||
}
|
||||
|
||||
if len(conf.Conf.BlockList) == 0 {
|
||||
bestTry(w, r, domain, dnsServer)
|
||||
bestTry(w, r, domain, dnsServer, ipNet)
|
||||
} else {
|
||||
manual(w, r, domain, dnsServer)
|
||||
manual(w, r, domain, dnsServer, ipNet)
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
server := &dns.Server{Addr: listenIP + ":53", Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string, ipNet net.IP) {
|
||||
msg, _ := dns.Exchange(r, dnsServer+":53")
|
||||
if msg == nil {
|
||||
return
|
||||
@@ -63,16 +63,16 @@ func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
|
||||
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
|
||||
glog.V(2).Infoln(ip+":80", err)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string, ipNet net.IP) {
|
||||
if rule.Match(strings.TrimSuffix(domain, ".")) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,12 +100,12 @@ func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
}
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string) *dns.Msg {
|
||||
func localA(r *dns.Msg, domain string, localIP net.IP) *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,
|
||||
A: localIP,
|
||||
}}
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@ require (
|
||||
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/golang/mock v1.2.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.7.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.8.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/miekg/dns v1.1.3
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible
|
||||
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/xtaci/kcp-go v5.0.7+incompatible
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc
|
||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
golang.org/x/sys v0.0.0-20190114130336-2be517255631 // indirect
|
||||
)
|
||||
|
||||
@@ -8,8 +8,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
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/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/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=
|
||||
@@ -18,23 +18,23 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
|
||||
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/klauspost/reedsolomon v1.8.0 h1:lvvOkvk64cE1EGbBIgFk7WSOOsI1GexpuLiT7zjab6g=
|
||||
github.com/klauspost/reedsolomon v1.8.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
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/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
|
||||
github.com/miekg/dns v1.1.3/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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
||||
@@ -43,19 +43,19 @@ github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
||||
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible h1:vM5gqHI8p5xa3eXQH279+16fG86gCz/IRamaQvWG+a8=
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
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=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/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/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
|
||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/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/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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/sys v0.0.0-20190114130336-2be517255631 h1:g/5trXm6f9Tm+ochb21RlFNnF63lt+elB9hVBqtPu5Y=
|
||||
golang.org/x/sys v0.0.0-20190114130336-2be517255631/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
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=
|
||||
|
||||
@@ -7,14 +7,22 @@ import (
|
||||
"github.com/wweir/sower/proxy"
|
||||
)
|
||||
|
||||
var version, date string
|
||||
|
||||
func main() {
|
||||
conf := conf.Conf
|
||||
glog.Infoln("Starting:", conf)
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, conf)
|
||||
|
||||
if conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Password)
|
||||
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Cipher, conf.Password)
|
||||
|
||||
} else {
|
||||
go dns.StartDNS(conf.DnsServer)
|
||||
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Password)
|
||||
if conf.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(conf.NetType, conf.ServerAddr,
|
||||
conf.Cipher, conf.Password, conf.HTTPProxy)
|
||||
}
|
||||
|
||||
go dns.StartDNS(conf.DNSServer, conf.ClientIP, conf.ClientIPNet)
|
||||
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-13
@@ -14,18 +14,25 @@ type Client interface {
|
||||
Dial(server string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func StartClient(netType, server, password string) {
|
||||
var connCh = listenLocal([]string{":80", ":443"})
|
||||
var client Client
|
||||
func NewClient(netType string) Client {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
client = quic.NewClient()
|
||||
return quic.NewClient()
|
||||
case KCP.String():
|
||||
client = kcp.NewClient()
|
||||
return kcp.NewClient()
|
||||
case TCP.String():
|
||||
client = tcp.NewClient()
|
||||
return tcp.NewClient()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartClient(netType, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{":80", ":443"})
|
||||
client := NewClient(netType)
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
@@ -36,20 +43,17 @@ func StartClient(netType, server, password string) {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rc, err = shadow.Shadow(rc, password); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
go relay(conn, rc)
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(ports []string) <-chan net.Conn {
|
||||
func listenLocal(listenIP string, 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)
|
||||
ln, err := net.Listen("tcp", listenIP+port)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
@@ -57,10 +61,11 @@ func listenLocal(ports []string) <-chan net.Conn {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", port, "fail:", err)
|
||||
glog.Errorln("accept", listenIP+port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}(ports[i])
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/shadow"
|
||||
)
|
||||
|
||||
func StartHttpProxy(netType, server, cipher, password, addr string) {
|
||||
client := NewClient(netType)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, client, server, cipher, password)
|
||||
} else {
|
||||
httpProxy(w, r, client, server, cipher, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, req *http.Request, client Client, server, cipher, password string) {
|
||||
roundTripper := &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
conn, err := client.Dial(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shadow.Shadow(conn, cipher, password), nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := roundTripper.RoundTrip(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, get remote data:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
for k, vs := range resp.Header {
|
||||
for _, v := range vs {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request, client Client, server, cipher, password string) {
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, write data fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// remote conn
|
||||
rc, err := client.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ type server struct {
|
||||
SockBuf int
|
||||
}
|
||||
|
||||
func NewServer(password string) *server {
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
|
||||
+11
-5
@@ -16,11 +16,12 @@ type client struct {
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
// conf: &quic.Config{
|
||||
// HandshakeTimeout: 5 * time.Second,
|
||||
// MaxIncomingStreams: 1024,
|
||||
// KeepAlive: true,
|
||||
// },
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +30,11 @@ func (c *client) Dial(server string) (net.Conn, error) {
|
||||
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
|
||||
return nil, errors.Wrap(err, "session")
|
||||
} else {
|
||||
go func() {
|
||||
<-sess.Context().Done()
|
||||
sess.Close()
|
||||
c.sess = nil
|
||||
}()
|
||||
c.sess = sess
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package quic
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
@@ -14,11 +15,12 @@ type server struct {
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
// conf: &quic.Config{
|
||||
// HandshakeTimeout: 5 * time.Second,
|
||||
// MaxIncomingStreams: 1024,
|
||||
// KeepAlive: true,
|
||||
// },
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-14
@@ -16,34 +16,37 @@ type Server interface {
|
||||
Listen(port string) (<-chan net.Conn, error)
|
||||
}
|
||||
|
||||
func StartServer(netType, port, password string) {
|
||||
var server Server
|
||||
func NewServer(netType string) Server {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
server = quic.NewServer()
|
||||
return quic.NewServer()
|
||||
case KCP.String():
|
||||
server = kcp.NewServer(password)
|
||||
return kcp.NewServer()
|
||||
case TCP.String():
|
||||
server = tcp.NewServer()
|
||||
return tcp.NewServer()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer(netType, port, cipher, password string) {
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
if !strings.Contains(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
connCh, err := server.Listen(port)
|
||||
|
||||
connCh, err := NewServer(netType).Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
|
||||
glog.Infoln("Server started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
conn, err := shadow.Shadow(conn, password)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
conn = shadow.Shadow(conn, cipher, password)
|
||||
|
||||
go handle(conn)
|
||||
}
|
||||
@@ -52,6 +55,7 @@ func StartServer(netType, port, password string) {
|
||||
func handle(conn net.Conn) {
|
||||
conn, addr, err := parse.ParseAddr(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
@@ -59,13 +63,11 @@ func handle(conn net.Conn) {
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil {
|
||||
glog.Warningln(err)
|
||||
}
|
||||
rc.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
|
||||
+7
-1
@@ -10,5 +10,11 @@ func NewClient() *client {
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
return net.Dial("tcp", server)
|
||||
conn, err := net.Dial("tcp", server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
glog.Fatalln("TCP listen:", err)
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=cipherType $GOFILE
|
||||
type cipherType int
|
||||
|
||||
const (
|
||||
AES_128_GCM cipherType = iota
|
||||
AES_192_GCM
|
||||
AES_256_GCM
|
||||
CHACHA20_IETF_POLY1305
|
||||
XCHACHA20_IETF_POLY1305
|
||||
)
|
||||
|
||||
func pickCipher(cipherType, password string) (cipher.AEAD, error) {
|
||||
var blockSize int
|
||||
switch cipherType {
|
||||
case AES_128_GCM.String():
|
||||
blockSize = 16
|
||||
case AES_192_GCM.String():
|
||||
blockSize = 24
|
||||
case AES_256_GCM.String():
|
||||
blockSize = 32
|
||||
|
||||
case CHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.New(genKey(password, 256))
|
||||
case XCHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.NewX(genKey(password, 256))
|
||||
|
||||
default:
|
||||
return nil, errors.New("do not support cipher type: " + cipherType)
|
||||
}
|
||||
|
||||
// aes gcm
|
||||
block, err := aes.NewCipher(genKey(password, blockSize))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "password")
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, AES_128_GCM.String())
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
func genKey(filler string, size int) []byte {
|
||||
res := make([]byte, size)
|
||||
if filler == "" {
|
||||
panic("password should not be empty")
|
||||
}
|
||||
|
||||
fillerByte := []byte(filler)
|
||||
length := len(fillerByte)
|
||||
for i := 0; ; i++ {
|
||||
if copy(res[i*length:], fillerByte) != length {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Code generated by "stringer -type=cipherType cipher.go"; DO NOT EDIT.
|
||||
|
||||
package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305"
|
||||
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78}
|
||||
|
||||
func (i cipherType) String() string {
|
||||
if i < 0 || i >= cipherType(len(_cipherType_index)-1) {
|
||||
return "cipherType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _cipherType_name[_cipherType_index[i]:_cipherType_index[i+1]]
|
||||
}
|
||||
+3
-5
@@ -1,6 +1,4 @@
|
||||
// transter conn to be a crypto conn
|
||||
// stream mode, blocksize is 1350, for mtu
|
||||
// data [0 ... 1332]
|
||||
// block [size][2 ... 1350]
|
||||
// payload size + data size + aead overhead => (2+1332+16)
|
||||
// Package shadow transter conn to be a crypto conn
|
||||
// support aead mode only
|
||||
// payload: [size](2+Overhead bytes) + [content](size+Overhead bytes)
|
||||
package shadow
|
||||
|
||||
+54
-80
@@ -1,128 +1,102 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.Ltime | log.Lshortfile)
|
||||
}
|
||||
|
||||
var MTU = 1350 //MTU must little than 0xFFFF
|
||||
const MAX_SIZE = 0xFFFF
|
||||
|
||||
type conn struct {
|
||||
dataSize int
|
||||
maxSize int
|
||||
aead cipher.AEAD
|
||||
encryptNonce func() []byte
|
||||
decryptNonce func() []byte
|
||||
writeBuf []byte
|
||||
readBuf []byte
|
||||
readOffset int
|
||||
readLast int
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
bLength := len(b)
|
||||
offset := 0
|
||||
switch c.readOffset {
|
||||
case -1:
|
||||
return 0, io.EOF
|
||||
case 0: // read from conn
|
||||
default: // read from buffer
|
||||
offset = copy(b, c.readBuf[c.readOffset:c.readLast])
|
||||
if offset+c.readOffset < c.readLast {
|
||||
c.readOffset += offset
|
||||
return offset, nil
|
||||
}
|
||||
// read from buffer
|
||||
if c.readOffset != 0 {
|
||||
dataSize := len(c.readBuf) - c.aead.Overhead()
|
||||
n = copy(b, c.readBuf[c.readOffset:dataSize])
|
||||
c.readOffset += n
|
||||
|
||||
if c.readLast < c.dataSize {
|
||||
c.readOffset = -1
|
||||
return offset, io.EOF
|
||||
if c.readOffset == dataSize {
|
||||
c.readOffset = 0
|
||||
}
|
||||
c.readOffset = 0
|
||||
return
|
||||
}
|
||||
|
||||
// read from conn
|
||||
for ; offset < bLength; offset += c.readOffset {
|
||||
_, err = io.ReadFull(c.Conn, c.readBuf)
|
||||
if err != nil && err != io.EOF {
|
||||
c.readOffset = 0
|
||||
return offset, err
|
||||
dataSize := 0
|
||||
{ //read data size
|
||||
c.readBuf = make([]byte, 2+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, e := c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil)
|
||||
if e != nil {
|
||||
c.readOffset = 0
|
||||
return offset, e
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.readLast = int(c.readBuf[0])<<8 + int(c.readBuf[1]) + 2
|
||||
c.readOffset = copy(b[offset:], c.readBuf[2:2+c.readLast])
|
||||
if c.readOffset < c.readLast {
|
||||
c.readOffset += 2
|
||||
return len(b), nil
|
||||
} else if err == io.EOF {
|
||||
readOffset := c.readOffset
|
||||
c.readOffset = 0
|
||||
return readOffset, err
|
||||
dataSize = int(c.readBuf[0])<<8 + int(c.readBuf[1])
|
||||
}
|
||||
{ // read data
|
||||
c.readBuf = make([]byte, dataSize+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return len(b), err
|
||||
|
||||
// buffer extra data
|
||||
if n = copy(b, c.readBuf[:dataSize]); n < dataSize {
|
||||
c.readOffset = n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
bLength := len(b)
|
||||
size := 0
|
||||
|
||||
for offset := 0; offset < bLength; offset += size {
|
||||
if offset+c.dataSize <= bLength {
|
||||
size = c.dataSize
|
||||
} else {
|
||||
size = bLength - offset
|
||||
}
|
||||
|
||||
// BigEndian
|
||||
c.writeBuf[0], c.writeBuf[1] = byte((size&0xFF00)>>8), byte(size&0xFF)
|
||||
copy(c.writeBuf[2:size+2], b[offset:offset+size])
|
||||
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:c.dataSize+2], nil)
|
||||
|
||||
_, err = c.Conn.Write(c.writeBuf)
|
||||
if err != nil {
|
||||
return offset, err
|
||||
}
|
||||
bLen := len(b)
|
||||
dataSize := MAX_SIZE - (2 + c.aead.Overhead()) - c.aead.Overhead()
|
||||
if bLen < c.maxSize {
|
||||
dataSize = bLen
|
||||
}
|
||||
return len(b), err
|
||||
|
||||
// BigEndian
|
||||
c.writeBuf[0], c.writeBuf[1] = byte((dataSize&0xFF00)>>8), byte(dataSize&0xFF)
|
||||
|
||||
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil)
|
||||
c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil)
|
||||
|
||||
_, err = c.Conn.Write(c.writeBuf[:dataSize+(2+c.aead.Overhead())+c.aead.Overhead()])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dataSize, err
|
||||
}
|
||||
|
||||
func Shadow(c net.Conn, password string) (net.Conn, error) {
|
||||
block, err := aes.NewCipher([]byte(password + password)[:16])
|
||||
func Shadow(c net.Conn, cipher, password string) net.Conn {
|
||||
aead, err := pickCipher(cipher, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "password too short")
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GCM")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &conn{
|
||||
dataSize: MTU - aead.Overhead() - 2,
|
||||
maxSize: MAX_SIZE - (2 - aead.Overhead()) - aead.Overhead(),
|
||||
aead: aead,
|
||||
encryptNonce: newNonce(password, aead.NonceSize()),
|
||||
decryptNonce: newNonce(password, aead.NonceSize()),
|
||||
writeBuf: make([]byte, MTU),
|
||||
readBuf: make([]byte, MTU),
|
||||
writeBuf: make([]byte, 0xFFFF),
|
||||
Conn: c,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newNonce(password string, size int) func() []byte {
|
||||
|
||||
Reference in New Issue
Block a user