Compare commits

...
135 Commits
Author SHA1 Message Date
wweir d7e25d6565 Update dependency 2019-09-26 06:51:00 +08:00
wweir acb3f58526 Update go.yml 2019-09-11 19:01:24 +08:00
wweir 50684272c9 Update go.yml 2019-09-11 18:13:48 +08:00
wweir b0428177de Support Windows install with relative path 2019-06-24 10:41:46 +08:00
wweir 75e7f1aab7 Update dependency 2019-05-31 14:36:11 +08:00
wweir f256ab686d Fix default config file path 2019-05-05 09:32:23 +08:00
wweir 0c0f0699c1 Change config 2019-04-18 14:03:53 +08:00
wweir 8532a2e16c Merge branch 'feature/more-protocol' 2019-04-16 08:08:26 +08:00
wweir bad338033c Fix code 2019-04-16 08:04:37 +08:00
wweir a57f85ab1b Changes for unit test 2019-04-04 10:57:25 +08:00
wweir aed2afbe6d Fix protocol 2019-04-04 10:57:25 +08:00
wweir 2becd42176 Fix protocol 2019-03-31 08:06:00 +08:00
wweir d3664dc2b9 Add more protocols support 2019-03-30 12:31:02 +08:00
wweir a6fd60b5a2 Fix proxy port 2019-03-30 07:33:10 +08:00
wweir d6dbfd4129 Fix windows refresh hook cmd 2019-03-28 09:51:45 +08:00
wweir d31bbf9226 Fix #12, add print version flag 2019-03-27 21:22:52 +08:00
wweir ac4e4266bc Fix deploy script for #12 2019-03-27 15:36:49 +08:00
wweir f9f0ca4ee7 Merge branch 'bug/suggest' 2019-03-27 15:23:32 +08:00
wweir 98c3934762 Optimize suggest trigger 2019-03-27 15:22:25 +08:00
wweir f001f7dab7 Precompile more platform binary file 2019-03-24 18:44:27 +08:00
wweir 36ef329b68 Update todo list 2019-03-24 18:40:34 +08:00
wweir 64778f68ac Merge branch 'feature/socsk5' 2019-03-24 00:16:02 +08:00
wweir 53748cfa85 Support set socsk5 as upstream proxy 2019-03-24 00:15:08 +08:00
wweir d35115b7b3 Fix for vet 2019-03-20 08:48:46 +08:00
wweir 559c5fde6b Merge branch 'feature/dhcp' 2019-03-20 08:45:50 +08:00
wweir eb8080df18 Avoid unnecessary dhcp message 2019-03-20 08:45:37 +08:00
wweir 45ca063a7e Add timeout for dns exchange 2019-03-19 13:30:36 +08:00
wweir 6add7afd19 Fix for go modules 2019-03-15 11:46:51 +08:00
wweir f13687506e Merge branch 'feature/suggest-level' 2019-03-14 08:41:50 +08:00
wweir 199e4cf295 For #11, expose suggest level option 2019-03-14 08:38:18 +08:00
wweir b3ad9f6657 Fix panic in special network environment 2019-03-12 11:53:58 +08:00
wweir f9e9fe366f Fix log message 2019-03-12 11:53:58 +08:00
wweir 103699e25e Add docker build badge 2019-03-11 10:25:43 +08:00
wweir f80755eae5 Shorten install file url 2019-03-11 07:31:27 +08:00
wweir 5b217b794d Merge branch 'refactor/codes' 2019-03-11 00:22:15 +08:00
wweir 26055c5203 Refactor deploy 2019-03-11 00:11:07 +08:00
wweir 029611c078 Refactor proxy 2019-03-11 00:09:02 +08:00
wweir 89adc6a2c9 Change code style 2019-03-10 21:03:11 +08:00
wweir 8c95c59e5e Merge branch 'bug/dhcp' 2019-03-10 13:28:50 +08:00
wweir 4f8376c041 Fix #7, refactor dhcp implementation for dynamic DNS on Windows 2019-03-10 13:26:33 +08:00
wweir 10172f8be2 Build docker image in docker hub 2019-03-08 21:55:18 +08:00
wweir 2831cdf284 Add Windows flush dns cmd 2019-03-07 07:43:22 +08:00
wweir 06558ac635 Optimize http ping score logic 2019-03-06 11:06:37 +08:00
wweir 3cf4deddb7 Merge branch 'bug/ci-windows' 2019-03-06 08:13:10 +08:00
wweir 679263be9a Fix #8, fix ci script 2019-03-06 08:12:48 +08:00
wweir 21d8ec4ec7 Merge branch 'refactor/http-ping' 2019-03-06 07:44:17 +08:00
wweir 4916245522 Optimize http ping logic 2019-03-06 07:43:45 +08:00
wweir 11c2808320 Add description 2019-03-02 10:25:17 +08:00
wweir 21ddd79e4d Fix deploy script 2019-03-02 09:29:21 +08:00
wweir 9a158d4088 Optimize QUIC paramaters 2019-03-02 09:22:31 +08:00
wweir 88990cddd9 Update dependency 2019-03-02 09:07:20 +08:00
wweir ca1b2650ab Optimize install script 2019-03-02 08:07:01 +08:00
wweir 8ff1046ebf Merge branch 'bug/suffix-tree' 2019-02-28 17:38:52 +08:00
wweir dfe81f6f23 Fix suffix tree match logic 2019-02-28 17:38:34 +08:00
wweir 972875ec36 Optimize default rules 2019-02-28 16:14:40 +08:00
wweir 89b7a41d46 Add rule for mail 2019-02-28 15:40:57 +08:00
wweir 8f598dbc17 Optimize ci workflow 2019-02-27 14:28:54 +08:00
wweir 192b4f798b Optimize suggest logic 2019-02-27 14:16:02 +08:00
wweir 2adefe0dfc Fix clear dns command for windows 2019-02-26 17:33:39 +08:00
wweir 7b0c13ef1c Merge branch 'refactor/watch-config' 2019-02-24 18:20:31 +08:00
wweir 63bb934148 Release config file immediatly 2019-02-24 18:18:43 +08:00
wweir cacc732a4e Remove fsnotify, add refresh trigger for config 2019-02-24 17:43:17 +08:00
wweir 17d079dcb2 Fix #3, Merge branch 'feature/windows_svc' 2019-02-24 16:12:05 +08:00
wweir 413a93f6f7 Support Windows service deploy 2019-02-24 16:10:19 +08:00
wweir 6c5f5cd74f Add windows service support 2019-02-24 15:58:38 +08:00
wweir e948541efe Merge branch 'chore/code' 2019-02-19 16:53:21 +08:00
wweir ffa2128274 Move code files path 2019-02-19 16:53:08 +08:00
wweir b53b7874b6 Update README.md 2019-02-19 16:24:47 +08:00
wweir f3117edfa0 For macOS without brew 2019-02-19 13:04:36 +08:00
wweir ba8980da73 Limit dns detect timeout short than 1s for multi dns setting 2019-02-17 07:29:23 +08:00
wweir e3ec74b74f Merge branch 'bug/resolve-addr' 2019-02-16 22:41:28 +08:00
wweir 6e67193894 Release on tag 2019-02-16 22:40:54 +08:00
wweir af0c5f895c Fix resolve for dhcp 2019-02-16 22:26:20 +08:00
wweir 4883e240cc Merge branch 'feature/score' @release 2019-02-15 22:48:06 +08:00
wweir 18ee9ca993 Refactor intelligent detect to score logic 2019-02-15 22:47:51 +08:00
wweir 4fff2a1f3b Merge branch 'bug/dhcp-linux' @release 2019-02-15 00:28:53 +08:00
wweir 3e82890c33 Fix dhcp conflict with dhcpcd and dhclient 2019-02-15 00:27:03 +08:00
wweir 01d66d737b Merge branch 'bug/suggest' @release 2019-02-13 21:08:00 +08:00
wweir e342dadaf1 Fix http ping logic for suggestions 2019-02-13 21:07:40 +08:00
wweir 7ba9f1a23c Fix intelligent detect @release 2019-02-11 20:06:21 +08:00
wweir 7240208cb3 Merge branch 'bug/dhcp' @release 2019-02-02 08:35:33 +08:00
wweir a27748d835 Fix race 2019-02-02 08:35:16 +08:00
wweir 39797d077b Merge branch 'feature/dhcp' @release 2019-02-02 06:22:29 +08:00
wweir b6df69358a Fix #2, add dhcp dynamic setting upstream dns, ipv4 only 2019-02-02 06:21:51 +08:00
wweir ae525f9b40 Fix wait timeout 2019-01-31 07:36:41 +08:00
wweir 29eb0e6e1c Fix toml spell issue 2019-01-31 06:51:57 +08:00
wweir aef941b17e Add a reminder to check the configuration 2019-01-31 06:50:26 +08:00
wweir 9bc5a32074 Add more fuzz match support @release 2019-01-29 13:34:52 +08:00
wweir 8194083f54 Add more fuzz rules support @release 2019-01-28 22:41:07 +08:00
wweir c20903137d Fix #5, add ipv6 support @release 2019-01-28 16:54:27 +08:00
wweir 2f3c98b8d3 Clean code 2019-01-27 08:35:12 +08:00
wweir bc99bda1b3 Clear unnessary block rule 2019-01-25 23:13:48 +08:00
wweir ceedef4203 Add https ping @release 2019-01-25 22:46:28 +08:00
wweir 712b689708 Force enable go modules 2019-01-25 16:13:22 +08:00
wweir d5260d66df Fix match rule 2019-01-25 15:27:56 +08:00
wweir bde27f64ec Fix 433 detect 2019-01-23 22:31:37 +08:00
wweir 04193eb608 Change intelligent detect logic @release 2019-01-23 20:10:41 +08:00
wweir 41a1adc707 Fix intelligent detect logic @release 2019-01-23 12:52:34 +08:00
wweir c9191ec928 Fix intelligent detect logic @release 2019-01-23 11:36:34 +08:00
wweir 14da5b711e Fix intelligent logic @release 2019-01-23 07:33:28 +08:00
wweir 59257c0673 Add description about intelligent detect @release 2019-01-22 22:25:45 +08:00
wweir 7db92a19be Fix intelligent detect logic @release 2019-01-22 22:08:59 +08:00
wweir fbae2999dc Change deploy script to keep suggestions @release 2019-01-22 21:24:33 +08:00
wweir 92991da6a0 Fix rule for github network speed 2019-01-22 21:16:25 +08:00
wweir b60b5fceb3 Fix deadloop in wildcard dns setting case @release 2019-01-22 20:54:27 +08:00
wweir 756a8c8d17 Add intelligent rule suggestions @release 2019-01-22 20:12:47 +08:00
wweir e02da26936 Refactor for intelligent dns rule 2019-01-21 13:55:55 +08:00
wweir c5adcc4421 Fix permission @release 2019-01-21 11:22:38 +08:00
wweir c20c3621c6 Change release filenames @release 2019-01-21 11:19:31 +08:00
wweir 055c1a2901 Fix conn leak @release 2019-01-21 10:57:48 +08:00
wweir fc2ea8b8c0 Update default config file path 2019-01-20 21:41:29 +08:00
wweir 3e6b9e96ca Add docs link 2019-01-20 17:47:57 +08:00
wweir 2264d73c45 Add docker deploy description 2019-01-19 18:52:34 +08:00
wweir 99ba06b2d4 Add auto deploy and undeploy description 2019-01-19 18:40:02 +08:00
wweir a8e026ff7f For user friendly @release 2019-01-19 16:07:55 +08:00
wweir 9e377e4a2d Add deploy script for macOS and Linux platform 2019-01-19 15:33:00 +08:00
wweir 107174fb6b Fix badge link 2019-01-19 12:55:57 +08:00
wweir 3f8f5c2ef1 Fix badge display 2019-01-19 12:43:21 +08:00
wweir 918b964b78 Fix CI docker image tag 2019-01-19 12:37:05 +08:00
wweir 34722a460c Fix docker image and add release toggle option 2019-01-19 12:13:12 +08:00
wweir 8734486c48 Add CI release support 2019-01-19 11:58:33 +08:00
wweir 8d0ee2a9ea Add ci scripts 2019-01-19 11:03:32 +08:00
wweir 98247845ad Clean code 2019-01-19 08:10:11 +08:00
wweir 72dacaf1de Fix telegram config 2019-01-16 23:24:10 +08:00
wweir 56d5090841 Separate http_proxy interface setting for some case 2019-01-16 22:37:18 +08:00
wweir 821cbc302e Add http proxy support 2019-01-16 18:42:03 +08:00
wweir f6eddb9f96 For #1, support for specified a network interface 2019-01-16 14:40:06 +08:00
wweir 344531c301 Clean code 2019-01-16 07:26:59 +08:00
wweir df897f72c6 Fix quic session close 2019-01-15 22:55:50 +08:00
wweir dd2fe95900 update readme architecture 2019-01-15 19:15:06 +08:00
wweir 08a5d4363b Fix dead loop 2019-01-15 19:06:16 +08:00
wweir e712dcb78c Add multi cipher support 2019-01-15 19:00:12 +08:00
wweir e9f5fc4789 Extend quic idle timeout 2019-01-15 08:26:06 +08:00
wweir f76f31d054 Update dependency 2019-01-15 08:25:48 +08:00
wweir e0de537042 Clean dirty code 2019-01-15 08:04:54 +08:00
62 changed files with 2904 additions and 900 deletions
+18
View File
@@ -0,0 +1,18 @@
# Compile
FROM golang:1.12-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" ]
+18
View File
@@ -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>
+57
View File
@@ -0,0 +1,57 @@
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.13
steps:
- checkout # check out the code in the project directory
- run:
name: Prepare Environment
command: |
cp conf/sower.toml .
cp .circleci/* .
mkdir artifacts
- run:
name: Run Unit Test
command: |
make test
- run:
name: Build linux Binary
command: |
GOOS=linux GOARCH=amd64 make build
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=386 make build
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm make build
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm64 make build
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mips make build
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mipsle make build
tar czvf artifacts/sower-linux-mipsle.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.exe sower.toml
GOOS=windows GOARCH=386 make build
tar czvf artifacts/sower-windows-386.tar.gz sower.exe sower.toml
- run:
name: Publish Release on GitHub
command: |
git describe --tags | grep - && exit
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/
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -e
[[ -d /tmp/sower ]] || mkdir /tmp/sower
cd /tmp/sower
echo ======== Install sower to your system ========
succ_message(){
if [[ -z $IP ]]; then
IP=127.0.0.1
fi
echo
echo ======== Installed sower to your system ========
echo The config file is: /usr/local/etc/sower.toml
echo please set $IP 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")
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-darwin-amd64.tar.gz | tar xzv
sudo mkdir -p /usr/local/bin/
sudo mv sower /usr/local/bin/
if [[ -r /usr/local/etc/sower.toml ]]; then
echo The config file already exists, keep the original file
else
printf "Please enter remote server adddress: "
read ADDRESS
printf "Please enter remote server password (default: 12345678): "
read PASSWORD
if [[ -z $PASSWORD ]]; then
PASSWORD="12345678"
fi
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
read IP
if [[ -z $IP ]]; then
IP="127.0.0.1"
fi
echo
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
sed -i~ "s/# clear_dns_cache/clear_dns_cache/" sower.toml
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
sudo mkdir -p /usr/local/etc/
sudo mv sower.toml /usr/local/etc/
fi
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")
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower-linux-amd64.tar.gz | tar xzv
sudo mv sower /usr/local/bin/
if [[ -r /usr/local/etc/sower.toml ]]; then
echo The config file already exists, keep the original file
else
printf "Please enter remote server adddress: "
read ADDRESS
printf "Please enter remote server password (default: 12345678): "
read PASSWORD
if [[ -z $PASSWORD ]]; then
PASSWORD="12345678"
fi
printf "Please enter which IP do you wanna listen (default: 127.0.0.1):"
read IP
if [[ -z $IP ]]; then
IP="127.0.0.1"
fi
echo
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
sed -i~ "s/client_ip=\"127.0.0.1\"/client_ip=\"$IP\"/" sower.toml
sed -i~ "s/\"12345678\"/\"$PASSWORD\"/" sower.toml
sudo mv sower.toml /usr/local/etc/
fi
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
printf "Please enter remote server password (default: 12345678): "
read PASSWORD
echo
if [[ -z $PASSWORD ]]; then
PASSWORD="12345678"
fi
sudo mv sower /usr/local/bin/
sed -i~ "s%bin/sower%bin/sower -p $PASSWORD%" sower-server.service
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
+14
View File
@@ -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
+14
View File
@@ -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
+20
View File
@@ -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
+37
View File
@@ -0,0 +1,37 @@
name: Go
on: [push]
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: build matrix
run: |
GOOS=linux GOARCH=amd64 make build
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=386 make build
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm make build
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=arm64 make build
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mips make build
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=linux GOARCH=mipsle make build
tar czvf artifacts/sower-linux-mipsle.tar.gz sower sower.toml sower-client.service sower-server.service
GOOS=darwin GOARCH=amd64 make build
tar czvf artifacts/sower-darwin-amd64.tar.gz sower sower.toml cc.wweir.sower.plist
+5 -5
View File
@@ -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
+28 -11
View File
@@ -1,26 +1,43 @@
export GO111MODULE=on
SERVER:=127.0.0.1:5533
default: build
default: test build
generate:
ifeq ("", "$(shell which stringer)")
@echo installing generator
go get -v golang.org/x/tools/cmd/stringer
endif
go generate ./...
test:
go vet ./...
go test ./...
build:
GOBIN=$(PWD) go install -v
go build -ldflags \
"-X main.version=$(shell git describe --tags) \
-X main.date=$(shell date +%Y-%m-%d)"
image:
docker build -t sower -f .circleci/Dockerfile .
kill:
sudo pkill -9 sower || true
client: build kill
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
sudo $(PWD)/sower -s 127.0.0.1:5533 -H "127.0.0.1:8080"
server: build kill
$(PWD)/sower -n QUIC -logtostderr -v 1
server: build
$(PWD)/sower -f ''
run: build kill
$(PWD)/sower -n QUIC -logtostderr -v 1 &
sudo $(PWD)/sower -f conf/sower.toml -logtostderr &
sleep 1
@curl 127.0.0.1
sleep 1
sudo pkill -9 sower || true
$(PWD)/sower -f '' &
sudo $(PWD)/sower -f '' -s 127.0.0.1:5533 -H "127.0.0.1:8080" &
@sleep 1
HTTP_PROXY=http://127.0.0.1:8080 curl http://baidu.com || true
@echo
HTTPS_PROXY=http://127.0.0.1:8080 curl https://baidu.com || true
@echo
@sleep 1
@sudo pkill -9 sower || true
+74 -20
View File
@@ -1,7 +1,59 @@
# sower
Yet another cross platform transparent proxy tool
[![GitHub release](http://img.shields.io/github/release/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/releases)
[![CircleCI](https://img.shields.io/circleci/token/64114421aab286b6e918741071535250a5273f06/project/github/wweir/sower/master.svg?style=popout)](https://circleci.com/gh/wweir/sower)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/wweir/sower.svg?style=popout)](https://hub.docker.com/r/wweir/sower)
[![GitHub issue](https://img.shields.io/github/issues/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/wweir/sower.svg?style=popout)](LICENSE)
## architecture
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
The sower is a cross-platform intelligent transparent proxy tool base on DNS solution.
The first time you visit a new website, sower will detect if the domain in block list and add it in suggect list. So that, you do not need to care about the rules, sower will handle it in a intelligent way.
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 listening on `:8080` by default. You can turn it off or use another port at any time.
## Installation
After Deployed, please check your config file, it is placed in `/usr/local/etc/sower.toml` by default. Here is the example config file [**conf/sower.toml**](https://github.com/wweir/sower/blob/master/conf/sower.toml)
### Auto deploy
Auto deploy script support Linux server side and masOS/Linux client side.
```shell
$ bash -c "$(curl -sL https://git.io/fhhdp)"
```
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, run:
```shell
$ bash -c "$(curl -sL https://git.io/fhjer)"
```
### 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. Export the port(5533) and run it directly.
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
## Architecture
```
request target servers
<-------------+ +------------->
@@ -11,38 +63,40 @@ 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
```
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/)
## 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
## todo
- [ ] authenticate
- [ ] broker
## Todo
- [x] authenticate
- [ ] broker(waiting for QUIC implementation to be stable)
- [x] CI/CD
- [x] relay optimization
- [x] deploy script for all normal platform
- [x] dns rule intelligent suggestions
- [x] use socks5 as upstream proxy
- [ ] multi port http_proxy support
+91 -68
View File
@@ -1,108 +1,131 @@
package conf
import (
"context"
"flag"
"net"
"os/exec"
"path/filepath"
"os"
"strconv"
"time"
"github.com/BurntSushi/toml"
"github.com/golang/glog"
"github.com/wweir/fsnotify"
toml "github.com/pelletier/go-toml"
"github.com/wweir/sower/util"
)
// Conf define the config items
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"`
SuggestLevel string `toml:"suggest_level"`
ClearDNSCache string `toml:"clear_dns_cache"`
BlockList []string `toml:"blocklist"`
WhiteList []string `toml:"whitelist"`
Suggestions []string `toml:"suggestions"`
Verbose int `toml:"verbose"`
VersionOnly bool `toml:"-"`
}{}
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.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.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
if !flag.Parsed() {
flag.Parse()
}
if Conf.ConfigFile == "" {
initArgs()
if Conf.VersionOnly {
return
}
if err := OnRefreash[0](); err != nil {
panic(err)
}
watchConfigFile()
}
var OnRefreash = []func() error{func() error {
if _, err := toml.DecodeFile(Conf.ConfigFile, &Conf); err != nil {
return err
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
glog.Warningln("no config file has been load:", Conf.ConfigFile)
return
}
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
// clear dns cache
if Conf.ClearDnsCache != "" {
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil {
glog.Errorln(err)
for i := range refreshFns {
if action, err := refreshFns[i](); err != nil {
glog.Fatalln(action+":", err)
}
}
// for glog
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
return err
go addSuggestions()
}
// refreshFns will be executed while init and write new config
var refreshFns = []func() (string, error){
func() (string, error) {
action := "load config"
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
if err != nil {
return action, err
}
defer f.Close()
//safe refresh config
file := Conf.ConfigFile
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
return action, err
}
Conf.ConfigFile = file
return action, flag.Set("v", strconv.Itoa(Conf.Verbose))
},
func() (string, error) {
action := "clear dns cache"
if Conf.ClearDNSCache != "" {
return action, execute(Conf.ClearDNSCache)
}
return action, nil
},
}
// AddRefreshFn add refreshh function for reload config
func AddRefreshFn(init bool, fn func() (string, error)) error {
if init {
if _, err := fn(); err != nil {
return err
}
}
refreshFns = append(refreshFns, fn)
return nil
}}
}
func watchConfigFile() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
glog.Fatalln(err)
}
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
glog.Fatalln(err)
}
// SuggestCh add domain into suggestios
var SuggestCh = make(chan string)
go func() {
for {
select {
case event := <-watcher.Events:
if event.Op == fsnotify.Rename {
if err := watcher.Add(Conf.ConfigFile); err != nil {
glog.Errorln(err)
}
}
// addSuggestions add new domain into suggest rules
func addSuggestions() {
for domain := range SuggestCh {
Conf.Suggestions = append(Conf.Suggestions, domain)
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
glog.Errorln(err)
}
}
case err := <-watcher.Errors:
glog.Fatalln(err)
{ // safe write
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
glog.Errorln(err)
continue
}
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
glog.Errorln(err)
f.Close()
continue
}
f.Close()
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
glog.Errorln(err)
continue
}
}
}()
// reload config
for i := range refreshFns {
if action, err := refreshFns[i](); err != nil {
glog.Errorln(action+":", err)
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
// +build !windows
package conf
import (
"context"
"flag"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/pkg/errors"
"github.com/wweir/sower/dns"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/proxy/transport"
)
func initArgs() {
cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml"))
flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location")
flag.StringVar(&Conf.NetType, "n", "TCP", "net type (socks5 client only): "+strings.Join(transport.ListTransports(), ","))
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type: "+strings.Join(shadow.ListCiphers(), ","))
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.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")
flag.StringVar(&Conf.SuggestLevel, "l", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ","))
flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version")
if !flag.Parsed() {
flag.Set("logtostderr", "true")
flag.Parse()
}
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput()
return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
}
+179
View File
@@ -0,0 +1,179 @@
// +build windows
package conf
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/golang/glog"
"github.com/pkg/errors"
"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
func initArgs() {
cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml"))
flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location")
flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version")
install := flag.Bool("install", false, "install sower as a service")
uninstall := flag.Bool("uninstall", false, "uninstall sower from service list")
exePath,_:=filepath.Abs(os.Args[0])
if !flag.Parsed() {
os.Mkdir("log", 0755)
flag.Set("log_dir", filepath.Dir(os.Args[0])+"/log")
flag.Parse()
}
switch {
case *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, exePath, 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()
})
os.Exit(0)
case *uninstall:
serviceDo(func(s *mgr.Service) error {
err := s.Delete()
if err != nil {
return err
}
return eventlog.Remove(name)
})
os.Exit(0)
default:
os.Chdir(filepath.Dir(os.Args[0]))
if active, err := svc.IsAnInteractiveSession(); err != nil {
glog.Exitf("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 {
glog.Exitln(err)
}
defer elog.Close()
if err := svc.Run(name, &myservice{}); err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
glog.Exitln(err)
}
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
os.Exit(0)
}()
}
}
}
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 {
glog.Exitln(err)
}
defer m.Disconnect()
if err := fn(m); err != nil {
glog.Fatalln(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 {
glog.Errorln(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(Conf.ClearDNSCache, " ") {
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}
out, err := command.CombinedOutput()
return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
}
+46 -78
View File
@@ -1,83 +1,51 @@
net_type="KCP"
net_type="TCP" # TCP, KCP, QUIC, SOCKS5(client only)
cipher="AES_128_GCM" # AES_128_GCM, AES_192_GCM, AES_256_GCM, CHACHA20_IETF_POLY1305, XCHACHA20_IETF_POLY1305
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" # replce it to remote server
http_proxy=":8080" # eg: 192.168.0.2:8080
dns_server="" # Keep empty for dynamic setting from net env
client_ip="127.0.0.1" # listen the IP, dns target is the IP
# clear_dns_cache="pkill mDNSResponder || true" # Windows: "ipconfig /flushdnss"
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
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",
"*.s3.amazonaws.com",
"*.cloudfront.net", # atlassian
"synchrony-cdn.atlassian.com",
"avatar-cdn.atlassian.com",
"*.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.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",
"shadowsocks.org",
"t66y.com",
"ocsp.godaddy.com",
"*.qualtrics.com",
"*.starfieldtech.com",
"*.typekit.net",
"www.v2ray.com",
"**.google.*", # google
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com", # youtube
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com", # facebook
"**.fbcdn.net",
"**.twitter.com", # twitter
"**.twimg.com",
"**.blogspot.com", # blogspot
"**.appspot.com",
"**.wikipedia.org", # wikipeida
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.github.*",
]
whitelist=[
"**.in-addr.arpa",
"imap.*.*",
"imap.*.*.*",
"smtp.*.*",
"smtp.*.*.*",
"pop.*.*",
"pop.*.*.*",
"imap-mail.outlook.com",
"**.qq.com",
"**.baidu.com",
"*.aliyun.com",
"**.cn",
"**.icloud.com",
"**.163.com",
"**.weiyun.com",
]
verbose=0
+60
View File
@@ -0,0 +1,60 @@
package dns
import (
"math/rand"
"net"
"runtime"
"time"
"github.com/krolaw/dhcp4"
"github.com/libp2p/go-reuseport"
"github.com/pkg/errors"
"github.com/wweir/sower/util"
)
var xid = make([]byte, 4)
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
func GetDefaultDNSServer() (string, error) {
iface, err := util.PickInterface()
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
}
+22
View File
@@ -0,0 +1,22 @@
package dns
import (
"runtime"
"testing"
)
func TestGetDefaultDNSServer(t *testing.T) {
switch runtime.GOOS {
case "windows":
case "darwin":
default:
t.Skip("skip for some enviroment not support dhcp and permission set")
return
}
if got, err := GetDefaultDNSServer(); err != nil {
t.Errorf("GetDefaultDNSServer() return error: %s", err)
} else {
t.Logf("GetDefaultDNSServer() return IP: %v", got)
}
}
+153 -61
View File
@@ -1,22 +1,39 @@
package dns
import (
"context"
"net"
"strings"
"sync/atomic"
"time"
"github.com/golang/glog"
"github.com/miekg/dns"
"github.com/wweir/sower/conf"
mem "github.com/wweir/mem-go"
"github.com/wweir/sower/util"
)
const colon = byte(':')
func StartDNS(dnsServer string) {
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string) {
ip := net.ParseIP(listenIP)
suggest := &intelliSuggest{suggestCh, parseSuggestLevel(level), listenIP, time.Second}
mem.DefaultCache = mem.New(time.Hour)
dhcpCh := make(chan struct{})
if dnsServer != "" {
dnsServer = net.JoinHostPort(dnsServer, "53")
} else {
go dynamicSetUpstreamDNS(listenIP, &dnsServer, dhcpCh)
dhcpCh <- struct{}{}
}
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())
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
@@ -26,86 +43,161 @@ func StartDNS(dnsServer string) {
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, colon); idx > 0 {
domain = domain[:idx]
domain = domain[:idx] // trim port
}
if len(conf.Conf.BlockList) == 0 {
bestTry(w, r, domain, dnsServer)
} else {
manual(w, r, domain, dnsServer)
}
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
})
server := &dns.Server{Addr: ":53", Net: "udp"}
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "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
func dynamicSetUpstreamDNS(listenIP string, dnsServer *string, dhcpCh <-chan struct{}) {
addr, _ := dns.ReverseAddr(listenIP)
msg := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: false,
},
Question: []dns.Question{{
Name: addr,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}},
}
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
}
for {
<-dhcpCh
if _, err := dns.Exchange(msg, *dnsServer); err == nil {
continue
}
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
glog.V(2).Infoln(ip+":80", err)
w.WriteMsg(localA(r, domain))
return
host, err := GetDefaultDNSServer()
if err != nil {
glog.Errorln(err)
continue
}
// atomic action
*dnsServer = net.JoinHostPort(host, "53")
glog.Infoln("set dns server to", host)
}
w.WriteMsg(msg)
}
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
if rule.Match(strings.TrimSuffix(domain, ".")) {
inWriteList := whiteList.Match(domain)
if !inWriteList && (blockList.Match(domain) || suggestList.Match(domain)) {
glog.V(2).Infof("match %s suss", domain)
w.WriteMsg(localA(r, domain))
w.WriteMsg(localA(r, domain, ipNet))
return
}
msg, err := dns.Exchange(r, dnsServer+":53")
if msg == nil { // expose any response except nil
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
go mem.Remember(suggest, domain)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
msg, err := dns.ExchangeContext(ctx, r, dnsServer)
if err != nil {
if dhcpCh != nil {
select {
case dhcpCh <- struct{}{}:
default:
}
}
glog.V(1).Infof("get dns of %s from %s fail: %s", domain, dnsServer, err)
return
} else if msg == nil { // expose any response except nil
glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer)
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
}
type intelliSuggest struct {
suggestCh chan<- string
level level
listenIP string
timeout time.Duration
}
_, 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 (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) {
iface, e = struct{}{}, nil
if i.level == DISABLE {
return
}
}
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
// kill deadloop, for ugly wildcard setting dns setting
domain := strings.TrimSuffix(key.(string), ".")
if strings.Count(domain, ".") > 10 {
return
}
ip, err := net.LookupIP(domain)
if err != nil || len(ip) == 0 {
glog.V(1).Infoln(domain, ip, err)
return
}
var (
pings = [...]struct {
viaAddr string
port util.Port
}{
{ip[0].String(), util.HTTP},
{i.listenIP, util.HTTP},
{ip[0].String(), util.HTTPS},
{i.listenIP, util.HTTPS},
}
protos = [...]*int32{
new(int32), /*HTTP*/
new(int32), /*HTTPS*/
}
score = new(int32)
)
for idx := range pings {
go func(idx int) {
if err := util.HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil {
// local ping fail
if pings[idx].viaAddr == i.listenIP {
atomic.AddInt32(score, -1)
glog.V(1).Infof("remote ping %s fail", domain)
} else {
atomic.AddInt32(score, 1)
glog.V(1).Infof("local ping %s fail", domain)
}
// remote ping faster
} else if pings[idx].viaAddr == i.listenIP {
if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.level == SPEEDUP {
atomic.AddInt32(score, 1)
}
glog.V(1).Infof("remote ping %s faster", domain)
} else {
atomic.CompareAndSwapInt32(protos[idx/2], 0, 2)
return // score change trigger add suggestion
}
// check all remote pings are faster
if atomic.LoadInt32(score) == int32(len(protos)) {
for i := range protos {
if atomic.LoadInt32(protos[i]) != 1 {
return
}
}
}
// 1. local fail and remote success
// 2. all remote pings are faster
if atomic.LoadInt32(score) >= int32(len(protos)) {
old := atomic.SwapInt32(score, -1) // avoid readd the suggestion
i.suggestCh <- domain
glog.Infof("suggested domain: %s with score: %d", domain, old)
}
}(idx)
}
return
}
+16
View File
@@ -0,0 +1,16 @@
// Code generated by "stringer -type=level util.go"; DO NOT EDIT.
package dns
import "strconv"
const _level_name = "DISABLEBLOCKSPEEDUPlevelEnd"
var _level_index = [...]uint8{0, 7, 12, 19, 27}
func (i level) String() string {
if i < 0 || i >= level(len(_level_index)-1) {
return "level(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _level_name[_level_index[i]:_level_index[i+1]]
}
-73
View File
@@ -1,73 +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 {
switch len(n.Node) {
case 0:
return true
case 1:
_, ok := n.Node["*"]
return ok
default:
return false
}
}
if n, ok := n.Node[secs[length-1]]; ok {
return n.matchSecs(secs[:length-1])
}
_, ok := n.Node["*"]
return ok
}
+68 -11
View File
@@ -1,19 +1,76 @@
package dns
import (
"net"
"github.com/golang/glog"
"github.com/wweir/sower/conf"
"github.com/miekg/dns"
"github.com/wweir/sower/util"
)
var rule *Node
var (
blockList *util.Node
suggestList *util.Node
whiteList *util.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
})
// LoadRules init rules from config
func LoadRules(blocklist, suggestions, whitelist []string, host string) {
blockList = loadRules("block", blocklist)
suggestList = loadRules("suggest", suggestions)
whiteList = loadRules("white", whitelist)
whiteList.Add(host)
glog.V(1).Infoln("reloaded config")
}
func loadRules(name string, list []string) *util.Node {
rule := util.NewNodeFromRules(".", list...)
glog.V(3).Infof("load %s rule:\n%s", name, rule)
return rule
}
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
}
//go:generate stringer -type=level $GOFILE
type level int32
const (
DISABLE level = iota
BLOCK
SPEEDUP
levelEnd
)
func ListSuggestLevels() []string {
list := make([]string, 0, int(levelEnd))
for i := level(0); i < levelEnd; i++ {
list = append(list, i.String())
}
return list
}
func parseSuggestLevel(suggestLevel string) level {
for i := level(0); i < levelEnd; i++ {
if suggestLevel == i.String() {
return i
}
}
glog.Exitln("invalid suggest level: " + suggestLevel)
return levelEnd
}
+19 -20
View File
@@ -1,28 +1,27 @@
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/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.7.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/pkg/errors v0.8.0
github.com/guregu/null v3.4.0+incompatible // indirect
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/reedsolomon v1.9.2 // indirect
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
github.com/lib/pq v1.2.0 // indirect
github.com/libp2p/go-reuseport v0.0.1
github.com/lucas-clemente/quic-go v0.12.0
github.com/miekg/dns v1.1.18
github.com/pelletier/go-toml v1.4.0
github.com/pkg/errors v0.8.1
github.com/satori/go.uuid v1.2.0 // indirect
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/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
github.com/xtaci/kcp-go v5.4.10+incompatible
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
)
+75 -33
View File
@@ -1,68 +1,110 @@
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/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/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/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=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
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/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/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
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.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.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/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI=
github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/miekg/dns v1.1.18 h1:S82KA03bsvMvziY41d0WitiplMCt8QhawbSQLtoqsdI=
github.com/miekg/dns v1.1.18/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M=
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/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
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/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/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJdVlhyv+xO3A7ZlpfL9nFLWLYcc=
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s=
github.com/xtaci/kcp-go v5.4.10+incompatible h1:FgH1ji3efEmRFaHEeyim1RYN4Q/c8BT8VqXMklBmh84=
github.com/xtaci/kcp-go v5.4.10+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
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-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
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-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-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-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 h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+36 -6
View File
@@ -1,20 +1,50 @@
package main
import (
"encoding/json"
"fmt"
"net"
"github.com/golang/glog"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/dns"
"github.com/wweir/sower/proxy"
"github.com/wweir/sower/proxy/transport"
)
var version, date string
func main() {
conf := conf.Conf
glog.Infoln("Starting:", conf)
cfg := &conf.Conf
if cfg.VersionOnly {
config, _ := json.MarshalIndent(cfg, "", "\t")
fmt.Printf("Version:\n\t%s %s\nConfig:\n%s", version, date, config)
return
}
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
tran, err := transport.GetTransport(cfg.NetType)
if err != nil {
glog.Exitln(err)
}
if cfg.ServerAddr == "" {
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
if conf.ServerAddr == "" {
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Password)
} else {
go dns.StartDNS(conf.DnsServer)
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Password)
conf.AddRefreshFn(true, func() (string, error) {
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, cfg.ServerAddr)
return "load rules", nil
})
isSocks5 := (cfg.NetType == "SOCKS5")
serverAddr := net.JoinHostPort(cfg.ServerAddr, cfg.ServerPort)
if cfg.HTTPProxy != "" {
go proxy.StartHttpProxy(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy)
}
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
proxy.StartClient(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
}
}
-46
View File
@@ -1,46 +0,0 @@
package parse
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"strings"
)
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
teeConn = &TeeConn{Conn: conn}
teeConn.StartOrReset()
defer teeConn.Stop()
buf := make([]byte, 1)
if n, err := teeConn.Read(buf); err != nil {
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
}
teeConn.StartOrReset()
// 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
}
+66 -40
View File
@@ -4,69 +4,95 @@ import (
"net"
"github.com/golang/glog"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
"github.com/wweir/sower/proxy/parser"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/proxy/socks5"
"github.com/wweir/sower/proxy/transport"
)
type Client interface {
Dial(server string) (net.Conn, error)
}
func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) {
conn80 := listenLocal(listenIP, "80")
conn443 := listenLocal(listenIP, "443")
var isHttp bool
var conn net.Conn
func StartClient(netType, server, password string) {
var connCh = listenLocal([]string{":80", ":443"})
var client Client
switch netType {
case QUIC.String():
client = quic.NewClient()
case KCP.String():
client = kcp.NewClient()
case TCP.String():
client = tcp.NewClient()
}
glog.Infoln("Client started.")
for {
conn := <-connCh
select {
case conn = <-conn80:
isHttp = true
case conn = <-conn443:
isHttp = false
}
resolveAddr(&server)
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
rc, err := client.Dial(server)
rc, err := tran.Dial(server)
if err != nil {
conn.Close()
glog.Errorln(err)
continue
}
if rc, err = shadow.Shadow(rc, password); err != nil {
glog.Fatalln(err)
switch {
case isSocks5 && isHttp:
c, host, port, err := parser.ParseHttpAddr(conn)
if err != nil {
c.Close()
rc.Close()
glog.Errorln(err)
continue
}
conn = c
rc = socks5.ToSocks5(rc, host, port)
case isSocks5 && !isHttp:
c, host, err := parser.ParseHttpsHost(conn)
if err != nil {
c.Close()
rc.Close()
glog.Errorln(err)
continue
}
conn = c
rc = socks5.ToSocks5(rc, host, "443")
case !isSocks5 && isHttp:
rc = shadow.Shadow(rc, cipher, password)
rc = parser.NewHttpConn(rc)
case !isSocks5 && !isHttp:
rc = shadow.Shadow(rc, cipher, password)
rc = parser.NewHttpsConn(rc, "443")
}
go relay(conn, rc)
}
}
func listenLocal(ports []string) <-chan net.Conn {
func listenLocal(listenIP string, port string) <-chan net.Conn {
connCh := make(chan net.Conn, 10)
for i := range ports {
go func(port string) {
ln, err := net.Listen("tcp", port)
go func() {
ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port))
if err != nil {
glog.Fatalln(err)
}
for {
conn, err := ln.Accept()
if err != nil {
glog.Fatalln(err)
glog.Errorln("accept", listenIP+port, "fail:", err)
continue
}
for {
conn, err := ln.Accept()
if err != nil {
glog.Errorln("accept", port, "fail:", err)
continue
}
conn.(*net.TCPConn).SetKeepAlive(true)
connCh <- conn
}
}()
connCh <- conn
}
}(ports[i])
}
glog.Infoln("listening ports:", ports)
glog.Infoln("listening port:", port)
return connCh
}
+127
View File
@@ -0,0 +1,127 @@
package proxy
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"net/url"
"time"
"github.com/golang/glog"
"github.com/wweir/sower/proxy/parser"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/proxy/socks5"
"github.com/wweir/sower/proxy/transport"
)
func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, password, addr string) {
srv := &http.Server{
Addr: addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resolveAddr(&server)
if r.Method == http.MethodConnect {
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
} else {
httpProxy(w, r, tran, isSocks5, 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, r *http.Request,
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
roundTripper := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if isSocks5 {
roundTripper.Proxy = func(*http.Request) (*url.URL, error) {
return url.Parse("socks5://" + server)
}
} else {
roundTripper.DialContext = func(context.Context, string, string) (net.Conn, error) {
conn, err := tran.Dial(server)
if err != nil {
return nil, err
}
conn = shadow.Shadow(conn, cipher, password)
return parser.NewHttpConn(conn), nil
}
}
resp, err := roundTripper.RoundTrip(r)
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,
tran transport.Transport, isSocks5 bool, 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 {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
conn.Close()
glog.Errorln("serve https proxy, write data fail:", err)
return
}
// remote conn
rc, err := tran.Dial(server)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
conn.Close()
glog.Errorln("serve https proxy, dial remote fail:", err)
return
}
host, port, err := net.SplitHostPort(r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
conn.Close()
glog.Errorln("serve https proxy, dial remote fail:", err)
return
}
if isSocks5 {
rc = socks5.ToSocks5(rc, host, port)
} else {
rc = shadow.Shadow(rc, cipher, password)
rc = parser.NewHttpsConn(rc, port)
}
relay(rc, conn)
}
-65
View File
@@ -1,65 +0,0 @@
package kcp
import (
"net"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type client struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
AckNodelay bool
NoDelay int
Interval int
Resend int
NoCongestion int
SndWnd int
RcvWnd int
MTU int
}
func NewClient() *client {
return &client{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SndWnd: 0,
RcvWnd: 0,
MTU: 1350,
}
}
func (c *client) Dial(server string) (net.Conn, error) {
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
if err != nil {
return nil, errors.Wrap(err, "dial")
}
conn.SetStreamMode(true)
conn.SetWriteDelay(false)
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
conn.SetMtu(c.MTU)
conn.SetACKNoDelay(c.AckNodelay)
if err := conn.SetDSCP(c.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
return conn, nil
}
-56
View File
@@ -1,56 +0,0 @@
package kcp
import (
"net"
"github.com/golang/glog"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type server struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
}
func NewServer(password string) *server {
return &server{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
if err != nil {
return nil, err
}
if err := ln.SetDSCP(s.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.AcceptKCP()
if err != nil {
glog.Fatalln("KCP listen:", err)
}
connCh <- conn
}
}()
return connCh, nil
}
-16
View File
@@ -1,16 +0,0 @@
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
package proxy
import "strconv"
const _netType_name = "QUICKCPTCP"
var _netType_index = [...]uint8{0, 4, 7, 10}
func (i netType) String() string {
if i < 0 || i >= netType(len(_netType_index)-1) {
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _netType_name[_netType_index[i]:_netType_index[i+1]]
}
+12
View File
@@ -0,0 +1,12 @@
// Package parser transter conn to be a parser conn
//
// init request payload:
// <type>(1) + <size>(2))(+Overhead) + <data>(size+Overhead)
// data definition:
// 0x00(any): [size](1) + [addr:port] + content
// 0x01(http): content
// 0x02(https): [port](2) + content
//
// init response payload:
// ([status code](2) + <size>(2))(+Overhead) + <content>(size+Overhead)
package parser
+163
View File
@@ -0,0 +1,163 @@
package parser
import (
"bufio"
"io"
"net"
"net/http"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/wweir/sower/util"
)
const (
OTHER byte = iota
HTTP
HTTPS
)
// Write Addr
type conn struct {
typ byte
domain string
port string
init bool
net.Conn
}
func NewOtherConn(c net.Conn, domain, port string) net.Conn {
return &conn{
typ: OTHER,
domain: domain,
port: port,
init: true,
Conn: c,
}
}
func NewHttpConn(c net.Conn) net.Conn {
return &conn{
typ: HTTP,
init: true,
Conn: c,
}
}
func NewHttpsConn(c net.Conn, port string) net.Conn {
return &conn{
typ: HTTPS,
port: port,
init: true,
Conn: c,
}
}
func (c *conn) Write(b []byte) (n int, err error) {
if c.init {
var pkg []byte
var prefixLen int
switch c.typ {
case OTHER:
// type + domain + ':' + port + data
prefixLen = 1 + len(c.domain) + 1 + len(c.port)
pkg = make([]byte, 0, prefixLen+len(b))
pkg = append(pkg, OTHER)
pkg = append(pkg, byte(len(c.domain)+1+len(c.port)))
pkg = append(pkg, []byte(c.domain+":"+c.port)...)
case HTTP:
// type + data
prefixLen = 1
pkg = make([]byte, 0, prefixLen+len(b))
pkg = append(pkg, HTTP)
case HTTPS:
// type + port + data
prefixLen = 1 + 2
pkg = make([]byte, 0, prefixLen+len(b))
pkg = append(pkg, HTTPS)
port, _ := strconv.Atoi(c.port)
pkg = append(pkg, byte(port>>8), byte(port))
}
c.init = false
n, err := c.Conn.Write(append(pkg, b...))
// n should larger than prefix length, if not, err is not nil
return n - prefixLen, err
}
return c.Conn.Write(b)
}
// Read Addr
func ParseAddr(conn net.Conn) (net.Conn, string, string, error) {
buf := make([]byte, 1)
if _, err := io.ReadFull(conn, buf); err != nil {
return conn, "", "", err
}
switch buf[0] {
case OTHER:
if _, err := io.ReadFull(conn, buf); err != nil {
return conn, "", "", err
}
buf = make([]byte, int(buf[0]))
if _, err := io.ReadFull(conn, buf); err != nil {
return conn, "", "", err
}
addr := string(buf)
if idx := strings.LastIndex(addr, ":"); idx != -1 {
return conn, addr[:idx], addr[idx+1:], nil
}
return conn, "", "", errors.New("invalid payload")
case HTTP:
return ParseHttpAddr(conn)
case HTTPS:
buf = make([]byte, 2)
if _, err := io.ReadFull(conn, buf); err != nil {
return conn, "", "", err
}
port := strconv.Itoa(int(buf[0])<<8 + int(buf[1]))
conn, domain, err := ParseHttpsHost(conn)
return conn, domain, port, err
default:
return conn, "", "", errors.Errorf("not supported type (%v)", buf[0])
}
}
func ParseHttpAddr(conn net.Conn) (net.Conn, string, string, error) {
teeConn := &util.TeeConn{Conn: conn}
teeConn.StartOrReset()
defer teeConn.Stop()
b := bufio.NewReader(teeConn)
resp, err := http.ReadRequest(b)
if err != nil {
return teeConn, "", "", err
}
if idx := strings.LastIndex(resp.Host, ":"); idx != -1 {
return teeConn, resp.Host[:idx], resp.Host[idx+1:], nil
}
return teeConn, resp.Host, "80", nil
}
func ParseHttpsHost(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
teeConn.StartOrReset()
defer teeConn.Stop()
domain, _, err := extractSNI(teeConn)
if err != nil {
return teeConn, "", err
} else if domain == "" {
return teeConn, "", errors.New("ClientHello did not present an SNI extension")
}
return teeConn, domain, nil
}
+97
View File
@@ -0,0 +1,97 @@
package parser
import (
"bufio"
"bytes"
"io/ioutil"
"net"
"net/http"
"testing"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/util"
)
func TestParseAddr1(t *testing.T) {
c1, c2 := net.Pipe()
go func() {
c1 = NewHttpConn(c1)
req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3}))
req.Write(c1)
}()
c2, host, port, err := ParseAddr(c2)
if err != nil || host != "wweir.cc" || port != "80" {
t.Error(err, host, port)
}
req, err := http.ReadRequest(bufio.NewReader(c2))
if err != nil {
t.Error(err)
}
data, err := ioutil.ReadAll(req.Body)
if err != nil || len(data) != 3 || data[0] != 1 {
t.Error(err, data)
}
}
func TestParseAddr2(t *testing.T) {
c1, c2 := net.Pipe()
go func() {
c1 = NewHttpsConn(c1, "443")
c1.Write(util.HTTPS.PingMsg("wweir.cc"))
}()
_, host, port, err := ParseAddr(c2)
if err != nil || host != "wweir.cc" || port != "443" {
t.Error(err, host, port)
}
}
func TestParseAddr3(t *testing.T) {
c1, c2 := net.Pipe()
go func() {
c1 = NewOtherConn(c1, "wweir.cc", "1080")
c1.Write(util.HTTPS.PingMsg("wweir.cc"))
}()
_, host, port, err := ParseAddr(c2)
if err != nil || host != "wweir.cc" || port != "1080" {
t.Error(err, host, port)
}
}
func TestParseAddr4(t *testing.T) {
c1, c2 := net.Pipe()
go func() {
c1 = shadow.Shadow(c1, "AES_128_GCM", "12345678")
c1 = NewHttpConn(c1)
req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3}))
req.Write(c1)
}()
c2 = shadow.Shadow(c2, "AES_128_GCM", "12345678")
c2, host, port, err := ParseAddr(c2)
if err != nil || host != "wweir.cc" || port != "80" {
t.Error(err, host, port)
}
req, err := http.ReadRequest(bufio.NewReader(c2))
if err != nil {
t.Error(err)
}
data, err := ioutil.ReadAll(req.Body)
if err != nil || len(data) != 3 || data[0] != 1 {
t.Error(err, data)
}
}
+1 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package parse
package parser
import (
"encoding/binary"
-50
View File
@@ -1,50 +0,0 @@
package quic
import (
"crypto/tls"
"net"
"time"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type client struct {
conf *quic.Config
sess quic.Session
}
func NewClient() *client {
return &client{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (c *client) Dial(server string) (net.Conn, error) {
if c.sess == nil {
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
return nil, errors.Wrap(err, "session")
} else {
c.sess = sess
}
}
var stream quic.Stream
if err := WithTimeout(func() (err error) {
if stream, err = c.sess.OpenStream(); err != nil {
c.sess = nil
}
return
}, time.Second); err != nil {
return nil, errors.Wrap(err, "stream")
}
return &streamConn{
Stream: stream,
sess: c.sess,
}, nil
}
-57
View File
@@ -1,57 +0,0 @@
package quic
import (
"net"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type server struct {
conf *quic.Config
}
func NewServer() *server {
return &server{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
if err != nil {
return nil, errors.WithStack(err)
}
connCh := make(chan net.Conn)
go func() {
for {
sess, err := ln.Accept()
if err != nil {
glog.Fatalln(err)
}
go accept(sess, connCh)
}
}()
return connCh, nil
}
func accept(sess quic.Session, connCh chan<- net.Conn) {
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
defer sess.Close()
for {
stream, err := sess.AcceptStream()
if err != nil {
glog.Errorln(err)
return
}
connCh <- &streamConn{stream, sess}
}
}
-67
View File
@@ -1,67 +0,0 @@
package quic
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"net"
"time"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type streamConn struct {
quic.Stream
sess quic.Session
}
func (s *streamConn) LocalAddr() net.Addr {
return s.sess.LocalAddr()
}
func (s *streamConn) RemoteAddr() net.Addr {
return s.sess.RemoteAddr()
}
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}}
}
func WithTimeout(fn func() error, timeout time.Duration) error {
var okCh = make(chan struct{})
var err error
go func() {
err = fn()
close(okCh)
}()
select {
case <-okCh:
return err
case <-time.After(timeout):
return errors.New("timeout: " + timeout.String())
}
}
+17 -38
View File
@@ -5,68 +5,47 @@ import (
"strings"
"github.com/golang/glog"
"github.com/wweir/sower/parse"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
"github.com/wweir/sower/proxy/parser"
"github.com/wweir/sower/proxy/shadow"
"github.com/wweir/sower/proxy/transport"
)
type Server interface {
Listen(port string) (<-chan net.Conn, error)
}
func StartServer(netType, port, password string) {
var server Server
switch netType {
case QUIC.String():
server = quic.NewServer()
case KCP.String():
server = kcp.NewServer(password)
case TCP.String():
server = tcp.NewServer()
}
func StartServer(tran transport.Transport, port, cipher, password string) {
if port == "" {
glog.Fatalln("port must set")
}
if !strings.Contains(port, ":") {
if !strings.HasPrefix(port, ":") {
port = ":" + port
}
connCh, err := server.Listen(port)
connCh, err := tran.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)
}
go handle(conn)
go handle(<-connCh, cipher, password)
}
}
func handle(conn net.Conn) {
conn, addr, err := parse.ParseAddr(conn)
func handle(conn net.Conn, cipher, password string) {
conn = shadow.Shadow(conn, cipher, password)
conn, host, port, err := parser.ParseAddr(conn)
if err != nil {
conn.Close()
glog.Warningln(err)
return
}
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
glog.V(1).Infof("new conn from %s to %s:%s", conn.RemoteAddr(), host, port)
rc, err := net.Dial("tcp", addr)
rc, err := net.Dial("tcp", net.JoinHostPort(host, port))
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)
}
+76
View File
@@ -0,0 +1,76 @@
package shadow
import (
"crypto/aes"
"crypto/cipher"
"github.com/pkg/errors"
"golang.org/x/crypto/chacha20poly1305"
)
//go:generate stringer -type=typ $GOFILE
type typ int
const (
AES_128_GCM typ = iota
AES_192_GCM
AES_256_GCM
CHACHA20_IETF_POLY1305
XCHACHA20_IETF_POLY1305
cipherEnd
)
func ListCiphers() []string {
list := make([]string, 0, int(cipherEnd))
for i := typ(0); i < cipherEnd; i++ {
list = append(list, i.String())
}
return list
}
func pickCipher(typ, password string) (cipher.AEAD, error) {
var blockSize int
switch typ {
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: " + typ)
}
// 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
}
}
}
+5
View File
@@ -0,0 +1,5 @@
// Package shadow transter conn to be a crypto conn
// support aead mode only
// data payload:
// <size>(2+Overhead) + <content>(size+Overhead)
package shadow
+6 -14
View File
@@ -1,14 +1,11 @@
package shadow
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"io"
"math/rand"
"net"
"github.com/pkg/errors"
)
const MAX_SIZE = 0xFFFF
@@ -74,7 +71,7 @@ func (c *conn) Write(b []byte) (n int, err error) {
}
// BigEndian
c.writeBuf[0], c.writeBuf[1] = byte((dataSize&0xFF00)>>8), byte(dataSize&0xFF)
c.writeBuf[0], c.writeBuf[1] = byte(dataSize>>8), byte(dataSize)
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)
@@ -83,18 +80,13 @@ func (c *conn) Write(b []byte) (n int, err error) {
if err != nil {
return 0, err
}
return len(b), 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{
@@ -104,7 +96,7 @@ func Shadow(c net.Conn, password string) (net.Conn, error) {
decryptNonce: newNonce(password, aead.NonceSize()),
writeBuf: make([]byte, 0xFFFF),
Conn: c,
}, nil
}
}
func newNonce(password string, size int) func() []byte {
+22
View File
@@ -0,0 +1,22 @@
package shadow
import (
"net"
"testing"
)
func TestShadow(t *testing.T) {
c1, c2 := net.Pipe()
go func() {
conn := Shadow(c1, "AES_128_GCM", "12345678")
conn.Write([]byte{1, 2})
}()
conn := Shadow(c2, "AES_128_GCM", "12345678")
buf := make([]byte, 3)
n, _ := conn.Read(buf)
if n!=2|| buf[0] != 1 || buf[1] != 2 {
t.Error(buf)
}
}
+16
View File
@@ -0,0 +1,16 @@
// Code generated by "stringer -type=typ cipher.go"; DO NOT EDIT.
package shadow
import "strconv"
const _typ_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
var _typ_index = [...]uint8{0, 11, 22, 33, 55, 78, 87}
func (i typ) String() string {
if i < 0 || i >= typ(len(_typ_index)-1) {
return "typ(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _typ_name[_typ_index[i]:_typ_index[i+1]]
}
+150
View File
@@ -0,0 +1,150 @@
package socks5
import (
"encoding/binary"
"io"
"net"
"strconv"
"github.com/pkg/errors"
)
func ToSocks5(c net.Conn, domain, port string) net.Conn {
num, _ := strconv.Atoi(port)
bytes := []byte{byte(num >> 8), byte(num)}
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: bytes}
}
type conn struct {
init chan struct{}
domain string
port []byte
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
}
}
{
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: c.port,
}
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, errors.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)
}
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
}
-14
View File
@@ -1,14 +0,0 @@
package tcp
import "net"
type client struct {
}
func NewClient() *client {
return &client{}
}
func (c *client) Dial(server string) (net.Conn, error) {
return net.Dial("tcp", server)
}
-34
View File
@@ -1,34 +0,0 @@
package tcp
import (
"net"
"github.com/golang/glog"
)
type server struct {
}
func NewServer() *server {
return &server{}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := net.Listen("tcp", port)
if err != nil {
return nil, err
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.Accept()
if err != nil {
glog.Fatalln("TCP listen:", err)
}
connCh <- conn
}
}()
return connCh, nil
}
+115
View File
@@ -0,0 +1,115 @@
package transport
import (
"net"
"github.com/golang/glog"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type kcpTran struct {
client
server
}
type client struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
AckNodelay bool
NoDelay int
Interval int
Resend int
NoCongestion int
SndWnd int
RcvWnd int
MTU int
}
type server struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
}
func init() {
transports["KCP"] = &kcpTran{
client: client{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SndWnd: 0,
RcvWnd: 0,
MTU: 1350,
},
server: server{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
},
}
}
func (c *client) Dial(server string) (net.Conn, error) {
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
if err != nil {
return nil, errors.Wrap(err, "dial")
}
conn.SetStreamMode(true)
conn.SetWriteDelay(false)
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
conn.SetMtu(c.MTU)
conn.SetACKNoDelay(c.AckNodelay)
if err := conn.SetDSCP(c.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
return conn, nil
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
if err != nil {
return nil, err
}
if err := ln.SetDSCP(s.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.AcceptKCP()
if err != nil {
glog.Fatalln("KCP listen:", err)
}
connCh <- conn
}
}()
return connCh, nil
}
+137
View File
@@ -0,0 +1,137 @@
package transport
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"net"
"time"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/wweir/sower/util"
)
type quicTran struct {
clientConf *quic.Config
sess quic.Session
serverConf *quic.Config
}
func init() {
transports["QUIC"] = &quicTran{
clientConf: &quic.Config{
HandshakeTimeout: time.Second,
KeepAlive: true,
IdleTimeout: time.Minute,
},
serverConf: &quic.Config{
MaxIncomingStreams: 1024,
},
}
}
func (c *quicTran) Dial(server string) (net.Conn, error) {
if c.sess == nil {
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.clientConf); err != nil {
return nil, errors.Wrap(err, "session")
} else {
go func() {
<-sess.Context().Done()
sess.Close()
c.sess = nil
}()
c.sess = sess
}
}
var stream quic.Stream
if err := util.WithTimeout(func() (err error) {
if stream, err = c.sess.OpenStream(); err != nil {
c.sess = nil
}
return
}, time.Second); err != nil {
return nil, errors.Wrap(err, "stream")
}
return &streamConn{
Stream: stream,
sess: c.sess,
}, nil
}
type streamConn struct {
quic.Stream
sess quic.Session
}
func (s *streamConn) LocalAddr() net.Addr {
return s.sess.LocalAddr()
}
func (s *streamConn) RemoteAddr() net.Addr {
return s.sess.RemoteAddr()
}
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}}
}
func (s *quicTran) Listen(port string) (<-chan net.Conn, error) {
ln, err := quic.ListenAddr(port, mockTlsPem(), s.serverConf)
if err != nil {
return nil, errors.WithStack(err)
}
connCh := make(chan net.Conn)
go func() {
for {
sess, err := ln.Accept(context.Background())
if err != nil {
glog.Fatalln(err)
}
go accept(sess, connCh)
}
}()
return connCh, nil
}
func accept(sess quic.Session, connCh chan<- net.Conn) {
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
defer sess.Close()
for {
stream, err := sess.AcceptStream(context.Background())
if err != nil {
glog.Errorln(err)
return
}
connCh <- &streamConn{stream, sess}
}
}
+58
View File
@@ -0,0 +1,58 @@
package transport
import (
"net"
"time"
"github.com/golang/glog"
)
type tcp struct {
DialTimeout time.Duration
isSocks5 bool
}
func init() {
transports["TCP"] = &tcp{
DialTimeout: 5 * time.Second,
}
transports["SOCKS5"] = &tcp{
DialTimeout: 5 * time.Second,
isSocks5: true,
}
}
func (t *tcp) Dial(server string) (net.Conn, error) {
conn, err := net.DialTimeout("tcp", server, t.DialTimeout)
if err != nil {
return nil, err
}
conn.(*net.TCPConn).SetKeepAlive(true)
return conn, nil
}
func (t *tcp) Listen(port string) (<-chan net.Conn, error) {
if t.isSocks5 {
panic("not support run as socks5 server")
}
ln, err := net.Listen("tcp", port)
if err != nil {
return nil, err
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.Accept()
if err != nil {
glog.Fatalln("TCP listen:", err)
}
conn.(*net.TCPConn).SetKeepAlive(true)
connCh <- conn
}
}()
return connCh, nil
}
+30
View File
@@ -0,0 +1,30 @@
package transport
import (
"net"
"github.com/pkg/errors"
)
type Transport interface {
Dial(server string) (net.Conn, error)
Listen(port string) (<-chan net.Conn, error)
}
var transports = map[string]Transport{}
func ListTransports() []string {
list := make([]string, 0, len(transports))
for key := range transports {
list = append(list, key)
}
return list
}
func GetTransport(netType string) (Transport, error) {
tran, ok := transports[netType]
if !ok {
return nil, errors.New("invalid net type: " + netType)
}
return tran, nil
}
+16 -9
View File
@@ -10,14 +10,20 @@ import (
"github.com/golang/glog"
)
//go:generate stringer -type=netType $GOFILE
type netType int
// race safe
var resolved = false
const (
QUIC netType = iota
KCP
TCP
)
func resolveAddr(server *string) {
if !resolved {
if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil {
glog.Errorln(err)
} else {
glog.Infof("remote server (%s)=>(%s)", *server, addr)
*server = addr.String()
resolved = true
}
}
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
@@ -34,9 +40,10 @@ func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
}
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetReadDeadline(now)
dst.SetWriteDeadline(now)
src.SetDeadline(now)
dst.SetDeadline(now)
} else {
src.Close()
dst.Close()
-6
View File
@@ -1,6 +0,0 @@
// 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
+131
View File
@@ -0,0 +1,131 @@
package util
import (
"bytes"
"crypto/tls"
"encoding/binary"
"io"
"net"
"time"
)
// HTTPPing try connect to a http(s) server with domain though the http addr
func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err error) {
conn, err := net.DialTimeout("tcp", port.JoinAddr(viaHost), timeout)
if err != nil {
return err
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(timeout))
if _, err = conn.Write(port.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 == io.EOF && viaHost == domain {
return nil
}
return err
}
// Port ==========================
type Port uint16
const (
HTTP Port = iota
HTTPS
)
func (p Port) JoinAddr(addr string) string {
switch p {
case HTTP:
return addr + ":80"
case HTTPS:
return addr + ":443"
default:
panic("invalid port")
}
}
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 (0x0301)
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()
}
+30
View File
@@ -0,0 +1,30 @@
// +build !windows
package util
import (
"errors"
"net"
)
// PickInterface pick the first active net interface
func PickInterface() (*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")
}
+23
View File
@@ -0,0 +1,23 @@
package util
import (
"runtime"
"testing"
)
func TestPickInterface(t *testing.T) {
switch runtime.GOOS {
case "windows":
case "darwin":
default:
t.Skip("skip for some enviroment not have net interface")
return
}
got, err := PickInterface()
if err != nil {
t.Errorf("PickInterface() error = %v", err)
} else {
t.Logf("PickInterface() got: MAC: %s IP: %s", got.HardwareAddr, got.IP)
}
}
+51
View File
@@ -0,0 +1,51 @@
// +build windows
package util
import (
"bytes"
"errors"
"net"
"os"
"syscall"
"unsafe"
)
// PickInterface pick the first active net interface
func PickInterface() (*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
}
+52
View File
@@ -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
}
+65
View File
@@ -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)
}
})
}
}
+105
View File
@@ -0,0 +1,105 @@
package util
import (
"strings"
"sync"
)
type Node struct {
node
sep string
*sync.RWMutex
}
type node struct {
node map[string]*node
}
func NewNode(sep string) *Node {
return &Node{node{node: map[string]*node{}}, sep, &sync.RWMutex{}}
}
func NewNodeFromRules(sep string, rules ...string) *Node {
n := NewNode(sep)
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 {
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
}
+79
View File
@@ -0,0 +1,79 @@
package util
import (
"testing"
)
func TestNode_Match(t *testing.T) {
type test struct {
arg string
want bool
}
tests := []struct {
name string
node *Node
tests []test
}{{
"simple",
NewNodeFromRules(".", "a.wweir.cc", "b.wweir.cc"),
[]test{
{"a.wweir.cc", true},
{"b.wweir.cc", true},
},
}, {
"parent",
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"b.wweir.cc", false},
},
}, {
"fuzz1",
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",
NewNodeFromRules(".", "a.*.cc", "c.wweir.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", false},
{"c.wweir.cc", true},
},
}, {
"fuzz3",
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", true},
{"iamp.wweir.cc", true},
},
}, {
"fuzz4",
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)
}
}
})
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package parse
package util
import (
"net"
+30
View File
@@ -0,0 +1,30 @@
package util
import (
"errors"
"net"
"time"
)
// Iface is net interface address info
type Iface struct {
net.HardwareAddr
net.IP
}
func WithTimeout(fn func() error, timeout time.Duration) error {
var okCh = make(chan struct{})
var err error
go func() {
err = fn()
close(okCh)
}()
select {
case <-okCh:
return err
case <-time.After(timeout):
return errors.New("timeout: " + timeout.String())
}
}