├── .gitignore ├── .travis.yml ├── Docker └── server │ ├── Dockerfile │ ├── docker_push.sh │ └── latest │ └── Dockerfile ├── LICENSE ├── README.md ├── client.json ├── cnipset.txt ├── common ├── channel │ ├── common │ │ └── common.go │ ├── config.go │ ├── dial.go │ ├── direct │ │ └── local.go │ ├── http │ │ ├── local.go │ │ └── remote.go │ ├── http2 │ │ ├── local.go │ │ └── remote.go │ ├── kcp │ │ ├── local.go │ │ └── remote.go │ ├── local.go │ ├── local_p2p.go │ ├── mux.go │ ├── quic │ │ ├── local.go │ │ └── remote.go │ ├── remote.go │ ├── remote_p2p.go │ ├── ssh │ │ └── local.go │ ├── tcp │ │ ├── local.go │ │ └── remote.go │ ├── version.go │ └── websocket │ │ ├── local.go │ │ └── remote.go ├── dns │ └── dns.go ├── dump │ └── http.go ├── fakecert │ └── fake.go ├── gfwlist │ ├── gfwlist.go │ └── gfwlist_test.go ├── helper │ ├── bytes.go │ ├── ca.go │ ├── ca_others.go │ ├── chan.go │ ├── conn.go │ ├── io.go │ ├── net.go │ ├── rand.go │ ├── sni.go │ └── str.go ├── hosts │ └── hosts.go ├── logger │ ├── ansi_color.go │ ├── color_console_posix.go │ ├── color_console_windows.go │ └── log.go ├── mux │ ├── const.go │ ├── http2.go │ ├── io.go │ ├── mux.go │ ├── mux_test.go │ ├── quic.go │ └── ws.go ├── netx │ └── netx.go ├── protector │ ├── config.go │ ├── protected.go │ ├── protected_windows.go │ └── resolver.go └── socks │ ├── args.go │ └── socks.go ├── hosts.json ├── local ├── admin.go ├── config.go ├── gsnova │ ├── gsnova.go │ └── gsnova_unix.go ├── local_server.go ├── proxy.go ├── transparent_other.go ├── transparent_unix.go └── udpgw.go ├── main.go ├── remote ├── Procfile ├── config.go ├── manifest.yml ├── proxy.go └── web.go ├── server.json └── shell ├── centos_deploy_server.sh ├── cnipset.sh └── iptables.sh /.gitignore: -------------------------------------------------------------------------------- 1 | gsnova.log* 2 | my.json 3 | .DS_Store 4 | Godeps 5 | .exe 6 | vendor 7 | .DS_Store 8 | *.tar.gz 9 | *.tar.bz2 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | 5 | language: go 6 | 7 | go: 8 | - "1.10.3" 9 | 10 | env: 11 | global: 12 | - secure: "oQ5ZkuQJ3z/14dLFPhWqGvuTEY0CNydGMH495br71LQVkJlsM96Re5gmFXhSftkdAaMgcRRkDCPVxjOIl0NogLOt1tVT8w7byKEI8i7GkacQavrsW0ORiFkt4FbI+vwS0zkamG+M/eU0RkXl482/6QRs6QyvF6vEbFDogEOYw9M" 13 | - secure: "H+lBfJo6b7KXFvM/PT6K90fcHgpjM1czTvG8zb+waeWfKSxO1zUFzhzSt2IJo0Je5hL2djUM7iME9xsIUNyY4Q4y5tjUavBuc16F+oSAeMNJuBg+V+JHIWCvEMs/CtmPGqTKm6ZNn34HN62j/oFGqZWmgXPbmN5IyFfJLIwllDc=" 14 | - secure: "bRj17E9wb/iElTncxbIUO3ph3vbSXsTeXL/+nGQM6djDDDwzElieEs92ZeatGS0aET7xgXcEYALCEdUsve8giNRBSN3p6Zwg3OP2fT4E9fV778rc+0lpH/goVphUzRRn5bsbmM6epVBO/XvhGQMWZ2RCWsE3mtFt+EoCywIRhcs=" 15 | 16 | install: 17 | - go get github.com/golang/dep/cmd/dep 18 | - go get -t github.com/yinqiwen/gsnova 19 | script: 20 | - dep init -gopath 21 | - echo "\n[metadata.heroku]\n root-package = \"github.com/yinqiwen/gsnova\"\n go-version = \"go1.10.3\"\n ensure = \"false\"\n" >> Gopkg.toml 22 | - tar cf tmp-deploy.tar Gopkg.toml Gopkg.lock vendor local common remote main.go server.json && tar uf tmp-deploy.tar -C remote Procfile manifest.yml && bzip2 tmp-deploy.tar && mv tmp-deploy.tar.bz2 gsnova-paas-deploy-with-dependencies-${TRAVIS_TAG}.tar.bz2 23 | - export CGO_ENABLED=0 24 | - GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o gsnova.exe github.com/yinqiwen/gsnova 25 | - tar cjf gsnova_windows_386-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova.exe 26 | - GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o gsnova.exe github.com/yinqiwen/gsnova 27 | - tar cjf gsnova_windows_amd64-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova.exe 28 | - GOOS=linux GOARCH=386 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 29 | - tar cjf gsnova_linux_386-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 30 | - GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 31 | - tar cjf gsnova_linux_amd64-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 32 | - GOOS=linux GOARCH=arm GOARM=5 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 33 | - tar cjf gsnova_linux_armv5-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 34 | - GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 35 | - tar cjf gsnova_linux_armv6-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 36 | - GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 37 | - tar cjf gsnova_linux_armv7-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 38 | - GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 39 | - tar cjf gsnova_linux_arm64-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 40 | - GOOS=linux GOARCH=mipsle go build -ldflags="-s -w" github.com/yinqiwen/gsnova 41 | - tar cjf gsnova_linux_mipsle-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 42 | - GOOS=linux GOARCH=mips go build -ldflags="-s -w" github.com/yinqiwen/gsnova 43 | - tar cjf gsnova_linux_mips-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 44 | - GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" github.com/yinqiwen/gsnova 45 | - tar cjf gsnova_macos_amd64-${TRAVIS_TAG}.tar.bz2 client.json server.json hosts.json cnipset.txt gsnova 46 | 47 | 48 | deploy: 49 | - provider: releases 50 | api_key: 51 | secure: YngDT2JlydCG+qb8Ww9wBFyWiLRQJczybNLeoFKluBXKoh1Vu+TZeReMK+rRUjOqOmYV8sNtOnEVSg4+ICeYDNp9fRRwcNKTkp0H+8pH0OynxdH2faCjx1H5U0WQuWqMLQoMSHZ90I0tL40kEJOChLY6WGvwY47YBqi/dsvmTfg= 52 | file: 53 | - "gsnova-paas-deploy-with-dependencies-${TRAVIS_TAG}.tar.bz2" 54 | - "gsnova_windows_386-${TRAVIS_TAG}.tar.bz2" 55 | - "gsnova_windows_amd64-${TRAVIS_TAG}.tar.bz2" 56 | - "gsnova_linux_386-${TRAVIS_TAG}.tar.bz2" 57 | - "gsnova_linux_amd64-${TRAVIS_TAG}.tar.bz2" 58 | - "gsnova_linux_armv5-${TRAVIS_TAG}.tar.bz2" 59 | - "gsnova_linux_armv6-${TRAVIS_TAG}.tar.bz2" 60 | - "gsnova_linux_armv7-${TRAVIS_TAG}.tar.bz2" 61 | - "gsnova_linux_arm64-${TRAVIS_TAG}.tar.bz2" 62 | - "gsnova_linux_mips-${TRAVIS_TAG}.tar.bz2" 63 | - "gsnova_linux_mipsle-${TRAVIS_TAG}.tar.bz2" 64 | - "gsnova_macos_amd64-${TRAVIS_TAG}.tar.bz2" 65 | skip_cleanup: true 66 | overwrite: true 67 | on: 68 | tags: true 69 | repo: yinqiwen/gsnova 70 | - provider: script 71 | script: bash ./Docker/server/docker_push.sh 72 | skip_cleanup: true 73 | on: 74 | tags: true 75 | - provider: heroku 76 | api_key: 77 | secure: "PsICvdYk4eDTxFyS1j/DPAF6H7GCsNpkWcZHyFBROh60gxXGU+XHdUTQrrFHSPzLi6b8mDe4NOCtdrfA1xuluv6IbdbGQB+gM2lvRKse9x8sojh57rqvVAcaeCc/NsY7W/eAnk8Rd8TcW5nqrEg3qpUbl/Vio74ZQW/ZY+P0ZaQ=" 78 | app: $HEROKU_APP 79 | skip_cleanup: true 80 | on: 81 | tags: true 82 | condition: $TRAVIS_TAG = 'latest' 83 | 84 | 85 | -------------------------------------------------------------------------------- /Docker/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | 3 | ARG TZ="Asia/Shanghai" 4 | ARG GSNOVA_VER="latest" 5 | 6 | ENV TZ ${TZ} 7 | ENV GSNOVA_VER ${GSNOVA_VER} 8 | ENV GSNOVA_DOWNLOAD_URL https://github.com/yinqiwen/gsnova/releases/download/${GSNOVA_VER}/gsnova_linux_amd64-${GSNOVA_VER}.tar.bz2 9 | 10 | RUN apk upgrade --update \ 11 | && apk add curl tzdata \ 12 | && mkdir gsnova \ 13 | && (cd gsnova && curl -sfSL ${GSNOVA_DOWNLOAD_URL} | tar xj) \ 14 | && mv /gsnova/gsnova /usr/bin/gsnova \ 15 | && mv /gsnova /etc/gsnova \ 16 | && chmod +x /usr/bin/gsnova \ 17 | && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \ 18 | && echo ${TZ} > /etc/timezone \ 19 | && apk del curl \ 20 | && rm -rf /var/cache/apk/* 21 | 22 | # Document that the service listens on port 9443. 23 | EXPOSE 9443 9444 9445 9446 24 | 25 | # Run the outyet command by default when the container starts. 26 | CMD ["gsnova","-cmd" ,"-server", "-key", "809240d3a021449f6e67aa73221d42df942a308a", "-listen", "tcp://:9443", "-listen", "quic://:9443", "-listen", "http://:9444", "-listen", "kcp://:9444", "-listen", "http2://:9445", "-listen", "tls://:9446", "-log", "console"] 27 | -------------------------------------------------------------------------------- /Docker/server/docker_push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 4 | docker build --build-arg GSNOVA_VER=${TRAVIS_TAG} -t gsnova/gsnova-server:${TRAVIS_TAG} ./Docker/server 5 | docker push gsnova/gsnova-server:${TRAVIS_TAG} -------------------------------------------------------------------------------- /Docker/server/latest/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from a Debian image with the latest version of Go installed 2 | # and a workspace (GOPATH) configured at /go. 3 | FROM golang:1.9-alpine3.6 4 | 5 | # Copy the local package files to the container's workspace. 6 | #ADD ./server.json /go/bin 7 | 8 | RUN apk add --update git && \ 9 | go get -t github.com/yinqiwen/gsnova && \ 10 | go install github.com/yinqiwen/gsnova 11 | 12 | #WORKDIR /go/bin 13 | # Run the outyet command by default when the container starts. 14 | #ENTRYPOINT ["/go/bin/vps"] 15 | CMD ["/go/bin/gsnova", "-cmd" ,"-server", "-key","809240d3a021449f6e67aa73221d42df942a308a", "-listen", "tcp://:9443", "-listen", "quic://:9443", "-listen", "http://:9444", "-listen", "kcp://:9444", "-listen", "http2://:9445", "-listen", "tls://:9446", "-log", "console"] 16 | # Document that the service listens on port 9443. 17 | EXPOSE 9443 9444 9445 9446 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, yinqiwen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the gsnova nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GSnova: Private Proxy Solution & Network Troubleshooting Tool. 2 | [![Join the chat at https://gitter.im/gsnova/Lobby](https://badges.gitter.im/gsnova/Lobby.svg)](https://gitter.im/gsnova/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 3 | [![Build Status](https://travis-ci.org/yinqiwen/gsnova.svg?branch=master)](https://travis-ci.org/yinqiwen/gsnova) 4 | 5 | ``` 6 | 7 | ___ ___ ___ ___ ___ ___ 8 | /\ \ /\ \ /\__\ /\ \ /\__\ /\ \ 9 | /::\ \ /::\ \ /::| | /::\ \ /:/ / /::\ \ 10 | /:/\:\ \ /:/\ \ \ /:|:| | /:/\:\ \ /:/ / /:/\:\ \ 11 | /:/ \:\ \ _\:\~\ \ \ /:/|:| |__ /:/ \:\ \ /:/__/ ___ /::\~\:\ \ 12 | /:/__/_\:\__\/\ \:\ \ \__\/:/ |:| /\__\/:/__/ \:\__\|:| | /\__\/:/\:\ \:\__\ 13 | \:\ /\ \/__/\:\ \:\ \/__/\/__|:|/:/ /\:\ \ /:/ /|:| |/:/ /\/__\:\/:/ / 14 | \:\ \:\__\ \:\ \:\__\ |:/:/ / \:\ /:/ / |:|__/:/ / \::/ / 15 | \:\/:/ / \:\/:/ / |::/ / \:\/:/ / \::::/__/ /:/ / 16 | \::/ / \::/ / /:/ / \::/ / ~~~~ /:/ / 17 | \/__/ \/__/ \/__/ \/__/ \/__/ 18 | 19 | 20 | ``` 21 | **Deprecated, use the rust version [rsnova](https://github.com/yinqiwen/rsnova) instead.** 22 | 23 | # Features 24 | - Multiple transport channel support 25 | - http/https 26 | - http2 27 | - websocket 28 | - tcp/tls 29 | - quic 30 | - kcp 31 | - ssh 32 | - Multiplexing 33 | - All proxy connections running over N persist proxy channel connections 34 | - Simple PAC(Proxy Auto Config) 35 | - Multiple Ciphers support 36 | - Chacha20Poly1305 37 | - Salsa20 38 | - AES128 39 | - HTTP/Socks4/Socks5 Proxy 40 | - Local client running as HTTP/Socks4/Socks5 Proxy 41 | - Transparent TCP/UDP Proxy 42 | - Transparent tcp/udp proxy implementation in pure golang 43 | - Multi-hop Proxy 44 | - TLS man-in-the-middle(MITM) Proxy 45 | - HTTP(S) Packet Capture for Web Debugging 46 | - Log HTTP(S) Packets in file 47 | - Forward HTTP(S) Packets to Remote HTTP Server 48 | - P2P/P2S2P Proxy 49 | - P2P: Use TCP NAT tunnel for direct P2P commnunication if possible 50 | - P2S2P: Use middle server for two peers to communication 51 | - Use UPNP to expose port for remote p2p peer if possible. 52 | - Low-memory Environments Support 53 | - Use less than 20MB RSS memory at client/server side 54 | 55 | 56 | # Usage 57 | **go1.9 or higher is requied.** 58 | 59 | ## Compile 60 | ```shell 61 | go get -t -u -v github.com/yinqiwen/gsnova 62 | ``` 63 | There is also prebuilt binary release at [here](https://github.com/yinqiwen/gsnova/releases) 64 | 65 | ## Command Line Usage 66 | ``` 67 | Usage of ./gsnova: 68 | -admin string 69 | Client Admin listen address 70 | -blackList value 71 | Proxy blacklist item config 72 | -client 73 | Launch gsnova as client. 74 | -cmd 75 | Launch gsnova by command line without config file. 76 | -cnip string 77 | China IP list. (default "./cnipset.txt") 78 | -conf string 79 | Config file of gsnova. 80 | -forward value 81 | Forward connection to specified address 82 | -hosts string 83 | Hosts file of gsnova client. (default "./hosts.json") 84 | -httpdump.dst string 85 | HTTP Dump destination file or http url 86 | -httpdump.filter value 87 | HTTP Dump Domain Filter, eg:*.google.com 88 | -key string 89 | Cipher key for transmission between local&remote. (default "809240d3a021449f6e67aa73221d42df942a308a") 90 | -listen value 91 | Listen on address. 92 | -log string 93 | Log file setting (default "color,gsnova.log") 94 | -mitm 95 | Launch gsnova as a MITM Proxy 96 | -ots string 97 | Online trouble shooting listen address 98 | -p2p string 99 | P2P Token. 100 | -pid string 101 | PID file (default ".gsnova.pid") 102 | -ping_interval int 103 | Channel ping interval seconds. (default 30) 104 | -pprof string 105 | PProf trouble shooting listen address 106 | -proxy string 107 | Proxy setting to connect remote server. 108 | -remote value 109 | Next remote proxy hop server to connect for client, eg:wss://xxx.paas.com 110 | -servable 111 | Client as a proxy server for peer p2p client 112 | -server 113 | Launch gsnova as server. 114 | -stream_idle int 115 | Mux stream idle timout seconds. (default 10) 116 | -tls.cert string 117 | TLS Cert file 118 | -tls.key string 119 | TLS Key file 120 | -upnp int 121 | UPNP port to expose for p2p. 122 | -user string 123 | Username for remote server to authorize. (default "gsnova") 124 | -version 125 | Print version. 126 | -whitelist value 127 | Proxy whitelist item config 128 | -window string 129 | Max mux stream window size, default 512K 130 | -window_refresh string 131 | Mux stream window refresh size, default 32K 132 | ``` 133 | 134 | ## Deploy & Run Server 135 | 136 | ```shell 137 | ./gsnova -cmd -server -listen tcp://:48100 -listen quic://:48100 -listen tls://:48101 -listen kcp://:48101 -listen http://:48102 -listen http2://:48103 -key 809240d3a021449f6e67aa73221d42df942a308a -user "*" 138 | ``` 139 | This would launch a running instance listening at serveral ports with different transport protocol. 140 | 141 | The server can also be deployed to serveral PAAS service like heroku/openshift and some docker host service. 142 | 143 | ## Deploy & Run Client 144 | 145 | ### Run From Command Line 146 | ``` 147 | ./gsnova -cmd -client -listen :48100 -remote http2://app1.openshiftapps.com -key 809240d3a021449f6e67aa73221d42df942a308a 148 | ``` 149 | This would launch a socks4/socks5/http proxy at port 48100 and use http2://app1.openshiftapps.com as next proxy hop. 150 | 151 | ### Run With Confguration 152 | 153 | This is a sample for [client.json](https://github.com/yinqiwen/gsnova/blob/master/client.json), the `Key` and the `ServerList` need to be modified to match your server. 154 | ``` 155 | ./gsnova -client -conf ./client.json 156 | ``` 157 | 158 | ### Advanced Usage 159 | #### Multi-Hop Proxy 160 | GSnova support more than ONE remote server as the next hops, just add more `-remote server` arguments to enable multi-hop proxy. 161 | This would use `http2://app1.openshiftapps.com` as the first proxy ho and use `wss://app2.herokuapp.com` as the final proxy hop. 162 | ```shell 163 | ./gsnova -cmd -client -listen :48101 -remote http2://app1.openshiftapps.com -remote wss://app2.herokuapp.com -key 809240d3a021449f6e67aa73221d42df942a308a 164 | ``` 165 | #### Transparent Proxy 166 | - Edit iptables rules. 167 | - It's only works on linux. 168 | 169 | #### MITM Proxy 170 | GSnova support running the client as a MITM proxy to capture HTTP(S) packets for web debuging. 171 | This would capture HTTP(S) traffic packets into local dist file `httpdump.log`. 172 | ```shell 173 | ./gsnova -cmd -client -listen :48101 -remote direct -mitm -httpdump.dst ./httpdump.log -httpdump.filter "*.google.com" -httpdump.filter "*.facebook.com" 174 | ``` 175 | 176 | #### P2P/P2S2P Proxy 177 | P2P/P2S2P Proxy can help you to connect two nodes, and use one of them as a tcp proxy server for the other one. This feature can be used for scenarios like: 178 | - Expose any tcp based service behind a NAT or firewall to a specific node in the internet. 179 | 180 | There are 3 nodes which should install/run gsnova, a middle server(S) with public IP address, two client nodes(A & B) behind a NAT or firewall. 181 | For the middle server(S), run as a server with a cipher key. 182 | ```shell 183 | ./gsnova -cmd -server -listen tcp://:48103 -key p2pkey -log color 184 | ``` 185 | For the node(B) as a proxy server, run as a client to connect server with a P2P token: 186 | ```shell 187 | ./gsnova -cmd -client -servable -key p2pkey -remote tcp://:48103 -p2p testp2p -log color 188 | ``` 189 | For the node(A) as a client for peer proxy server, run as a client to connect server with same P2P token: 190 | ```shell 191 | ./gsnova -cmd -client -listen :7788 -key p2pkey -remote tcp://:48103 -p2p testp2p -log color 192 | ``` 193 | If there is no error, now the node A with listen address :7788 can be used as a http/socks4/socks5 proxy to access servers behind a NAT or firewall which node B located in. 194 | 195 | And in gsnova, it would try to run with P2P mode first, if it's not pissible, it would use P2S2P mode which would use the middle server to forward tcp stream to remote peeer. 196 | 197 | ## Mobile Client(Android) 198 | The client side can be compiled to android library by `gomobile`, eg: 199 | ``` 200 | gomobile bind -target=android -a -v github.com/yinqiwen/gsnova/local/gsnova 201 | ``` 202 | Users can develop there own app by using the generated `gsnova.aar`. 203 | There is a very simple andorid app [gsnova-android-v0.27.3.1.zip](https://github.com/yinqiwen/gsnova/releases/download/v0.27.3/gsnova-android-v0.27.3.1.zip) which use `tun2socks` + `gsnova` to build. 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /client.json: -------------------------------------------------------------------------------- 1 | { 2 | //this is just a example 3 | "Log": ["color", "gsnova.log"], 4 | "UserAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", 5 | //encrypt method can choose from none/auto/salsa20/chacha20poly1305/aes256-gcm 6 | //'auto' method would choose fastest encrypt method for current env 7 | "Cipher":{"Method":"auto", "Key":"809240d3a021449f6e67aa73221d42df942a308a", "User": "gsnova"}, 8 | "Mux":{ 9 | "MaxStreamWindow": "512K", 10 | "StreamMinRefresh":"32K", 11 | "StreamIdleTimeout":10, 12 | "SessionIdleTimeout":300 13 | }, 14 | 15 | "ProxyLimit":{ 16 | "WhiteList":["*"], 17 | "BlackList":[] 18 | }, 19 | 20 | // expose port by upnp for p2p 21 | //"UPNPExposePort":56789, 22 | 23 | "LocalDNS":{ 24 | //only listen UDP 25 | "Listen": "127.0.0.1:5300", 26 | "FastDNS":["223.5.5.5","180.76.76.76"], 27 | "TrustedDNS": ["208.67.222.222", "208.67.220.220"] 28 | }, 29 | 30 | "UDPGW":{ 31 | //fake address, only used as udp protocol indicator 32 | "Addr":"20.20.20.20:1111" 33 | }, 34 | 35 | "SNI":{ 36 | //Used to redirect SNI host to another for sniffed SNI 37 | "Redirect":{ 38 | //This fix "DF-DFERH-01" error in HW phone for google play 39 | "services.googleapis.cn":"services.googleapis.com" 40 | } 41 | }, 42 | 43 | //used to handle admin command from http client 44 | "Admin":{ 45 | //a local http server, do NOT expose this http server to public 46 | //listen on private IP instead of the default config 47 | //eg: "Listen": "192.168.1.1:7788", 48 | "Listen": ":7788", 49 | //used to broadcast admin server address. 50 | "BroadcastAddr":"224.0.0.1:48100", 51 | "ConfigDir":"./android" 52 | }, 53 | 54 | "GFWList":{ 55 | "URL":"https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt", 56 | "Proxy":"", 57 | "UserRule":[] 58 | }, 59 | 60 | "Proxy":[ 61 | { 62 | "Local": ":48100", 63 | //used to indicate if it's a MITM proxy server, which would use generated cert for TLS connections 64 | "MITM": false, 65 | "HTTPDump":{ 66 | "Dump":"./httpdump-48100.txt", 67 | "Domain":[], 68 | "ExcludeBody":["text/css"] 69 | }, 70 | "PAC":[ 71 | //{"Protocol":["dns", "udp"],"Remote":"direct"}, 72 | // Support rules 'IsCNIP/InHosts/BlockedByGFW' 73 | //{"Rule":["InHosts"],"Remote":"direct"}, 74 | //{"Rule":["!IsCNIP"],"Remote":"heroku"}, 75 | //{"Rule":["BlockedByGFW"],"Remote":"heroku"}, 76 | //{"Host":["*notexist_domain.com"],"Remote":"Reject"}, 77 | //{"Host":["*"],"Remote":"direct"}, 78 | //{"URL":["*"],"Remote":"direct"}, 79 | //{"Method":["CONNECT"],"Remote":"direct"} 80 | {"Rule":["IsPrivateIP"],"Remote":"direct"}, 81 | {"Remote":"Default"} 82 | ] 83 | }, 84 | { 85 | "Local": ":48101", 86 | "PAC":[ 87 | {"Remote":"heroku-websocket"} 88 | ] 89 | }, 90 | { 91 | "Local": ":48102", 92 | "PAC":[ 93 | {"Remote":"vps-quic"} 94 | ] 95 | } 96 | ], 97 | 98 | "Channel":[ 99 | { 100 | "Enable":false, 101 | "Name":"heroku-websocket", 102 | //Allowed server url with schema 'http/http2/https/ws/wss/tcp/tls/quic/kcp/ssh' 103 | //"ServerList":["quic://1.1.1.1:48101"], 104 | "ServerList":["wss://xyz.herokuapp.com"], 105 | //"ServerList":["tcp://127.0.0.1:18080"], 106 | //"ServerList":["ssh://root@1.1.1.1:22?key=./PPP"], 107 | //if u are behind a HTTP proxy 108 | "Proxy":"", 109 | "ConnsPerServer":3, 110 | "RemoteDialMSTimeout":5000, 111 | "RemoteDNSReadMSTimeout":1500, 112 | "RemoteUDPReadMSTimeout":15000, 113 | "LocalDialMSTimeout":5000, 114 | //Reconnect after 120s 115 | "ReconnectPeriod": 300, 116 | //ReconnectPeriod rand adjustment, the real reconnect period is random value between [P - adjust, P + adjust] 117 | "RCPRandomAdjustment" : 10, 118 | //Send heartbeat msg to keep alive 119 | "HeartBeatPeriod": 30, 120 | "Compressor":"none", 121 | "Hops":[], 122 | //Use matched RemoteSNI host to connect at remote side 123 | "RemoteSNIProxy":{ 124 | //"*.google.*":"GoogleHKSNI" 125 | } 126 | }, 127 | { 128 | "Enable":false, 129 | "Name":"vps-quic", 130 | "ServerList":["quic://1.1.1.1:8080"], 131 | //if u are behind a HTTP proxy 132 | "Proxy":"", 133 | "ConnsPerServer":3, 134 | "RemoteDialMSTimeout":5000, 135 | "RemoteDNSReadMSTimeout":1500, 136 | "RemoteUDPReadMSTimeout":15000, 137 | "LocalDialMSTimeout":5000, 138 | //Reconnect after 120s 139 | "ReconnectPeriod": 1800, 140 | //ReconnectPeriod rand adjustment, the real reconnect period is random value between [P - adjust, P + adjust] 141 | "RCPRandomAdjustment" : 60, 142 | //Send heartbeat msg to keep alive 143 | "HeartBeatPeriod": 30, 144 | "Compressor":"none", 145 | "KCP":{ 146 | "Mode":"fast2" 147 | }, 148 | "Hops":[] 149 | } 150 | ] 151 | } 152 | -------------------------------------------------------------------------------- /common/channel/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | _ "github.com/yinqiwen/gsnova/common/channel/direct" 5 | _ "github.com/yinqiwen/gsnova/common/channel/http" 6 | _ "github.com/yinqiwen/gsnova/common/channel/http2" 7 | _ "github.com/yinqiwen/gsnova/common/channel/kcp" 8 | _ "github.com/yinqiwen/gsnova/common/channel/quic" 9 | _ "github.com/yinqiwen/gsnova/common/channel/ssh" 10 | _ "github.com/yinqiwen/gsnova/common/channel/tcp" 11 | _ "github.com/yinqiwen/gsnova/common/channel/websocket" 12 | ) 13 | -------------------------------------------------------------------------------- /common/channel/config.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/url" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/yinqiwen/gsnova/common/helper" 12 | "github.com/yinqiwen/gsnova/common/logger" 13 | "github.com/yinqiwen/gsnova/common/mux" 14 | "github.com/yinqiwen/pmux" 15 | ) 16 | 17 | var ErrNotSupportedOperation = errors.New("Not supported operation") 18 | 19 | type ProxyLimitConfig struct { 20 | WhiteList []string 21 | BlackList []string 22 | } 23 | 24 | func (limit *ProxyLimitConfig) Allowed(host string) bool { 25 | if len(limit.WhiteList) == 0 && len(limit.BlackList) == 0 { 26 | return true 27 | } 28 | if len(limit.BlackList) > 0 { 29 | for _, rule := range limit.BlackList { 30 | if rule == "*" { 31 | return false 32 | } else { 33 | matched, _ := filepath.Match(rule, host) 34 | if matched { 35 | return false 36 | } 37 | } 38 | } 39 | return true 40 | } 41 | 42 | for _, rule := range limit.WhiteList { 43 | if rule == "*" { 44 | return true 45 | } else { 46 | matched, _ := filepath.Match(rule, host) 47 | if matched { 48 | return true 49 | } 50 | } 51 | } 52 | return false 53 | } 54 | 55 | type FeatureSet struct { 56 | AutoExpire bool 57 | Pingable bool 58 | } 59 | 60 | type MuxConfig struct { 61 | MaxStreamWindow string 62 | StreamMinRefresh string 63 | StreamIdleTimeout int 64 | SessionIdleTimeout int 65 | UpBufferSize int 66 | DownBufferSize int 67 | } 68 | 69 | func (m *MuxConfig) ToPMuxConf() *pmux.Config { 70 | cfg := pmux.DefaultConfig() 71 | cfg.EnableKeepAlive = false 72 | 73 | if len(m.MaxStreamWindow) > 0 { 74 | v, err := helper.ToBytes(m.MaxStreamWindow) 75 | if nil == err { 76 | cfg.MaxStreamWindowSize = uint32(v) 77 | } 78 | } 79 | if len(m.StreamMinRefresh) > 0 { 80 | v, err := helper.ToBytes(m.StreamMinRefresh) 81 | if nil == err { 82 | cfg.StreamMinRefresh = uint32(v) 83 | } 84 | } 85 | return cfg 86 | } 87 | 88 | type CipherConfig struct { 89 | User string 90 | Method string 91 | Key string 92 | 93 | allowedUser []string 94 | } 95 | 96 | func (conf *CipherConfig) Adjust() { 97 | if len(conf.Method) == 0 { 98 | conf.Method = "auto" 99 | } 100 | switch conf.Method { 101 | case "auto": 102 | if strings.Contains(runtime.GOARCH, "386") || strings.Contains(runtime.GOARCH, "amd64") { 103 | conf.Method = pmux.CipherAES256GCM 104 | } else if strings.Contains(runtime.GOARCH, "arm") { 105 | conf.Method = pmux.CipherChacha20Poly1305 106 | } 107 | case pmux.CipherChacha20Poly1305: 108 | case pmux.CipherSalsa20: 109 | case pmux.CipherAES256GCM: 110 | case pmux.CipherNone: 111 | default: 112 | logger.Error("Invalid encrypt method:%s, use 'chacha20poly1305' instead.", conf.Method) 113 | conf.Method = pmux.CipherChacha20Poly1305 114 | } 115 | } 116 | 117 | func (conf *CipherConfig) AllowUsers(users string) { 118 | if len(users) > 0 { 119 | conf.allowedUser = strings.Split(users, ",") 120 | } 121 | } 122 | 123 | func (conf *CipherConfig) VerifyUser(user string) bool { 124 | if len(conf.allowedUser) == 0 { 125 | return true 126 | } 127 | for _, u := range conf.allowedUser { 128 | if u == user || u == "*" { 129 | //log.Printf("Valid user:%s", user) 130 | return true 131 | } 132 | } 133 | logger.Error("[ERROR]Invalid user:%s", user) 134 | return false 135 | } 136 | 137 | type RateLimitConfig struct { 138 | Limit map[string]string 139 | } 140 | 141 | type HTTPBaseConfig struct { 142 | HTTPPushRateLimitPerSec int 143 | UserAgent string 144 | ReadTimeout int 145 | } 146 | type HTTPConfig struct { 147 | HTTPBaseConfig 148 | } 149 | 150 | func (hcfg *HTTPConfig) UnmarshalJSON(data []byte) error { 151 | hcfg.HTTPPushRateLimitPerSec = 3 152 | hcfg.ReadTimeout = 30000 153 | err := json.Unmarshal(data, &hcfg.HTTPBaseConfig) 154 | return err 155 | } 156 | 157 | type KCPBaseConfig struct { 158 | Mode string 159 | Conn int 160 | AutoExpire int 161 | ScavengeTTL int 162 | MTU int 163 | SndWnd int 164 | RcvWnd int 165 | DataShard int 166 | ParityShard int 167 | DSCP int 168 | AckNodelay bool 169 | NoDelay int 170 | Interval int 171 | Resend int 172 | NoCongestion int 173 | SockBuf int 174 | } 175 | 176 | func (kcfg *KCPBaseConfig) InitDefaultConf() { 177 | kcfg.Mode = "fast" 178 | kcfg.Conn = 1 179 | kcfg.AutoExpire = 0 180 | kcfg.ScavengeTTL = 600 181 | kcfg.MTU = 1350 182 | kcfg.SndWnd = 128 183 | kcfg.RcvWnd = 512 184 | kcfg.DataShard = 10 185 | kcfg.ParityShard = 3 186 | kcfg.DSCP = 0 187 | kcfg.AckNodelay = true 188 | kcfg.NoDelay = 0 189 | kcfg.Interval = 50 190 | kcfg.Resend = 0 191 | kcfg.Interval = 50 192 | kcfg.NoCongestion = 0 193 | kcfg.SockBuf = 4194304 194 | } 195 | 196 | type KCPConfig struct { 197 | KCPBaseConfig 198 | } 199 | 200 | func (config *KCPConfig) adjustByMode() { 201 | switch config.Mode { 202 | case "normal": 203 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 40, 2, 1 204 | case "fast": 205 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 30, 2, 1 206 | case "fast2": 207 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 20, 2, 1 208 | case "fast3": 209 | config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 10, 2, 1 210 | } 211 | } 212 | func (kcfg *KCPConfig) UnmarshalJSON(data []byte) error { 213 | kcfg.KCPBaseConfig.InitDefaultConf() 214 | err := json.Unmarshal(data, &kcfg.KCPBaseConfig) 215 | if nil == err { 216 | kcfg.adjustByMode() 217 | } 218 | return err 219 | } 220 | 221 | type HopServers []string 222 | 223 | func (i *HopServers) String() string { 224 | return "HopServers" 225 | } 226 | 227 | func (i *HopServers) Set(value string) error { 228 | *i = append(*i, value) 229 | return nil 230 | } 231 | 232 | type ProxyChannelConfig struct { 233 | Enable bool 234 | Name string 235 | ServerList []string 236 | ConnsPerServer int 237 | SNI []string 238 | SNIProxy string 239 | Proxy string 240 | RemoteDialMSTimeout int 241 | RemoteDNSReadMSTimeout int 242 | RemoteUDPReadMSTimeout int 243 | LocalDialMSTimeout int 244 | ReconnectPeriod int 245 | HeartBeatPeriod int 246 | RCPRandomAdjustment int 247 | Compressor string 248 | KCP KCPConfig 249 | HTTP HTTPConfig 250 | Cipher CipherConfig 251 | Hops HopServers 252 | RemoteSNIProxy map[string]string 253 | HibernateAfterSecs int 254 | P2PToken string 255 | P2S2PEnable bool 256 | 257 | proxyURL *url.URL 258 | lazyConnect bool 259 | } 260 | 261 | func (conf *ProxyChannelConfig) GetRemoteSNI(domain string) string { 262 | if nil != conf.RemoteSNIProxy { 263 | for k, v := range conf.RemoteSNIProxy { 264 | matched, err := filepath.Match(k, domain) 265 | if nil != err { 266 | logger.Error("Invalid pattern:%s with reason:%v", k, err) 267 | continue 268 | } 269 | if matched { 270 | return v 271 | } 272 | } 273 | } 274 | return "" 275 | } 276 | 277 | func (conf *ProxyChannelConfig) Adjust() { 278 | conf.Cipher.Adjust() 279 | if len(conf.KCP.Mode) == 0 { 280 | conf.KCP.InitDefaultConf() 281 | } 282 | conf.KCP.adjustByMode() 283 | if len(conf.Compressor) == 0 || !mux.IsValidCompressor(conf.Compressor) { 284 | conf.Compressor = mux.NoneCompressor 285 | } 286 | 287 | if conf.RCPRandomAdjustment > conf.ReconnectPeriod { 288 | conf.RCPRandomAdjustment = conf.ReconnectPeriod / 2 289 | } 290 | if conf.ConnsPerServer == 0 { 291 | conf.ConnsPerServer = 3 292 | } 293 | if 0 == conf.RemoteDNSReadMSTimeout { 294 | conf.RemoteDNSReadMSTimeout = 1000 295 | } 296 | if 0 == conf.RemoteUDPReadMSTimeout { 297 | conf.RemoteUDPReadMSTimeout = 15 * 1000 298 | } 299 | if 0 == conf.LocalDialMSTimeout { 300 | conf.LocalDialMSTimeout = 5000 301 | } 302 | if 0 == conf.HTTP.ReadTimeout { 303 | conf.HTTP.ReadTimeout = 30000 304 | } 305 | if 0 == conf.RemoteDialMSTimeout { 306 | conf.RemoteDialMSTimeout = 5000 307 | } 308 | if 0 == conf.HibernateAfterSecs { 309 | conf.HibernateAfterSecs = 1800 310 | } 311 | } 312 | 313 | func (c *ProxyChannelConfig) ProxyURL() *url.URL { 314 | if nil != c.proxyURL { 315 | return c.proxyURL 316 | } 317 | if len(c.Proxy) > 0 { 318 | var err error 319 | c.proxyURL, err = url.Parse(c.Proxy) 320 | if nil != err { 321 | logger.Error("Failed to parse proxy URL ", c.Proxy) 322 | } 323 | } 324 | return c.proxyURL 325 | } 326 | 327 | //var DefaultCipherKey string 328 | var defaultMuxConfig MuxConfig 329 | var defaultProxyLimitConfig ProxyLimitConfig 330 | 331 | func SetDefaultMuxConfig(cfg MuxConfig) { 332 | defaultMuxConfig = cfg 333 | } 334 | func SetDefaultProxyLimitConfig(cfg ProxyLimitConfig) { 335 | defaultProxyLimitConfig = cfg 336 | } 337 | 338 | func InitialPMuxConfig(cipher *CipherConfig) *pmux.Config { 339 | //cfg := pmux.DefaultConfig() 340 | cfg := defaultMuxConfig.ToPMuxConf() 341 | cfg.CipherKey = []byte(cipher.Key) 342 | cfg.CipherMethod = mux.DefaultMuxCipherMethod 343 | cfg.CipherInitialCounter = mux.DefaultMuxInitialCipherCounter 344 | //cfg.EnableKeepAlive = false 345 | //cfg.PingTimeout = 5 * time.Second 346 | return cfg 347 | } 348 | -------------------------------------------------------------------------------- /common/channel/dial.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "sync" 11 | "time" 12 | 13 | "github.com/yinqiwen/gsnova/common/dns" 14 | "github.com/yinqiwen/gsnova/common/helper" 15 | "github.com/yinqiwen/gsnova/common/hosts" 16 | "github.com/yinqiwen/gsnova/common/logger" 17 | "github.com/yinqiwen/gsnova/common/netx" 18 | "github.com/yinqiwen/gsnova/common/protector" 19 | ) 20 | 21 | func NewTLSConfig(conf *ProxyChannelConfig) *tls.Config { 22 | tlscfg := &tls.Config{} 23 | tlscfg.InsecureSkipVerify = true 24 | if len(conf.SNI) > 0 { 25 | tlscfg.ServerName = conf.SNI[0] 26 | } 27 | return tlscfg 28 | } 29 | 30 | func DialServerByConf(server string, conf *ProxyChannelConfig) (net.Conn, error) { 31 | rurl, err := url.Parse(server) 32 | if nil != err { 33 | return nil, err 34 | } 35 | hostport := rurl.Host 36 | tcpHost, tcpPort, err := net.SplitHostPort(hostport) 37 | if nil != err { 38 | switch rurl.Scheme { 39 | case "http", "ws", "tcp", "tcp4", "tcp6": 40 | tcpHost = rurl.Host 41 | tcpPort = "80" 42 | case "ssh": 43 | tcpPort = "22" 44 | tcpHost = rurl.Host 45 | case "http2", "https", "quic", "kcp", "tls", "wss": 46 | tcpHost = rurl.Host 47 | tcpPort = "443" 48 | default: 49 | return nil, fmt.Errorf("Invalid scheme:%s", rurl.Scheme) 50 | } 51 | hostport = net.JoinHostPort(tcpHost, tcpPort) 52 | } 53 | tlscfg := NewTLSConfig(conf) 54 | if len(tlscfg.ServerName) == 0 { 55 | if net.ParseIP(tcpHost) == nil { 56 | tlscfg.ServerName = tcpHost 57 | } 58 | } 59 | 60 | if len(conf.SNIProxy) > 0 && tcpPort == "443" { 61 | if net.ParseIP(conf.SNIProxy) == nil { 62 | if hosts.InHosts(conf.SNIProxy) { 63 | hostport = hosts.GetAddr(conf.SNIProxy, "443") 64 | tcpHost, _, _ = net.SplitHostPort(hostport) 65 | } else { 66 | logger.Info("SNIProxy Not exist in hosts:%s", conf.SNIProxy) 67 | } 68 | } else { 69 | tcpHost = conf.SNIProxy 70 | hostport = net.JoinHostPort(tcpHost, tcpPort) 71 | } 72 | logger.Info("Try to connect %s via sni proxy:%s", server, hostport) 73 | } 74 | 75 | var conn net.Conn 76 | dailTimeout := conf.LocalDialMSTimeout 77 | if 0 == dailTimeout { 78 | dailTimeout = 5000 79 | } 80 | timeout := time.Duration(dailTimeout) * time.Millisecond 81 | connAddr := hostport 82 | if len(conf.Proxy) == 0 { 83 | if net.ParseIP(tcpHost) == nil { 84 | iphost, err := dns.DnsGetDoaminIP(tcpHost) 85 | if nil != err { 86 | return nil, err 87 | } 88 | hostport = net.JoinHostPort(iphost, tcpPort) 89 | } 90 | if len(conf.P2PToken) > 0 { 91 | opt := &protector.NetOptions{ 92 | ReusePort: protector.SupportReusePort(), 93 | DialTimeout: timeout, 94 | } 95 | conn, err = protector.DialContextOptions(context.Background(), "tcp", hostport, opt) 96 | } else { 97 | conn, err = netx.DialTimeout("tcp", hostport, timeout) 98 | } 99 | 100 | } else { 101 | if len(conf.P2PToken) > 0 { 102 | conn, err = helper.ProxyDial(conf.Proxy, "", hostport, timeout, true) 103 | } else { 104 | conn, err = helper.ProxyDial(conf.Proxy, "", hostport, timeout, false) 105 | } 106 | connAddr = conf.Proxy 107 | } 108 | if nil == err { 109 | switch rurl.Scheme { 110 | case "tls": 111 | fallthrough 112 | case "http2": 113 | tlsconn := tls.Client(conn, tlscfg) 114 | err = tlsconn.Handshake() 115 | if err != nil { 116 | logger.Notice("TLS Handshake Failed %v", err) 117 | return nil, err 118 | } 119 | conn = tlsconn 120 | } 121 | } 122 | if nil != err { 123 | logger.Notice("Connect %s failed with reason:%v.", server, err) 124 | } else { 125 | logger.Debug("Connect %s success via %s.", server, connAddr) 126 | } 127 | return conn, err 128 | } 129 | 130 | func NewDialByConf(conf *ProxyChannelConfig, scheme string) func(network, addr string) (net.Conn, error) { 131 | localDial := func(network, addr string) (net.Conn, error) { 132 | //log.Printf("Connect %s", addr) 133 | server := fmt.Sprintf("%s://%s", scheme, addr) 134 | return DialServerByConf(server, conf) 135 | } 136 | return localDial 137 | } 138 | 139 | var httpClientMap sync.Map 140 | 141 | func NewHTTPClient(conf *ProxyChannelConfig, scheme string) (*http.Client, error) { 142 | tr := &http.Transport{ 143 | Dial: NewDialByConf(conf, scheme), 144 | DisableCompression: true, 145 | MaxIdleConnsPerHost: 2 * int(conf.ConnsPerServer), 146 | ResponseHeaderTimeout: time.Duration(conf.HTTP.ReadTimeout) * time.Millisecond, 147 | } 148 | // if len(conf.SNI) > 0 { 149 | // tlscfg := &tls.Config{} 150 | // tlscfg.InsecureSkipVerify = true 151 | // tlscfg.ServerName = conf.SNI[0] 152 | // tr.TLSClientConfig = tlscfg 153 | // } 154 | // if len(conf.Proxy) > 0 { 155 | // proxyUrl, err := url.Parse(conf.Proxy) 156 | // if nil != err { 157 | // logger.Error("[ERROR]Invalid proxy url:%s to create http client.", conf.Proxy) 158 | // return nil, err 159 | // } 160 | // tr.Proxy = http.ProxyURL(proxyUrl) 161 | // } 162 | hc := &http.Client{} 163 | //hc.Timeout = tr.ResponseHeaderTimeout 164 | hc.Transport = tr 165 | localClient, loaded := httpClientMap.LoadOrStore(conf, hc) 166 | if loaded { 167 | return localClient.(*http.Client), nil 168 | } 169 | return hc, nil 170 | } 171 | -------------------------------------------------------------------------------- /common/channel/direct/local.go: -------------------------------------------------------------------------------- 1 | package direct 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/url" 7 | "sync" 8 | "time" 9 | 10 | "github.com/yinqiwen/gsnova/common/channel" 11 | "github.com/yinqiwen/gsnova/common/dns" 12 | "github.com/yinqiwen/gsnova/common/helper" 13 | "github.com/yinqiwen/gsnova/common/hosts" 14 | "github.com/yinqiwen/gsnova/common/logger" 15 | "github.com/yinqiwen/gsnova/common/mux" 16 | "github.com/yinqiwen/gsnova/common/netx" 17 | ) 18 | 19 | type directStream struct { 20 | net.Conn 21 | conf *channel.ProxyChannelConfig 22 | addr string 23 | session *directMuxSession 24 | proxyServer string 25 | latestIOTime time.Time 26 | } 27 | 28 | func (tc *directStream) SetReadDeadline(t time.Time) error { 29 | if nil == tc.Conn { 30 | return io.EOF 31 | } 32 | return tc.Conn.SetReadDeadline(t) 33 | } 34 | func (tc *directStream) SetWriteDeadline(t time.Time) error { 35 | if nil == tc.Conn { 36 | return io.EOF 37 | } 38 | return tc.Conn.SetWriteDeadline(t) 39 | } 40 | 41 | func (tc *directStream) Auth(req *mux.AuthRequest) *mux.AuthResponse { 42 | return &mux.AuthResponse{Code: mux.AuthOK} 43 | } 44 | 45 | func (tc *directStream) Connect(network string, addr string, opt mux.StreamOptions) error { 46 | host, port, _ := net.SplitHostPort(addr) 47 | //log.Printf("Session:%d enter direct with host %s & event:%T", ev.GetId(), host, ev) 48 | 49 | if len(tc.conf.SNIProxy) > 0 && port == "443" && network == "tcp" && hosts.InHosts(tc.conf.SNIProxy) { 50 | host = tc.conf.SNIProxy 51 | } 52 | isIP := net.ParseIP(host) != nil 53 | if !isIP { 54 | host = hosts.GetHost(host) 55 | } 56 | 57 | var proxyURL *url.URL 58 | if nil == tc.conf.ProxyURL() && len(tc.proxyServer) == 0 { 59 | addr = host + ":" + port 60 | } else { 61 | if nil != tc.conf.ProxyURL() { 62 | addr = tc.conf.ProxyURL().Host 63 | proxyURL = tc.conf.ProxyURL() 64 | } else { 65 | u, err := url.Parse(tc.proxyServer) 66 | if nil != err { 67 | return err 68 | } 69 | addr = u.Host 70 | proxyURL = u 71 | } 72 | } 73 | connectHost, connectPort, _ := net.SplitHostPort(addr) 74 | if net.ParseIP(connectHost) == nil { 75 | iphost, err := dns.DnsGetDoaminIP(connectHost) 76 | if nil != err { 77 | return err 78 | } 79 | addr = net.JoinHostPort(iphost, connectPort) 80 | } 81 | //dailTimeout := tc.conf.DialTimeout 82 | if 0 == opt.DialTimeout { 83 | opt.DialTimeout = 5000 84 | } 85 | //log.Printf("Session:%d connect %s:%s for %s %T %v %v %s", ev.GetId(), network, addr, host, ev, needHttpsConnect, conf.ProxyURL(), net.JoinHostPort(host, port)) 86 | c, err := netx.DialTimeout(network, addr, time.Duration(opt.DialTimeout)*time.Millisecond) 87 | if nil != proxyURL && nil == err { 88 | switch proxyURL.Scheme { 89 | case "http_proxy": 90 | fallthrough 91 | case "http": 92 | err = helper.HTTPProxyConnect(tc.conf.ProxyURL(), c, "https://"+net.JoinHostPort(host, port)) 93 | case "socks": 94 | fallthrough 95 | case "socks4": 96 | fallthrough 97 | case "socks5": 98 | err = helper.Socks5ProxyConnect(tc.conf.ProxyURL(), c, net.JoinHostPort(host, port)) 99 | } 100 | } 101 | if nil != err { 102 | logger.Error("Failed to connect %s for %s with error:%v", addr, host, err) 103 | return err 104 | } 105 | 106 | tc.Conn = c 107 | tc.addr = addr 108 | return nil 109 | } 110 | 111 | func (tc *directStream) StreamID() uint32 { 112 | return 0 113 | } 114 | 115 | func (s *directStream) LatestIOTime() time.Time { 116 | return s.latestIOTime 117 | } 118 | 119 | func (tc *directStream) Read(p []byte) (int, error) { 120 | if nil == tc.Conn { 121 | return 0, io.EOF 122 | } 123 | tc.latestIOTime = time.Now() 124 | return tc.Conn.Read(p) 125 | } 126 | func (tc *directStream) Write(p []byte) (int, error) { 127 | if nil == tc.Conn { 128 | return 0, io.EOF 129 | } 130 | tc.latestIOTime = time.Now() 131 | return tc.Conn.Write(p) 132 | } 133 | 134 | func (tc *directStream) Close() error { 135 | conn := tc.Conn 136 | if nil != conn { 137 | conn.Close() 138 | tc.Conn = nil 139 | } 140 | tc.session.closeStream(tc) 141 | return nil 142 | } 143 | 144 | type directMuxSession struct { 145 | conf *channel.ProxyChannelConfig 146 | streams map[*directStream]bool 147 | streamsMutex sync.Mutex 148 | proxyServer string 149 | } 150 | 151 | func (s *directMuxSession) RemoteAddr() net.Addr { 152 | return nil 153 | } 154 | func (s *directMuxSession) LocalAddr() net.Addr { 155 | return nil 156 | } 157 | 158 | func (tc *directMuxSession) closeStream(s *directStream) { 159 | tc.streamsMutex.Lock() 160 | defer tc.streamsMutex.Unlock() 161 | delete(tc.streams, s) 162 | } 163 | 164 | func (tc *directMuxSession) CloseStream(stream mux.MuxStream) error { 165 | //stream.Close() 166 | return nil 167 | } 168 | 169 | func (tc *directMuxSession) OpenStream() (mux.MuxStream, error) { 170 | tc.streamsMutex.Lock() 171 | defer tc.streamsMutex.Unlock() 172 | stream := &directStream{ 173 | conf: tc.conf, 174 | session: tc, 175 | proxyServer: tc.proxyServer, 176 | } 177 | return stream, nil 178 | } 179 | func (tc *directMuxSession) AcceptStream() (mux.MuxStream, error) { 180 | return nil, channel.ErrNotSupportedOperation 181 | } 182 | 183 | func (tc *directMuxSession) NumStreams() int { 184 | tc.streamsMutex.Lock() 185 | defer tc.streamsMutex.Unlock() 186 | return len(tc.streams) 187 | } 188 | func (tc *directMuxSession) Ping() (time.Duration, error) { 189 | return 0, nil 190 | } 191 | 192 | func (tc *directMuxSession) Close() error { 193 | tc.streamsMutex.Lock() 194 | defer tc.streamsMutex.Unlock() 195 | for stream := range tc.streams { 196 | stream.Close() 197 | } 198 | tc.streams = make(map[*directStream]bool) 199 | return nil 200 | } 201 | 202 | type DirectProxy struct { 203 | proxyType string 204 | } 205 | 206 | func (p *DirectProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 207 | ps := server 208 | if len(p.proxyType) == 0 { 209 | ps = "" 210 | } 211 | session := &directMuxSession{ 212 | conf: conf, 213 | streams: make(map[*directStream]bool), 214 | proxyServer: ps, 215 | } 216 | return session, nil 217 | } 218 | 219 | func (p *DirectProxy) Features() channel.FeatureSet { 220 | return channel.FeatureSet{ 221 | AutoExpire: false, 222 | Pingable: false, 223 | } 224 | } 225 | 226 | func init() { 227 | channel.RegisterLocalChannelType(channel.DirectChannelName, &DirectProxy{}) 228 | channel.RegisterLocalChannelType("socks", &DirectProxy{"socks5"}) 229 | channel.RegisterLocalChannelType("socks4", &DirectProxy{"socks4"}) 230 | channel.RegisterLocalChannelType("socks5", &DirectProxy{"socks5"}) 231 | channel.RegisterLocalChannelType("http_proxy", &DirectProxy{"http_proxy"}) 232 | } 233 | -------------------------------------------------------------------------------- /common/channel/http/remote.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "sync/atomic" 11 | "time" 12 | 13 | "github.com/yinqiwen/gsnova/common/channel" 14 | "github.com/yinqiwen/gsnova/common/helper" 15 | "github.com/yinqiwen/gsnova/common/logger" 16 | "github.com/yinqiwen/gsnova/common/mux" 17 | "github.com/yinqiwen/pmux" 18 | ) 19 | 20 | type httpDuplexServConn struct { 21 | id string 22 | ackID string 23 | recvBuffer bytes.Buffer 24 | req *http.Request 25 | writer http.ResponseWriter 26 | writerStopCh chan struct{} 27 | recvLock sync.Mutex 28 | sendLock sync.Mutex 29 | running int32 30 | recvNotifyCh chan struct{} 31 | sendNotifyCh chan struct{} 32 | closeNotifyCh chan struct{} 33 | shutdownErr error 34 | lastActiveIOTime time.Time 35 | checkAliveTicker *time.Ticker 36 | } 37 | 38 | func (h *httpDuplexServConn) touch() { 39 | h.lastActiveIOTime = time.Now() 40 | } 41 | 42 | func (h *httpDuplexServConn) setReader(req *http.Request) { 43 | h.touch() 44 | h.req = req 45 | b := make([]byte, 8192) 46 | counter := 0 47 | for { 48 | n, err := req.Body.Read(b) 49 | if n > 0 { 50 | h.recvLock.Lock() 51 | h.recvBuffer.Write(b[0:n]) 52 | h.recvLock.Unlock() 53 | h.touch() 54 | helper.AsyncNotify(h.recvNotifyCh) 55 | } 56 | counter += n 57 | if nil != err { 58 | break 59 | } 60 | } 61 | //log.Printf("#####Chunk read %d bytes", counter) 62 | h.req = nil 63 | } 64 | 65 | func (h *httpDuplexServConn) setWriter(w http.ResponseWriter, ch chan struct{}) { 66 | h.touch() 67 | h.sendLock.Lock() 68 | if nil != h.writerStopCh { 69 | helper.AsyncNotify(h.writerStopCh) 70 | } 71 | h.writer = w 72 | h.writerStopCh = ch 73 | h.sendLock.Unlock() 74 | helper.AsyncNotify(h.sendNotifyCh) 75 | } 76 | 77 | func (h *httpDuplexServConn) init(id string) error { 78 | h.id = id 79 | h.ackID = helper.RandAsciiString(32) 80 | h.recvNotifyCh = make(chan struct{}) 81 | h.sendNotifyCh = make(chan struct{}) 82 | h.closeNotifyCh = make(chan struct{}) 83 | h.checkAliveTicker = time.NewTicker(10 * time.Second) 84 | h.lastActiveIOTime = time.Now() 85 | go func() { 86 | for _ = range h.checkAliveTicker.C { 87 | if !h.isRunning() { 88 | h.checkAliveTicker.Stop() 89 | return 90 | } 91 | if time.Now().Sub(h.lastActiveIOTime) > 2*time.Minute { 92 | h.checkAliveTicker.Stop() 93 | h.Close() 94 | logger.Debug("Stop http duplex conn:%s since it's not active since %v ago", h.id, time.Now().Sub(h.lastActiveIOTime)) 95 | return 96 | } 97 | } 98 | }() 99 | h.running = 1 100 | return nil 101 | } 102 | 103 | func (h *httpDuplexServConn) Read(b []byte) (n int, err error) { 104 | START: 105 | if !h.isRunning() { 106 | return 0, io.EOF 107 | } 108 | h.recvLock.Lock() 109 | if 0 == h.recvBuffer.Len() { 110 | h.recvLock.Unlock() 111 | goto WAIT 112 | } 113 | n, _ = h.recvBuffer.Read(b) 114 | h.recvLock.Unlock() 115 | return n, nil 116 | WAIT: 117 | var timeout <-chan time.Time 118 | var timer *time.Timer 119 | timer = time.NewTimer(time.Duration(10) * time.Second) 120 | timeout = timer.C 121 | select { 122 | case <-h.recvNotifyCh: 123 | if timer != nil { 124 | timer.Stop() 125 | } 126 | goto START 127 | case <-timeout: 128 | goto START 129 | } 130 | } 131 | 132 | func (h *httpDuplexServConn) Write(p []byte) (n int, err error) { 133 | START: 134 | if !h.isRunning() { 135 | return 0, io.EOF 136 | } 137 | h.sendLock.Lock() 138 | if nil == h.writer { 139 | h.sendLock.Unlock() 140 | goto WAIT 141 | } 142 | h.touch() 143 | n, err = h.writer.Write(p) 144 | if nil == err { 145 | h.writer.(http.Flusher).Flush() 146 | } else { 147 | h.writer = nil 148 | } 149 | h.sendLock.Unlock() 150 | return n, err 151 | WAIT: 152 | var timeout <-chan time.Time 153 | var timer *time.Timer 154 | timer = time.NewTimer(time.Duration(10) * time.Second) 155 | timeout = timer.C 156 | select { 157 | case <-h.sendNotifyCh: 158 | if timer != nil { 159 | timer.Stop() 160 | } 161 | goto START 162 | case <-timeout: 163 | goto START 164 | } 165 | } 166 | 167 | func (h *httpDuplexServConn) closeWrite() error { 168 | if h.isRunning() { 169 | h.sendLock.Lock() 170 | h.writer = nil 171 | if nil != h.writerStopCh { 172 | helper.AsyncNotify(h.writerStopCh) 173 | } 174 | h.sendLock.Unlock() 175 | helper.AsyncNotify(h.sendNotifyCh) 176 | } 177 | return nil 178 | } 179 | 180 | func (h *httpDuplexServConn) closeRead() error { 181 | req := h.req 182 | if nil != req && nil != req.Body { 183 | req.Body.Close() 184 | } 185 | return nil 186 | } 187 | 188 | func (h *httpDuplexServConn) shutdown(err error) { 189 | h.shutdownErr = err 190 | h.Close() 191 | h.closeRead() 192 | } 193 | 194 | func (h *httpDuplexServConn) isRunning() bool { 195 | return atomic.LoadInt32(&h.running) > 0 196 | } 197 | 198 | func (h *httpDuplexServConn) Close() error { 199 | if h.isRunning() { 200 | h.closeWrite() 201 | helper.AsyncNotify(h.recvNotifyCh) 202 | helper.AsyncNotify(h.closeNotifyCh) 203 | atomic.StoreInt32(&h.running, 0) 204 | } 205 | removetHttpDuplexServConnByID(h.id) 206 | return nil 207 | } 208 | 209 | var httpDuplexServConnTable = make(map[string]*httpDuplexServConn) 210 | var httpDuplexServConnMutex sync.Mutex 211 | 212 | func getHttpDuplexServConnByID(id string, createIfNotExist bool) (*httpDuplexServConn, bool) { 213 | httpDuplexServConnMutex.Lock() 214 | defer httpDuplexServConnMutex.Unlock() 215 | 216 | c, exist := httpDuplexServConnTable[id] 217 | if !exist { 218 | if createIfNotExist { 219 | c = &httpDuplexServConn{} 220 | c.init(id) 221 | httpDuplexServConnTable[id] = c 222 | return c, true 223 | } 224 | } 225 | return c, false 226 | } 227 | 228 | func removetHttpDuplexServConnByID(id string) { 229 | httpDuplexServConnMutex.Lock() 230 | defer httpDuplexServConnMutex.Unlock() 231 | delete(httpDuplexServConnTable, id) 232 | } 233 | 234 | func HttpTest(w http.ResponseWriter, r *http.Request) { 235 | //log.Printf("###Test req:%v", r) 236 | w.Write([]byte("OK")) 237 | } 238 | 239 | func HTTPInvoke(w http.ResponseWriter, r *http.Request) { 240 | id := r.Header.Get(mux.HTTPMuxSessionIDHeader) 241 | if len(id) == 0 { 242 | logger.Debug("Invalid header with no session id:%v", r) 243 | return 244 | } 245 | c, create := getHttpDuplexServConnByID(id, true) 246 | if create { 247 | if len(r.Header.Get(mux.HTTPMuxSessionACKIDHeader)) > 0 { 248 | w.WriteHeader(401) 249 | logger.Error("###ERR1 : %s", r.Header.Get(mux.HTTPMuxSessionACKIDHeader)) 250 | return 251 | } 252 | session, err := pmux.Server(c, channel.InitialPMuxConfig(&channel.DefaultServerCipher)) 253 | if nil != err { 254 | return 255 | } 256 | muxSession := &mux.ProxyMuxSession{Session: session} 257 | go func() { 258 | err := channel.ServProxyMuxSession(muxSession, nil, nil) 259 | if nil != err { 260 | c.shutdown(err) 261 | } 262 | }() 263 | } 264 | ackID := r.Header.Get(mux.HTTPMuxSessionACKIDHeader) 265 | if len(ackID) > 0 && ackID != c.ackID { 266 | w.WriteHeader(401) 267 | logger.Error("###ERR2 : %s %s", r.Header.Get(mux.HTTPMuxSessionACKIDHeader), c.ackID) 268 | return 269 | } 270 | w.Header().Set(mux.HTTPMuxSessionACKIDHeader, c.ackID) 271 | if strings.HasSuffix(r.URL.Path, "pull") { 272 | logger.Debug("HTTP server recv pull for id:%s", id) 273 | period, _ := strconv.Atoi(r.Header.Get("X-PullPeriod")) 274 | if period <= 0 { 275 | period = 30 276 | } 277 | timer := time.NewTimer(time.Duration(period) * time.Second) 278 | timeout := timer.C 279 | stopByOther := make(chan struct{}) 280 | c.setWriter(w, stopByOther) 281 | if !c.isRunning() { 282 | w.WriteHeader(401) 283 | timer.Stop() 284 | return 285 | } 286 | select { 287 | case <-timeout: 288 | c.closeWrite() 289 | logger.Notice("HTTP server close pull for id:%s", id) 290 | return 291 | case <-c.closeNotifyCh: 292 | timer.Stop() 293 | w.WriteHeader(401) 294 | logger.Debug("HTTP server close pull for id:%s close ", id) 295 | case <-stopByOther: 296 | logger.Debug("HTTP server recv pull id:%s stop by other pull", id) 297 | timer.Stop() 298 | return 299 | } 300 | } else { 301 | //counter := r.URL.Query().Get(pmux.HTTPPullCounterKey) 302 | c.setReader(r) 303 | if nil != c.shutdownErr { 304 | w.WriteHeader(401) 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /common/channel/http2/local.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/yinqiwen/gsnova/common/channel" 7 | "github.com/yinqiwen/gsnova/common/mux" 8 | ) 9 | 10 | type HTTP2Proxy struct { 11 | } 12 | 13 | func (p *HTTP2Proxy) Features() channel.FeatureSet { 14 | return channel.FeatureSet{ 15 | AutoExpire: true, 16 | Pingable: true, 17 | } 18 | } 19 | 20 | func (tc *HTTP2Proxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 21 | rurl, err := url.Parse(server) 22 | if nil != err { 23 | return nil, err 24 | } 25 | conn, err := channel.DialServerByConf(server, conf) 26 | if err != nil { 27 | return nil, err 28 | } 29 | //log.Printf("Connect %s success.", server) 30 | return mux.NewHTTP2ClientMuxSession(conn, rurl.Host) 31 | } 32 | 33 | func init() { 34 | channel.RegisterLocalChannelType("http2", &HTTP2Proxy{}) 35 | } 36 | -------------------------------------------------------------------------------- /common/channel/http2/remote.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "io" 7 | "net" 8 | "net/http" 9 | "sync" 10 | 11 | "golang.org/x/net/http2" 12 | 13 | "github.com/yinqiwen/gsnova/common/channel" 14 | "github.com/yinqiwen/gsnova/common/helper" 15 | "github.com/yinqiwen/gsnova/common/logger" 16 | "github.com/yinqiwen/gsnova/common/mux" 17 | //"github.com/yinqiwen/gsnova/remote" 18 | ) 19 | 20 | type http2Stream struct { 21 | req *http.Request 22 | rw http.ResponseWriter 23 | rwLock sync.Mutex 24 | closeCh chan struct{} 25 | } 26 | 27 | func (s *http2Stream) Read(b []byte) (n int, err error) { 28 | n, err = s.req.Body.Read(b) 29 | return 30 | } 31 | 32 | func (s *http2Stream) Write(b []byte) (n int, err error) { 33 | s.rwLock.Lock() 34 | defer s.rwLock.Unlock() 35 | if nil == s.rw { 36 | return 0, io.EOF 37 | } 38 | n, err = s.rw.Write(b) 39 | if nil != err { 40 | return n, err 41 | } 42 | if f, ok := s.rw.(http.Flusher); ok { 43 | f.Flush() 44 | } 45 | return n, nil 46 | } 47 | 48 | func (s *http2Stream) Close() (err error) { 49 | helper.AsyncNotify(s.closeCh) 50 | s.req.Body.Close() 51 | s.rwLock.Lock() 52 | s.rw = nil 53 | s.rwLock.Unlock() 54 | return nil 55 | } 56 | 57 | type http2Handler struct { 58 | session *mux.HTTP2MuxSession 59 | } 60 | 61 | func (ss *http2Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 62 | //log.Printf("New HTTP2 stream %v", req) 63 | s := &http2Stream{} 64 | s.req = req 65 | s.rw = rw 66 | s.closeCh = make(chan struct{}, 1) 67 | rw.WriteHeader(200) 68 | err := ss.session.OfferStream(&helper.TimeoutReadWriteCloser{ReadWriteCloser: s}) 69 | if nil != err { 70 | logger.Error("%v", err) 71 | return 72 | } 73 | <-s.closeCh 74 | } 75 | 76 | func servHTTP2(lp net.Listener, addr string, config *tls.Config) { 77 | for { 78 | conn, err := lp.Accept() 79 | if nil != err { 80 | continue 81 | } 82 | muxSession := mux.NewHTTP2ServerMuxSession(conn) 83 | go channel.ServProxyMuxSession(muxSession, nil, nil) 84 | server := &http.Server{ 85 | Addr: addr, 86 | TLSConfig: config, 87 | } 88 | http2Server := &http2.Server{ 89 | MaxConcurrentStreams: 4096, 90 | PermitProhibitedCipherSuites: true, 91 | } 92 | opt := &http2.ServeConnOpts{} 93 | opt.BaseConfig = server 94 | opt.Handler = &http2Handler{session: muxSession} 95 | 96 | go func() { 97 | tlsconn := tls.Server(conn, config) 98 | err = tlsconn.Handshake() 99 | if nil != err { 100 | logger.Error("TLS handshake failed:%v", err) 101 | muxSession.Close() 102 | return 103 | } 104 | stateData, _ := json.MarshalIndent(tlsconn.ConnectionState(), "", " ") 105 | logger.Notice("Recv conn state : %s", string(stateData)) 106 | http2Server.ServeConn(tlsconn, opt) 107 | muxSession.Close() 108 | }() 109 | } 110 | } 111 | 112 | func StartHTTTP2ProxyServer(addr string, config *tls.Config) error { 113 | lp, err := net.Listen("tcp", addr) 114 | if nil != err { 115 | logger.Error("[ERROR]Failed to listen TCP address:%s with reason:%v", addr, err) 116 | return err 117 | } 118 | logger.Info("Listen on HTTP2 address:%s", addr) 119 | servHTTP2(lp, addr, config) 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /common/channel/kcp/local.go: -------------------------------------------------------------------------------- 1 | package kcp 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/url" 7 | 8 | kcp "github.com/xtaci/kcp-go" 9 | "github.com/yinqiwen/gsnova/common/channel" 10 | "github.com/yinqiwen/gsnova/common/dns" 11 | "github.com/yinqiwen/gsnova/common/logger" 12 | "github.com/yinqiwen/gsnova/common/mux" 13 | "github.com/yinqiwen/gsnova/common/netx" 14 | "github.com/yinqiwen/pmux" 15 | ) 16 | 17 | // connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls 18 | // to Write syscalls that are 4 times faster on some OS'es. This should only be 19 | // used for connections that were produced by a net.Dial* call. 20 | type connectedUDPConn struct{ net.PacketConn } 21 | 22 | // WriteTo redirects all writes to the Write syscall, which is 4 times faster. 23 | func (c *connectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { 24 | writer, ok := c.PacketConn.(io.Writer) 25 | if ok { 26 | return writer.Write(b) 27 | } 28 | return c.PacketConn.WriteTo(b, addr) 29 | } 30 | 31 | type KCPProxy struct { 32 | //proxy.BaseProxy 33 | } 34 | 35 | func (p *KCPProxy) Features() channel.FeatureSet { 36 | return channel.FeatureSet{ 37 | AutoExpire: true, 38 | Pingable: true, 39 | } 40 | } 41 | 42 | func (tc *KCPProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 43 | rurl, err := url.Parse(server) 44 | if nil != err { 45 | return nil, err 46 | } 47 | hostport := rurl.Host 48 | tcpHost, tcpPort, _ := net.SplitHostPort(hostport) 49 | if net.ParseIP(tcpHost) == nil { 50 | iphost, err := dns.DnsGetDoaminIP(tcpHost) 51 | if nil != err { 52 | return nil, err 53 | } 54 | hostport = net.JoinHostPort(iphost, tcpPort) 55 | } 56 | block, _ := kcp.NewNoneBlockCrypt(nil) 57 | 58 | udpaddr, err := net.ResolveUDPAddr("udp", hostport) 59 | if err != nil { 60 | return nil, err 61 | } 62 | udpconn, err := netx.DialUDP("udp", nil, udpaddr) 63 | if err != nil { 64 | return nil, err 65 | } 66 | kcpconn, err := kcp.NewConn(hostport, block, conf.KCP.DataShard, conf.KCP.ParityShard, &connectedUDPConn{udpconn}) 67 | //kcpconn, err := kcp.NewConn(hostport, block, conf.KCP.DataShard, conf.KCP.ParityShard, udpconn) 68 | //kcpconn, err := kcp.DialWithOptions(hostport, block, conf.KCP.DataShard, conf.KCP.ParityShard) 69 | if err != nil { 70 | return nil, err 71 | } 72 | kcpconn.SetStreamMode(true) 73 | kcpconn.SetWriteDelay(true) 74 | kcpconn.SetNoDelay(conf.KCP.NoDelay, conf.KCP.Interval, conf.KCP.Resend, conf.KCP.NoCongestion) 75 | kcpconn.SetWindowSize(conf.KCP.SndWnd, conf.KCP.RcvWnd) 76 | kcpconn.SetMtu(conf.KCP.MTU) 77 | kcpconn.SetACKNoDelay(conf.KCP.AckNodelay) 78 | 79 | if err := kcpconn.SetDSCP(conf.KCP.DSCP); err != nil { 80 | logger.Notice("SetDSCP:%v with value:%v", err, conf.KCP.DSCP) 81 | } 82 | if err := kcpconn.SetReadBuffer(conf.KCP.SockBuf); err != nil { 83 | logger.Notice("SetReadBuffer:%v", err) 84 | } 85 | if err := kcpconn.SetWriteBuffer(conf.KCP.SockBuf); err != nil { 86 | logger.Notice("SetWriteBuffer:%v", err) 87 | } 88 | session, err := pmux.Client(kcpconn, channel.InitialPMuxConfig(&conf.Cipher)) 89 | if nil != err { 90 | return nil, err 91 | } 92 | logger.Debug("Connect %s success.", server) 93 | return &mux.ProxyMuxSession{Session: session}, nil 94 | } 95 | 96 | func init() { 97 | channel.RegisterLocalChannelType("kcp", &KCPProxy{}) 98 | } 99 | -------------------------------------------------------------------------------- /common/channel/kcp/remote.go: -------------------------------------------------------------------------------- 1 | package kcp 2 | 3 | import ( 4 | kcp "github.com/xtaci/kcp-go" 5 | "github.com/yinqiwen/gsnova/common/channel" 6 | "github.com/yinqiwen/gsnova/common/logger" 7 | "github.com/yinqiwen/gsnova/common/mux" 8 | "github.com/yinqiwen/pmux" 9 | ) 10 | 11 | func StartKCPProxyServer(addr string, config *channel.KCPConfig) error { 12 | block, _ := kcp.NewNoneBlockCrypt(nil) 13 | lis, err := kcp.ListenWithOptions(addr, block, config.DataShard, config.ParityShard) 14 | if nil != err { 15 | logger.Error("[ERROR]Failed to listen KCP address:%s with reason:%v", addr, err) 16 | return err 17 | } 18 | 19 | if err := lis.SetDSCP(config.DSCP); err != nil { 20 | logger.Debug("SetDSCP:%v", err) 21 | } 22 | if err := lis.SetReadBuffer(config.SockBuf); err != nil { 23 | logger.Debug("SetReadBuffer:%v", err) 24 | } 25 | if err := lis.SetWriteBuffer(config.SockBuf); err != nil { 26 | logger.Debug("SetWriteBuffer:%v", err) 27 | } 28 | logger.Info("Listen on KCP address:%s", addr) 29 | servKCP(lis, config) 30 | return nil 31 | } 32 | 33 | func servKCP(lp *kcp.Listener, config *channel.KCPConfig) { 34 | for { 35 | conn, err := lp.AcceptKCP() 36 | if nil != err { 37 | continue 38 | } 39 | 40 | //config := &remote.ServerConf.KCP 41 | conn.SetStreamMode(true) 42 | conn.SetWriteDelay(true) 43 | conn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion) 44 | conn.SetMtu(config.MTU) 45 | conn.SetWindowSize(config.SndWnd, config.RcvWnd) 46 | conn.SetACKNoDelay(config.AckNodelay) 47 | session, err := pmux.Server(conn, channel.InitialPMuxConfig(&channel.DefaultServerCipher)) 48 | if nil != err { 49 | logger.Error("[ERROR]Failed to create mux session for tcp server with reason:%v", err) 50 | continue 51 | } 52 | muxSession := &mux.ProxyMuxSession{Session: session} 53 | go channel.ServProxyMuxSession(muxSession, nil, nil) 54 | } 55 | //ws.WriteMessage(websocket.CloseMessage, []byte{}) 56 | } 57 | -------------------------------------------------------------------------------- /common/channel/local.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "sort" 7 | "time" 8 | 9 | "github.com/yinqiwen/gsnova/common/logger" 10 | "github.com/yinqiwen/gsnova/common/mux" 11 | "github.com/yinqiwen/pmux" 12 | ) 13 | 14 | type LocalChannel interface { 15 | //PrintStat(w io.Writer) 16 | CreateMuxSession(server string, conf *ProxyChannelConfig) (mux.MuxSession, error) 17 | Features() FeatureSet 18 | } 19 | 20 | var LocalChannelTypeTable map[string]reflect.Type = make(map[string]reflect.Type) 21 | 22 | const DirectChannelName = "direct" 23 | 24 | var DirectSchemes = []string{ 25 | DirectChannelName, 26 | "socks", 27 | "socks4", 28 | "socks5", 29 | "http_proxy", 30 | } 31 | 32 | func IsDirectScheme(scheme string) bool { 33 | for _, s := range DirectSchemes { 34 | if s == scheme { 35 | return true 36 | } 37 | } 38 | return false 39 | } 40 | 41 | func RegisterLocalChannelType(str string, p LocalChannel) error { 42 | rt := reflect.TypeOf(p) 43 | if rt.Kind() == reflect.Ptr { 44 | rt = rt.Elem() 45 | } 46 | LocalChannelTypeTable[str] = rt 47 | return nil 48 | } 49 | 50 | func AllowedSchema() []string { 51 | schemes := []string{} 52 | for scheme := range LocalChannelTypeTable { 53 | if !IsDirectScheme(scheme) { 54 | schemes = append(schemes, scheme) 55 | } 56 | } 57 | sort.Strings(schemes) 58 | return schemes 59 | } 60 | 61 | func clientAuthMuxSession(session mux.MuxSession, cipherMethod string, conf *ProxyChannelConfig, tunnelPriAddr, tunnelPubAddr string, isFirst bool, isP2P bool) (error, *mux.AuthRequest, *mux.AuthResponse) { 62 | authStream, err := session.OpenStream() 63 | if nil != err { 64 | return err, nil, nil 65 | } 66 | //counter := uint64(helper.RandBetween(0, math.MaxInt32)) 67 | counter := uint64(1) 68 | authReq := &mux.AuthRequest{ 69 | User: conf.Cipher.User, 70 | CipherCounter: counter, 71 | CipherMethod: cipherMethod, 72 | CompressMethod: conf.Compressor, 73 | P2PToken: conf.P2PToken, 74 | P2PPriAddr: tunnelPriAddr, 75 | P2PPubAddr: tunnelPubAddr, 76 | } 77 | if len(conf.P2PToken) > 0 { 78 | authReq.P2PConnID = p2pConnID 79 | } 80 | if isP2P { 81 | authReq.P2PToken = "" 82 | authReq.P2PConnID = "" 83 | authReq.P2PPriAddr = "" 84 | authReq.P2PPubAddr = "" 85 | } 86 | authStream.SetReadDeadline(time.Now().Add(3 * time.Second)) 87 | authRes := authStream.Auth(authReq) 88 | err = authRes.Error() 89 | if nil != err { 90 | return err, nil, nil 91 | } 92 | //wait auth stream close 93 | var zero time.Time 94 | authStream.SetReadDeadline(zero) 95 | authStream.Read(make([]byte, 1)) 96 | //authStream.Close() 97 | if isFirst { 98 | if psession, ok := session.(*mux.ProxyMuxSession); ok { 99 | err = psession.Session.ResetCryptoContext(cipherMethod, counter) 100 | if nil != err { 101 | logger.Error("[ERROR]Failed to reset cipher context with reason:%v, while cipher method:%s", err, cipherMethod) 102 | return err, nil, nil 103 | } 104 | } 105 | } 106 | return nil, authReq, authRes 107 | } 108 | 109 | func clientAuthConn(c net.Conn, cipherMethod string, conf *ProxyChannelConfig, p2pTunnel bool) (error, *mux.AuthRequest, *mux.AuthResponse, mux.MuxSession) { 110 | session, err := pmux.Client(c, InitialPMuxConfig(&conf.Cipher)) 111 | if nil != err { 112 | logger.Error("Failed to init mux session:%v", err) 113 | c.Close() 114 | return err, nil, nil, nil 115 | } 116 | ps := &mux.ProxyMuxSession{Session: session} 117 | var tunnelPriAddr string 118 | if !p2pTunnel { 119 | tunnelPriAddr = c.LocalAddr().String() 120 | } 121 | err, req, res := clientAuthMuxSession(ps, cipherMethod, conf, tunnelPriAddr, "", true, p2pTunnel) 122 | if nil != err { 123 | logger.Error("Failed to auth mux session:%v", err) 124 | c.Close() 125 | return err, nil, nil, nil 126 | } 127 | return nil, req, res, ps 128 | } 129 | -------------------------------------------------------------------------------- /common/channel/quic/local.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/url" 7 | 8 | quic "github.com/lucas-clemente/quic-go" 9 | "github.com/yinqiwen/gsnova/common/channel" 10 | "github.com/yinqiwen/gsnova/common/dns" 11 | "github.com/yinqiwen/gsnova/common/logger" 12 | "github.com/yinqiwen/gsnova/common/mux" 13 | "github.com/yinqiwen/gsnova/common/netx" 14 | ) 15 | 16 | type QUICProxy struct { 17 | //proxy.BaseProxy 18 | } 19 | 20 | func (p *QUICProxy) Features() channel.FeatureSet { 21 | return channel.FeatureSet{ 22 | AutoExpire: true, 23 | Pingable: false, 24 | } 25 | } 26 | 27 | func (tc *QUICProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 28 | rurl, err := url.Parse(server) 29 | if nil != err { 30 | return nil, err 31 | } 32 | hostport := rurl.Host 33 | tcpHost, tcpPort, _ := net.SplitHostPort(hostport) 34 | if net.ParseIP(tcpHost) == nil { 35 | iphost, err := dns.DnsGetDoaminIP(tcpHost) 36 | if nil != err { 37 | return nil, err 38 | } 39 | hostport = net.JoinHostPort(iphost, tcpPort) 40 | } 41 | var quicSession quic.Session 42 | 43 | udpAddr, err := net.ResolveUDPAddr("udp", hostport) 44 | if err != nil { 45 | return nil, err 46 | } 47 | udpConn, err := netx.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) 48 | if err != nil { 49 | return nil, err 50 | } 51 | quicConfig := &quic.Config{ 52 | KeepAlive: true, 53 | } 54 | quicSession, err = quic.Dial(udpConn, udpAddr, hostport, &tls.Config{InsecureSkipVerify: true}, quicConfig) 55 | 56 | if err != nil { 57 | return nil, err 58 | } 59 | logger.Debug("Connect %s success.", server) 60 | return &mux.QUICMuxSession{Session: quicSession}, nil 61 | } 62 | 63 | func init() { 64 | channel.RegisterLocalChannelType("quic", &QUICProxy{}) 65 | } 66 | -------------------------------------------------------------------------------- /common/channel/quic/remote.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "crypto/tls" 5 | 6 | quic "github.com/lucas-clemente/quic-go" 7 | "github.com/yinqiwen/gsnova/common/channel" 8 | "github.com/yinqiwen/gsnova/common/logger" 9 | "github.com/yinqiwen/gsnova/common/mux" 10 | ) 11 | 12 | func servQUIC(lp quic.Listener) { 13 | for { 14 | sess, err := lp.Accept() 15 | if nil != err { 16 | continue 17 | } 18 | muxSession := &mux.QUICMuxSession{Session: sess} 19 | go channel.ServProxyMuxSession(muxSession, nil, nil) 20 | } 21 | //ws.WriteMessage(websocket.CloseMessage, []byte{}) 22 | } 23 | 24 | func StartQuicProxyServer(addr string, config *tls.Config) error { 25 | lp, err := quic.ListenAddr(addr, config, nil) 26 | if nil != err { 27 | logger.Error("[ERROR]Failed to listen QUIC address:%s with reason:%v", addr, err) 28 | return err 29 | } 30 | logger.Info("Listen on QUIC address:%s", addr) 31 | servQUIC(lp) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /common/channel/remote_p2p.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync" 7 | "time" 8 | 9 | "github.com/yinqiwen/gsnova/common/logger" 10 | "github.com/yinqiwen/gsnova/common/mux" 11 | ) 12 | 13 | type p2pNode struct { 14 | sessions map[mux.MuxSession]bool 15 | peerPubAddr string 16 | peerPriAddr string 17 | } 18 | 19 | var p2pSessionTable = make(map[string]map[string]*p2pNode) 20 | var p2pSessionMutex sync.Mutex 21 | 22 | func addP2PSession(auth *mux.AuthRequest, session mux.MuxSession, raddr net.Addr) bool { 23 | p2pSessionMutex.Lock() 24 | defer p2pSessionMutex.Unlock() 25 | m1, exist := p2pSessionTable[auth.P2PToken] 26 | if !exist { 27 | m1 = make(map[string]*p2pNode) 28 | p2pSessionTable[auth.P2PToken] = m1 29 | } 30 | 31 | sessions, exist := m1[auth.P2PConnID] 32 | if !exist { 33 | if len(m1) >= 2 { 34 | logger.Error("Already TWO users joined for P2P Room:%s", auth.P2PToken) 35 | return false 36 | } 37 | sessions = &p2pNode{ 38 | sessions: make(map[mux.MuxSession]bool), 39 | } 40 | m1[auth.P2PConnID] = sessions 41 | logger.Info("P2P Room:%s have %d members, '%s' just joined.", auth.P2PToken, len(m1), auth.P2PConnID) 42 | } 43 | if len(auth.P2PPriAddr) > 0 { 44 | logger.Info("Recv P2P Room:%s tunnel conn at from %v with req:%v", auth.P2PToken, raddr, auth) 45 | sessions.peerPriAddr = auth.P2PPriAddr 46 | if len(auth.P2PPubAddr) == 0 { 47 | sessions.peerPubAddr = raddr.String() 48 | } else { 49 | sessions.peerPubAddr = auth.P2PPubAddr 50 | } 51 | } else { 52 | sessions.sessions[session] = true 53 | } 54 | return true 55 | } 56 | 57 | func removeP2PSession(auth *mux.AuthRequest, session mux.MuxSession) bool { 58 | if nil == auth { 59 | return false 60 | } 61 | 62 | p2pSessionMutex.Lock() 63 | defer p2pSessionMutex.Unlock() 64 | m1, exist := p2pSessionTable[auth.P2PToken] 65 | if !exist { 66 | return false 67 | } 68 | sessions, exist := m1[auth.P2PConnID] 69 | if !exist { 70 | return false 71 | } 72 | delete(sessions.sessions, session) 73 | if len(sessions.sessions) == 0 { 74 | delete(m1, auth.P2PConnID) 75 | logger.Info("P2P Room:%s have %d members, '%s' just exit.", auth.P2PToken, len(m1), auth.P2PConnID) 76 | if len(m1) == 0 { 77 | delete(p2pSessionTable, auth.P2PToken) 78 | } 79 | } 80 | if len(auth.P2PPriAddr) > 0 { 81 | sessions.peerPriAddr = "" 82 | sessions.peerPubAddr = "" 83 | } 84 | return true 85 | } 86 | 87 | func getPeerAddr(auth *mux.AuthRequest) (string, string) { 88 | p2pSessionMutex.Lock() 89 | defer p2pSessionMutex.Unlock() 90 | m1, exist := p2pSessionTable[auth.P2PToken] 91 | if !exist { 92 | logger.Error("No P2P Room found for %s", auth.P2PToken) 93 | return "", "" 94 | } 95 | for connID, sessions := range m1 { 96 | if connID != auth.P2PConnID { 97 | return sessions.peerPriAddr, sessions.peerPubAddr 98 | } 99 | } 100 | return "", "" 101 | } 102 | 103 | func openPeerStream(roomID string, cid string) (mux.MuxStream, bool) { 104 | p2pSessionMutex.Lock() 105 | defer p2pSessionMutex.Unlock() 106 | m1, exist := p2pSessionTable[roomID] 107 | if !exist { 108 | logger.Error("No P2P Room found for %s", roomID) 109 | return nil, false 110 | } 111 | for connID, sessions := range m1 { 112 | if connID != cid { 113 | for session := range sessions.sessions { 114 | stream, err := session.OpenStream() 115 | if nil == err { 116 | logger.Debug("Create peer stream %s:(%s <-> %s)", roomID, cid, connID) 117 | return stream, true 118 | } 119 | logger.Error("Failed to create peer P2P stream with %s:%s", roomID, connID) 120 | return nil, false 121 | } 122 | } 123 | } 124 | logger.Error("No P2P Peer found for %s:%s", roomID, cid) 125 | return nil, false 126 | } 127 | 128 | func handleP2PProxyStream(stream mux.MuxStream, ctx *sessionContext) { 129 | peerStream, success := openPeerStream(ctx.auth.P2PToken, ctx.auth.P2PConnID) 130 | if !success { 131 | stream.Close() 132 | return 133 | } 134 | start := time.Now() 135 | closeSig := make(chan bool, 1) 136 | go func() { 137 | io.Copy(stream, peerStream) 138 | logger.Info("P2P:Cost %v to copy local to remote", time.Now().Sub(start)) 139 | stream.Close() 140 | closeSig <- true 141 | }() 142 | io.Copy(peerStream, stream) 143 | logger.Info("P2P:Cost %v to copy remote to local", time.Now().Sub(start)) 144 | <-closeSig 145 | stream.Close() 146 | peerStream.Close() 147 | 148 | } 149 | -------------------------------------------------------------------------------- /common/channel/ssh/local.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net" 8 | "net/url" 9 | "sync" 10 | "time" 11 | 12 | "golang.org/x/crypto/ssh" 13 | 14 | "github.com/yinqiwen/gsnova/common/channel" 15 | "github.com/yinqiwen/gsnova/common/logger" 16 | "github.com/yinqiwen/gsnova/common/mux" 17 | ) 18 | 19 | type sshStream struct { 20 | net.Conn 21 | conf *channel.ProxyChannelConfig 22 | addr string 23 | session *sshMuxSession 24 | latestIOTime time.Time 25 | } 26 | 27 | func (tc *sshStream) SetReadDeadline(t time.Time) error { 28 | if nil == tc.Conn { 29 | return io.EOF 30 | } 31 | return tc.Conn.SetReadDeadline(t) 32 | } 33 | func (tc *sshStream) SetWriteDeadline(t time.Time) error { 34 | if nil == tc.Conn { 35 | return io.EOF 36 | } 37 | return tc.Conn.SetWriteDeadline(t) 38 | } 39 | 40 | func (s *sshStream) LatestIOTime() time.Time { 41 | return s.latestIOTime 42 | } 43 | 44 | func (tc *sshStream) Auth(req *mux.AuthRequest) *mux.AuthResponse { 45 | return &mux.AuthResponse{Code: mux.AuthOK} 46 | } 47 | 48 | func (tc *sshStream) Connect(network string, addr string, opt mux.StreamOptions) error { 49 | switch network { 50 | case "tcp", "tcp6", "tcp4": 51 | default: 52 | return fmt.Errorf("No support for local proxy connections by network type:%s", network) 53 | } 54 | sshClient := tc.session.getSSHClient() 55 | if nil == sshClient { 56 | tc.session.Close() 57 | return fmt.Errorf("SSH connection closed") 58 | } 59 | conn, err := sshClient.Dial(network, addr) 60 | if nil != err { 61 | tc.session.Close() 62 | return err 63 | } 64 | tc.Conn = conn 65 | tc.addr = addr 66 | return nil 67 | } 68 | 69 | func (tc *sshStream) StreamID() uint32 { 70 | return 0 71 | } 72 | 73 | func (tc *sshStream) Read(p []byte) (int, error) { 74 | if nil == tc.Conn { 75 | return 0, io.EOF 76 | } 77 | tc.latestIOTime = time.Now() 78 | return tc.Conn.Read(p) 79 | } 80 | func (tc *sshStream) Write(p []byte) (int, error) { 81 | if nil == tc.Conn { 82 | return 0, io.EOF 83 | } 84 | tc.latestIOTime = time.Now() 85 | return tc.Conn.Write(p) 86 | } 87 | 88 | func (tc *sshStream) Close() error { 89 | conn := tc.Conn 90 | if nil != conn { 91 | conn.Close() 92 | tc.Conn = nil 93 | } 94 | tc.session.closeStream(tc) 95 | return nil 96 | } 97 | 98 | type sshMuxSession struct { 99 | conf *channel.ProxyChannelConfig 100 | streams map[*sshStream]bool 101 | streamsMutex sync.Mutex 102 | sshClient *ssh.Client 103 | } 104 | 105 | func (s *sshMuxSession) RemoteAddr() net.Addr { 106 | return nil 107 | } 108 | func (s *sshMuxSession) LocalAddr() net.Addr { 109 | return nil 110 | } 111 | 112 | func (tc *sshMuxSession) getSSHClient() *ssh.Client { 113 | return tc.sshClient 114 | } 115 | 116 | func (tc *sshMuxSession) closeStream(s *sshStream) { 117 | tc.streamsMutex.Lock() 118 | defer tc.streamsMutex.Unlock() 119 | delete(tc.streams, s) 120 | } 121 | 122 | func (tc *sshMuxSession) CloseStream(stream mux.MuxStream) error { 123 | //stream.Close() 124 | return nil 125 | } 126 | 127 | func (tc *sshMuxSession) OpenStream() (mux.MuxStream, error) { 128 | if nil == tc.sshClient { 129 | return nil, fmt.Errorf("SSH client closed") 130 | } 131 | tc.streamsMutex.Lock() 132 | defer tc.streamsMutex.Unlock() 133 | stream := &sshStream{ 134 | conf: tc.conf, 135 | session: tc, 136 | } 137 | return stream, nil 138 | } 139 | func (tc *sshMuxSession) AcceptStream() (mux.MuxStream, error) { 140 | return nil, nil 141 | } 142 | 143 | func (tc *sshMuxSession) NumStreams() int { 144 | tc.streamsMutex.Lock() 145 | defer tc.streamsMutex.Unlock() 146 | return len(tc.streams) 147 | } 148 | 149 | func (tc *sshMuxSession) Close() error { 150 | tc.streamsMutex.Lock() 151 | defer tc.streamsMutex.Unlock() 152 | for stream := range tc.streams { 153 | stream.Close() 154 | } 155 | tc.streams = make(map[*sshStream]bool) 156 | if nil != tc.sshClient { 157 | tc.sshClient.Close() 158 | tc.sshClient = nil 159 | } 160 | return nil 161 | } 162 | 163 | func (tc *sshMuxSession) Ping() (time.Duration, error) { 164 | if nil != tc.sshClient { 165 | start := time.Now() 166 | _, _, err := tc.sshClient.SendRequest("ping", true, nil) 167 | return time.Now().Sub(start), err 168 | } 169 | return 0, nil 170 | } 171 | 172 | type SSHProxy struct { 173 | } 174 | 175 | func (p *SSHProxy) Features() channel.FeatureSet { 176 | return channel.FeatureSet{ 177 | AutoExpire: true, 178 | Pingable: true, 179 | } 180 | } 181 | 182 | func (p *SSHProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 183 | u, err := url.Parse(server) 184 | if nil != err { 185 | return nil, err 186 | } 187 | c, err := channel.DialServerByConf(server, conf) 188 | if err != nil { 189 | return nil, err 190 | } 191 | var sshConf *ssh.ClientConfig 192 | passwd, ok := u.User.Password() 193 | if ok { 194 | sshConf = &ssh.ClientConfig{ 195 | User: u.User.Username(), 196 | Auth: []ssh.AuthMethod{ 197 | ssh.Password(passwd), 198 | }, 199 | HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 200 | return nil 201 | }, 202 | } 203 | } else { 204 | if identify := u.Query().Get("key"); len(identify) > 0 { 205 | if content, err := ioutil.ReadFile(identify); nil != err { 206 | logger.Error("Invalid SSH identify path:%s for reason:%v", identify, err) 207 | return nil, err 208 | } else { 209 | signer, err := ssh.ParsePrivateKey(content) 210 | if nil != err { 211 | logger.Error("Invalid pem content for path:%s with reason:%v\n", identify, err) 212 | return nil, err 213 | } 214 | sshConf = &ssh.ClientConfig{ 215 | User: u.User.Username(), 216 | Auth: []ssh.AuthMethod{ 217 | ssh.PublicKeys(signer), 218 | }, 219 | HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 220 | return nil 221 | }, 222 | } 223 | } 224 | } else { 225 | return nil, fmt.Errorf("Can NOT connect ssh server:%s", server) 226 | } 227 | } 228 | 229 | conn, chans, reqs, err := ssh.NewClientConn(c, u.Host, sshConf) 230 | if nil != err { 231 | return nil, err 232 | } 233 | sClient := ssh.NewClient(conn, chans, reqs) 234 | session := &sshMuxSession{ 235 | conf: conf, 236 | streams: make(map[*sshStream]bool), 237 | sshClient: sClient, 238 | } 239 | return session, nil 240 | } 241 | 242 | func init() { 243 | channel.RegisterLocalChannelType("ssh", &SSHProxy{}) 244 | } 245 | -------------------------------------------------------------------------------- /common/channel/tcp/local.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/yinqiwen/gsnova/common/channel" 5 | "github.com/yinqiwen/gsnova/common/logger" 6 | "github.com/yinqiwen/gsnova/common/mux" 7 | "github.com/yinqiwen/pmux" 8 | ) 9 | 10 | type TcpProxy struct { 11 | //proxy.BaseProxy 12 | } 13 | 14 | func (p *TcpProxy) Features() channel.FeatureSet { 15 | return channel.FeatureSet{ 16 | AutoExpire: true, 17 | Pingable: true, 18 | } 19 | } 20 | 21 | func (tc *TcpProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 22 | conn, err := channel.DialServerByConf(server, conf) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | ps, err := pmux.Client(conn, channel.InitialPMuxConfig(&conf.Cipher)) 28 | if nil != err { 29 | return nil, err 30 | } 31 | logger.Info("TCP Session:%v", server) 32 | return &mux.ProxyMuxSession{Session: ps, NetConn: conn}, nil 33 | } 34 | 35 | func init() { 36 | channel.RegisterLocalChannelType("tcp", &TcpProxy{}) 37 | channel.RegisterLocalChannelType("tls", &TcpProxy{}) 38 | } 39 | -------------------------------------------------------------------------------- /common/channel/tcp/remote.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | 7 | "github.com/yinqiwen/gsnova/common/channel" 8 | "github.com/yinqiwen/gsnova/common/logger" 9 | "github.com/yinqiwen/gsnova/common/mux" 10 | "github.com/yinqiwen/pmux" 11 | ) 12 | 13 | func servTCP(lp net.Listener) { 14 | for { 15 | conn, err := lp.Accept() 16 | if nil != err { 17 | continue 18 | } 19 | session, err := pmux.Server(conn, channel.InitialPMuxConfig(&channel.DefaultServerCipher)) 20 | if nil != err { 21 | logger.Error("[ERROR]Failed to create mux session for tcp server with reason:%v", err) 22 | continue 23 | } 24 | //conn.RemoteAddr().String() 25 | muxSession := &mux.ProxyMuxSession{Session: session} 26 | go func() { 27 | channel.ServProxyMuxSession(muxSession, nil, conn.RemoteAddr()) 28 | conn.Close() 29 | }() 30 | } 31 | //ws.WriteMessage(websocket.CloseMessage, []byte{}) 32 | } 33 | 34 | func StartTcpProxyServer(addr string) error { 35 | lp, err := net.Listen("tcp", addr) 36 | if nil != err { 37 | logger.Error("[ERROR]Failed to listen TCP address:%s with reason:%v", addr, err) 38 | return err 39 | } 40 | logger.Info("Listen on TCP address:%s", addr) 41 | servTCP(lp) 42 | return nil 43 | } 44 | 45 | func StartTLSProxyServer(addr string, config *tls.Config) error { 46 | lp, err := net.Listen("tcp", addr) 47 | if nil != err { 48 | logger.Error("[ERROR]Failed to listen TLS address:%s with reason:%v", addr, err) 49 | return err 50 | } 51 | lp = tls.NewListener(lp, config) 52 | logger.Info("Listen on TLS address:%s", addr) 53 | servTCP(lp) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /common/channel/version.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | const Version = "0.34.0" 4 | -------------------------------------------------------------------------------- /common/channel/websocket/local.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/yinqiwen/gsnova/common/channel" 8 | "github.com/yinqiwen/gsnova/common/logger" 9 | "github.com/yinqiwen/gsnova/common/mux" 10 | "github.com/yinqiwen/pmux" 11 | ) 12 | 13 | type WebsocketProxy struct { 14 | } 15 | 16 | func (p *WebsocketProxy) Features() channel.FeatureSet { 17 | return channel.FeatureSet{ 18 | AutoExpire: true, 19 | Pingable: true, 20 | } 21 | } 22 | 23 | func (ws *WebsocketProxy) CreateMuxSession(server string, conf *channel.ProxyChannelConfig) (mux.MuxSession, error) { 24 | u, err := url.Parse(server) 25 | if nil != err { 26 | return nil, err 27 | } 28 | u.Path = "/ws" 29 | wsDialer := &websocket.Dialer{} 30 | wsDialer.NetDial = channel.NewDialByConf(conf, u.Scheme) 31 | wsDialer.TLSClientConfig = channel.NewTLSConfig(conf) 32 | c, _, err := wsDialer.Dial(u.String(), nil) 33 | if err != nil { 34 | logger.Notice("dial websocket error:%v %v", err, u.String()) 35 | return nil, err 36 | } 37 | logger.Info("Connect %s success from %v->%v", server, c.LocalAddr(), c.RemoteAddr()) 38 | ps, err := pmux.Client(&mux.WsConn{Conn: c}, channel.InitialPMuxConfig(&conf.Cipher)) 39 | if nil != err { 40 | return nil, err 41 | } 42 | return &mux.ProxyMuxSession{Session: ps, NetConn: c}, nil 43 | } 44 | 45 | func init() { 46 | channel.RegisterLocalChannelType("ws", &WebsocketProxy{}) 47 | channel.RegisterLocalChannelType("wss", &WebsocketProxy{}) 48 | } 49 | -------------------------------------------------------------------------------- /common/channel/websocket/remote.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/yinqiwen/gsnova/common/channel" 8 | "github.com/yinqiwen/gsnova/common/mux" 9 | "github.com/yinqiwen/pmux" 10 | ) 11 | 12 | var ( 13 | upgrader = websocket.Upgrader{ 14 | ReadBufferSize: 4096, 15 | WriteBufferSize: 4096, 16 | } 17 | ) 18 | 19 | // handleWebsocket connection. Update to 20 | func WebsocketInvoke(w http.ResponseWriter, r *http.Request) { 21 | if r.Method != "GET" { 22 | http.Error(w, "Method not allowed", 405) 23 | return 24 | } 25 | 26 | //logger.Info("req headers: %v", r.Header, r.R) 27 | ws, err := upgrader.Upgrade(w, r, nil) 28 | if err != nil { 29 | //log.WithField("err", err).Println("Upgrading to websockets") 30 | http.Error(w, "Error Upgrading to websockets", 400) 31 | return 32 | } 33 | session, err := pmux.Server(&mux.WsConn{Conn: ws}, channel.InitialPMuxConfig(&channel.DefaultServerCipher)) 34 | if nil != err { 35 | return 36 | } 37 | muxSession := &mux.ProxyMuxSession{Session: session} 38 | channel.ServProxyMuxSession(muxSession, nil, nil) 39 | //ws.WriteMessage(websocket.CloseMessage, []byte{}) 40 | } 41 | -------------------------------------------------------------------------------- /common/dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "net" 7 | 8 | "github.com/miekg/dns" 9 | "github.com/yinqiwen/fdns" 10 | "github.com/yinqiwen/gotoolkit/cip" 11 | "github.com/yinqiwen/gsnova/common/logger" 12 | "github.com/yinqiwen/gsnova/common/netx" 13 | ) 14 | 15 | var LocalDNS *fdns.TrustedDNS 16 | 17 | func pickIP(rr []dns.RR) string { 18 | for _, answer := range rr { 19 | if a, ok := answer.(*dns.A); ok { 20 | return a.A.String() 21 | } 22 | } 23 | for _, answer := range rr { 24 | if aaaa, ok := answer.(*dns.AAAA); ok { 25 | return aaaa.AAAA.String() 26 | } 27 | } 28 | return "" 29 | } 30 | 31 | func selectDNSServer(ss []string) string { 32 | var server string 33 | slen := len(ss) 34 | if slen == 1 { 35 | server = ss[0] 36 | } else { 37 | server = ss[rand.Intn(slen)] 38 | } 39 | return server 40 | } 41 | 42 | func getIPByDefaultResolver(domain string) (string, error) { 43 | addrs, err := net.DefaultResolver.LookupHost(context.Background(), domain) 44 | if nil == err && len(addrs) > 0 { 45 | return addrs[0], nil 46 | } 47 | return "", err 48 | } 49 | 50 | func DnsGetDoaminIP(domain string) (string, error) { 51 | if nil != LocalDNS { 52 | ips, err := LocalDNS.LookupA(domain) 53 | if len(ips) > 0 { 54 | return pickIP(ips), err 55 | } 56 | } 57 | return getIPByDefaultResolver(domain) 58 | } 59 | 60 | var CNIPSet *cip.CountryIPSet 61 | 62 | type LocalDNSConfig struct { 63 | Listen string 64 | TrustedDNS []string 65 | FastDNS []string 66 | CNIPSet string 67 | } 68 | 69 | func Init(conf *LocalDNSConfig) { 70 | cnipset, err := cip.LoadIPSet(conf.CNIPSet, "CN") 71 | if nil != err { 72 | logger.Error("Failed to load IP range file:%s with reason:%v", conf.CNIPSet, err) 73 | } else { 74 | CNIPSet = cnipset 75 | } 76 | cfg := &fdns.Config{} 77 | cfg.Listen = conf.Listen 78 | for _, s := range conf.FastDNS { 79 | ss := fdns.ServerConfig{ 80 | Server: s, 81 | Timeout: 500, 82 | MaxResponse: 1, 83 | } 84 | cfg.FastDNS = append(cfg.FastDNS, ss) 85 | } 86 | for _, s := range conf.TrustedDNS { 87 | ss := fdns.ServerConfig{ 88 | Server: s, 89 | Timeout: 1000, 90 | MaxResponse: 5, 91 | } 92 | cfg.TrustedDNS = append(cfg.TrustedDNS, ss) 93 | } 94 | cfg.MinTTL = 24 * 3600 95 | cfg.DialTimeout = netx.DialTimeout 96 | cfg.IsCNIP = func(ip net.IP) bool { 97 | if nil == CNIPSet { 98 | return false 99 | } 100 | return CNIPSet.IsInCountry(ip, "CN") 101 | } 102 | cfg.IsDomainPoisioned = func(domain string) int { 103 | //conf.GFWList.Load() 104 | return -1 105 | } 106 | LocalDNS, _ = fdns.NewTrustedDNS(cfg) 107 | if len(conf.Listen) > 0 { 108 | go func() { 109 | err := LocalDNS.Start() 110 | if nil != err { 111 | logger.Error("Failed to start dns server:%v", err) 112 | } 113 | }() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /common/dump/http.go: -------------------------------------------------------------------------------- 1 | package dump 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/flate" 7 | "compress/gzip" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "net/http/httputil" 13 | "os" 14 | "strings" 15 | "sync" 16 | 17 | "github.com/dsnet/compress/brotli" 18 | "github.com/yinqiwen/gotoolkit/iotools" 19 | "github.com/yinqiwen/gsnova/common/helper" 20 | ) 21 | 22 | var dumpFiles = make(map[string]*iotools.RotateFile) 23 | var dumpFilesMutex sync.Mutex 24 | 25 | func getDumpFile(file string) *iotools.RotateFile { 26 | dumpFilesMutex.Lock() 27 | f := dumpFiles[file] 28 | if nil == f { 29 | f = &iotools.RotateFile{ 30 | Path: file, 31 | MaxBackupIndex: 2, 32 | MaxFileSize: 1024 * 1024, 33 | SyncBytesPeriod: 1024 * 1024, 34 | } 35 | dumpFiles[file] = f 36 | } 37 | dumpFilesMutex.Unlock() 38 | return f 39 | } 40 | 41 | type HttpDumpReadWriter struct { 42 | R io.Reader 43 | W io.Writer 44 | 45 | notHTTP bool 46 | isTLS bool 47 | requestWriter *io.PipeWriter 48 | responseWriter *io.PipeWriter 49 | reqReader *io.PipeReader 50 | resReader *io.PipeReader 51 | requestReader *bufio.Reader 52 | responseReader *bufio.Reader 53 | 54 | dumpWriter io.Writer 55 | 56 | excludeBody []string 57 | includeBody []string 58 | } 59 | 60 | func decompressReader(r io.ReadCloser, header http.Header) io.ReadCloser { 61 | switch header.Get("Content-Encoding") { 62 | case "gzip": 63 | reader, err := gzip.NewReader(r) 64 | if nil == err { 65 | return ioutil.NopCloser(reader) 66 | } 67 | return r 68 | case "deflate": 69 | return flate.NewReader(r) 70 | case "br": 71 | reader, _ := brotli.NewReader(r, nil) 72 | return ioutil.NopCloser(reader) 73 | default: 74 | return r 75 | } 76 | } 77 | 78 | func (h *HttpDumpReadWriter) shouldDumpBody(header http.Header) bool { 79 | t := header.Get("Content-Type") 80 | if strings.Contains(t, "image") || strings.Contains(t, "binary") || strings.Contains(t, "video") || strings.Contains(t, "octet-stream") { 81 | return false 82 | } 83 | for _, exclude := range h.excludeBody { 84 | if strings.Contains(t, exclude) { 85 | return false 86 | } 87 | } 88 | if len(h.includeBody) > 0 { 89 | for _, include := range h.includeBody { 90 | if strings.Contains(t, include) { 91 | return true 92 | } 93 | } 94 | return false 95 | } 96 | return true 97 | } 98 | func (h *HttpDumpReadWriter) closeReader() error { 99 | if nil != h.reqReader { 100 | h.reqReader.Close() 101 | } 102 | if nil != h.resReader { 103 | h.resReader.Close() 104 | } 105 | return nil 106 | } 107 | func (h *HttpDumpReadWriter) closeWriter() error { 108 | if nil != h.requestWriter { 109 | h.requestWriter.Close() 110 | } 111 | if nil != h.responseWriter { 112 | h.responseWriter.Close() 113 | } 114 | 115 | return nil 116 | } 117 | func (h *HttpDumpReadWriter) Close() error { 118 | h.closeReader() 119 | h.closeWriter() 120 | return nil 121 | } 122 | 123 | func (h *HttpDumpReadWriter) Read(p []byte) (n int, err error) { 124 | n, err = h.R.Read(p) 125 | if n > 0 && !h.notHTTP { 126 | h.responseWriter.Write(p[0:n]) 127 | } 128 | return 129 | } 130 | func (h *HttpDumpReadWriter) Write(p []byte) (n int, err error) { 131 | n, err = h.W.Write(p) 132 | if n > 0 && !h.notHTTP { 133 | h.requestWriter.Write(p[0:n]) 134 | } 135 | return 136 | } 137 | 138 | func (h *HttpDumpReadWriter) dumpRecord(req *http.Request, res *http.Response) { 139 | var buf bytes.Buffer 140 | if nil != req { 141 | if !strings.Contains(req.RequestURI, "://") { 142 | if h.isTLS { 143 | req.RequestURI = "https://" + req.Host + req.RequestURI 144 | } else { 145 | req.RequestURI = "http://" + req.Host + req.RequestURI 146 | } 147 | } 148 | 149 | req.Body = decompressReader(req.Body, req.Header) 150 | content, _ := httputil.DumpRequest(req, h.shouldDumpBody(req.Header)) 151 | buf.Write(content) 152 | } 153 | if nil != res { 154 | fmt.Fprintf(&buf, "->\n") 155 | res.Body = decompressReader(res.Body, res.Header) 156 | content, _ := httputil.DumpResponse(res, h.shouldDumpBody(res.Header)) 157 | buf.Write(content) 158 | } 159 | fmt.Fprintf(&buf, "\n") 160 | io.Copy(h.dumpWriter, &buf) 161 | } 162 | 163 | func (h *HttpDumpReadWriter) dump() { 164 | 165 | for { 166 | req, err := http.ReadRequest(h.requestReader) 167 | if nil != err { 168 | h.closeReader() 169 | return 170 | } 171 | res, err := http.ReadResponse(h.responseReader, req) 172 | if nil != err { 173 | h.dumpRecord(req, nil) 174 | h.closeReader() 175 | return 176 | } 177 | h.dumpRecord(req, res) 178 | //content, _ = httputil.DumpResponse(res, true) 179 | } 180 | } 181 | 182 | type HttpDumpOptions struct { 183 | IncludeBody []string 184 | ExcludeBody []string 185 | Destination string 186 | IsTLS bool 187 | } 188 | 189 | func NewHttpDumpReadWriter(r io.Reader, w io.Writer, options *HttpDumpOptions) *HttpDumpReadWriter { 190 | h := &HttpDumpReadWriter{ 191 | R: r, 192 | W: w, 193 | isTLS: options.IsTLS, 194 | includeBody: options.IncludeBody, 195 | excludeBody: options.ExcludeBody, 196 | } 197 | pr, pw := io.Pipe() 198 | h.requestWriter = pw 199 | h.requestReader = bufio.NewReader(pr) 200 | h.reqReader = pr 201 | pr, pw = io.Pipe() 202 | h.responseWriter = pw 203 | h.responseReader = bufio.NewReader(pr) 204 | h.resReader = pr 205 | 206 | if len(options.Destination) == 0 { 207 | h.dumpWriter = os.Stdout 208 | } else { 209 | if strings.HasPrefix(options.Destination, "http://") || strings.HasPrefix(options.Destination, "https://") { 210 | h.dumpWriter = &helper.HttpPostWriter{URL: options.Destination} 211 | } else { 212 | h.dumpWriter = getDumpFile(options.Destination) 213 | } 214 | } 215 | 216 | go h.dump() 217 | return h 218 | } 219 | -------------------------------------------------------------------------------- /common/fakecert/fake.go: -------------------------------------------------------------------------------- 1 | package fakecert 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | //"errors" 11 | 12 | "log" 13 | "math/big" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | var RootCert tls.Certificate 19 | var X509RootCert *x509.Certificate 20 | var RC4Key = "8976501f8451f03c5c4067b47882f2e5" 21 | 22 | var cachedCertificates = make(map[string]tls.Certificate) 23 | 24 | func randBigInt() (value *big.Int) { 25 | value, _ = rand.Int(rand.Reader, big.NewInt(0x7FFFFFFFFFFFFFFF)) 26 | return 27 | } 28 | 29 | func randBytes() (bytes []byte) { 30 | bytes = make([]byte, 20) 31 | rand.Read(bytes) 32 | return 33 | } 34 | 35 | func init() { 36 | cert := "Fake-ACRoot-Certificate.cer" 37 | key := "Fake-ACRoot-Key.pem" 38 | root_cert, err := tls.LoadX509KeyPair(cert, key) 39 | if nil == err { 40 | RootCert = root_cert 41 | X509RootCert, err = x509.ParseCertificate(root_cert.Certificate[0]) 42 | return 43 | } 44 | log.Printf("###Failed to load root cert:%v", err) 45 | } 46 | 47 | func TLSConfig(host string) (*tls.Config, error) { 48 | cfg := new(tls.Config) 49 | if strings.Contains(host, ":") { 50 | host = strings.Split(host, ":")[0] 51 | } 52 | cert, err := getTLSCert(host) 53 | if nil != err { 54 | log.Printf("Failed to get tls cert:%v", err) 55 | return nil, err 56 | } 57 | cfg.Certificates = []tls.Certificate{cert} 58 | //cfg.BuildNameToCertificate() 59 | return cfg, nil 60 | } 61 | 62 | func getTLSCert(host string) (tls.Certificate, error) { 63 | var tls_cer tls.Certificate 64 | if cert, exist := cachedCertificates[host]; exist { 65 | return cert, nil 66 | } 67 | 68 | priv, err := rsa.GenerateKey(rand.Reader, 1024) 69 | if err != nil { 70 | return tls_cer, err 71 | } 72 | serial := randBigInt() 73 | keyId := randBytes() 74 | template := x509.Certificate{ 75 | Subject: pkix.Name{ 76 | CommonName: host, 77 | }, 78 | Issuer: pkix.Name{ 79 | CommonName: "GSnova Root Fake CA", 80 | }, 81 | SerialNumber: serial, 82 | SubjectKeyId: keyId, 83 | AuthorityKeyId: X509RootCert.AuthorityKeyId, 84 | NotBefore: time.Now().Add(-5 * time.Minute).UTC(), 85 | NotAfter: time.Now().AddDate(12, 0, 0).UTC(), 86 | } 87 | 88 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, X509RootCert, &priv.PublicKey, RootCert.PrivateKey) 89 | if err != nil { 90 | return tls_cer, err 91 | } 92 | crt, err := x509.ParseCertificate(derBytes) 93 | if err != nil { 94 | return tls_cer, err 95 | } 96 | cBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: crt.Raw}) 97 | kBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 98 | tls_cer, err = tls.X509KeyPair(cBytes, kBytes) 99 | if nil == err { 100 | cachedCertificates[host] = tls_cer 101 | } 102 | return tls_cer, err 103 | } 104 | -------------------------------------------------------------------------------- /common/gfwlist/gfwlist.go: -------------------------------------------------------------------------------- 1 | package gfwlist 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "os" 11 | "regexp" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/yinqiwen/gsnova/common/logger" 17 | ) 18 | 19 | type hostWildcardRule struct { 20 | pattern string 21 | } 22 | 23 | func (r *hostWildcardRule) match(req *http.Request) bool { 24 | if strings.Contains(req.Host, r.pattern) { 25 | return true 26 | } 27 | return false 28 | } 29 | 30 | type urlWildcardRule struct { 31 | pattern string 32 | prefixMatch bool 33 | } 34 | 35 | func (r *urlWildcardRule) match(req *http.Request) bool { 36 | if len(req.URL.Scheme) == 0 { 37 | req.URL.Scheme = "https" 38 | } 39 | if r.prefixMatch { 40 | return strings.HasPrefix(req.URL.String(), r.pattern) 41 | } 42 | return strings.Contains(req.URL.String(), r.pattern) 43 | } 44 | 45 | type regexRule struct { 46 | pattern string 47 | } 48 | 49 | func (r *regexRule) match(req *http.Request) bool { 50 | if len(req.URL.Scheme) == 0 { 51 | req.URL.Scheme = "https" 52 | } 53 | matched, err := regexp.MatchString(r.pattern, req.URL.String()) 54 | if nil != err { 55 | logger.Error("Invalid regex pattern:%s wiuth reason:%v", r.pattern, err) 56 | } 57 | return matched 58 | } 59 | 60 | type whiteListRule struct { 61 | r gfwListRule 62 | } 63 | 64 | func (r *whiteListRule) match(req *http.Request) bool { 65 | return r.r.match(req) 66 | } 67 | 68 | type gfwListRule interface { 69 | match(req *http.Request) bool 70 | } 71 | 72 | type GFWList struct { 73 | ruleMap map[string]gfwListRule 74 | ruleList []gfwListRule 75 | mutex sync.Mutex 76 | } 77 | 78 | func (gfw *GFWList) clone(n *GFWList) { 79 | gfw.mutex.Lock() 80 | defer gfw.mutex.Unlock() 81 | gfw.ruleList = n.ruleList 82 | } 83 | 84 | func (gfw *GFWList) FastMatchDoamin(req *http.Request) (bool, bool) { 85 | domain := req.Host 86 | rootDomain := domain 87 | if strings.Contains(domain, ":") { 88 | domain, _, _ = net.SplitHostPort(domain) 89 | rootDomain = domain 90 | } 91 | 92 | rule, exist := gfw.ruleMap[domain] 93 | if !exist { 94 | ss := strings.Split(domain, ".") 95 | if len(ss) > 2 { 96 | rootDomain = ss[len(ss)-2] + "." + ss[len(ss)-1] 97 | if len(ss[len(ss)-2]) < 4 && len(ss) >= 3 { 98 | rootDomain = ss[len(ss)-3] + "." + rootDomain 99 | } 100 | } 101 | rule, exist = gfw.ruleMap[rootDomain] 102 | } 103 | if exist { 104 | matched := rule.match(req) 105 | if _, ok := rule.(*whiteListRule); ok { 106 | return !matched, true 107 | } 108 | return matched, true 109 | } 110 | return false, false 111 | } 112 | 113 | func (gfw *GFWList) IsBlockedByGFW(req *http.Request) bool { 114 | gfw.mutex.Lock() 115 | defer gfw.mutex.Unlock() 116 | 117 | fastMatchResult, exist := gfw.FastMatchDoamin(req) 118 | if exist { 119 | return fastMatchResult 120 | } 121 | 122 | for _, rule := range gfw.ruleList { 123 | if rule.match(req) { 124 | if _, ok := rule.(*whiteListRule); ok { 125 | //log.Printf("#### %s is in whilte list %v", req.Host, rule.(*whiteListRule).r) 126 | return false 127 | } 128 | return true 129 | } 130 | } 131 | return false 132 | } 133 | 134 | func Parse(rules string) (*GFWList, error) { 135 | reader := bufio.NewReader(strings.NewReader(rules)) 136 | gfw := new(GFWList) 137 | gfw.ruleMap = make(map[string]gfwListRule) 138 | //i := 0 139 | for { 140 | line, _, err := reader.ReadLine() 141 | if nil != err { 142 | break 143 | } 144 | str := strings.TrimSpace(string(line)) 145 | //comment 146 | if strings.HasPrefix(str, "!") || len(str) == 0 || strings.HasPrefix(str, "[") { 147 | continue 148 | } 149 | var rule gfwListRule 150 | isWhileListRule := false 151 | fastMatch := false 152 | if strings.HasPrefix(str, "@@") { 153 | str = str[2:] 154 | isWhileListRule = true 155 | } 156 | if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") { 157 | str = str[1 : len(str)-1] 158 | rule = ®exRule{str} 159 | } else { 160 | if strings.HasPrefix(str, "||") { 161 | str = str[2:] 162 | rule = &hostWildcardRule{str} 163 | fastMatch = true 164 | } else if strings.HasPrefix(str, "|") { 165 | rule = &urlWildcardRule{str[1:], true} 166 | } else { 167 | if !strings.Contains(str, "/") { 168 | fastMatch = true 169 | rule = &hostWildcardRule{str} 170 | if strings.HasPrefix(str, ".") { 171 | str = str[1:] 172 | } 173 | } else { 174 | rule = &urlWildcardRule{str, false} 175 | } 176 | } 177 | } 178 | if isWhileListRule { 179 | rule = &whiteListRule{rule} 180 | } 181 | if fastMatch { 182 | gfw.ruleMap[str] = rule 183 | } else { 184 | gfw.ruleList = append(gfw.ruleList, rule) 185 | } 186 | } 187 | return gfw, nil 188 | } 189 | 190 | func ParseRaw(rules string) (*GFWList, error) { 191 | content, err := base64.StdEncoding.DecodeString(string(rules)) 192 | if err != nil { 193 | return nil, err 194 | } 195 | return Parse(string(content)) 196 | } 197 | 198 | func NewGFWList(u string, hc *http.Client, userRules []string, cacheFile string, watch bool) (*GFWList, error) { 199 | // hc := &http.Client{} 200 | // if len(proxy) > 0 { 201 | // hc.Transport = &http.Transport{ 202 | // Proxy: func(*http.Request) (*url.URL, error) { 203 | // return url.Parse(proxy) 204 | // }, 205 | // } 206 | // } 207 | nextFetchTime := 6 * time.Hour 208 | firstFetch := true 209 | fetch := func() (string, error) { 210 | var gfwlistContent string 211 | fetchFromRemote := false 212 | if firstFetch { 213 | if len(cacheFile) > 0 { 214 | if _, err := os.Stat(cacheFile); nil == err { 215 | gfwlistBody, _ := ioutil.ReadFile(cacheFile) 216 | gfwlistContent = string(gfwlistBody) 217 | nextFetchTime = 30 * time.Second 218 | } 219 | } 220 | } 221 | if len(gfwlistContent) == 0 || !firstFetch { 222 | firstFetch = false 223 | resp, err := hc.Get(u) 224 | if nil != err { 225 | return "", err 226 | } 227 | defer resp.Body.Close() 228 | if resp.StatusCode != 200 { 229 | return "", fmt.Errorf("Invalid response:%v", resp) 230 | } 231 | body, err := ioutil.ReadAll(resp.Body) 232 | if nil != err { 233 | return "", err 234 | } 235 | plainTxt, err := base64.StdEncoding.DecodeString(string(body)) 236 | if nil != err { 237 | return "", err 238 | } 239 | logger.Notice("Fetch latest GFWList success at %s", cacheFile) 240 | fetchFromRemote = true 241 | gfwlistContent = string(plainTxt) 242 | if len(userRules) > 0 { 243 | userGfwlistContent := "\n!################User Rule List Begin#################\n" 244 | for _, rule := range userRules { 245 | userGfwlistContent = userGfwlistContent + rule + "\n" 246 | } 247 | userGfwlistContent = userGfwlistContent + "!################User Rule List End#################\n" 248 | gfwlistContent = gfwlistContent + userGfwlistContent 249 | } 250 | } 251 | if len(cacheFile) > 0 && fetchFromRemote { 252 | ioutil.WriteFile(cacheFile, []byte(gfwlistContent), 0666) 253 | } 254 | return gfwlistContent, nil 255 | } 256 | content, err := fetch() 257 | if nil != err { 258 | return nil, err 259 | } 260 | gfwlist, err := Parse(content) 261 | if nil != err { 262 | return nil, err 263 | } 264 | if watch { 265 | go func() { 266 | for { 267 | select { 268 | case <-time.After(nextFetchTime): 269 | newContent, nerr := fetch() 270 | if nerr == nil { 271 | nlist, _ := Parse(newContent) 272 | gfwlist.clone(nlist) 273 | } 274 | } 275 | } 276 | }() 277 | } 278 | return gfwlist, nil 279 | } 280 | -------------------------------------------------------------------------------- /common/gfwlist/gfwlist_test.go: -------------------------------------------------------------------------------- 1 | package gfwlist 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestGFWList(t *testing.T) { 11 | userRules := []string{"||4ter2n.com", "|https://85.17.73.31/"} 12 | gfwlist, err := NewGFWList("https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt", "", userRules, "gfwlist.txt", false) 13 | if nil != err { 14 | log.Printf("#####%v", err) 15 | return 16 | } 17 | req, _ := http.NewRequest("GET", "https://static.soup.io", nil) 18 | s1 := time.Now() 19 | for i := 0; i < 100; i++ { 20 | gfwlist.IsBlockedByGFW(req) 21 | } 22 | v := gfwlist.IsBlockedByGFW(req) 23 | log.Printf("#####match %v %v", v, time.Now().Sub(s1)) 24 | } 25 | -------------------------------------------------------------------------------- /common/helper/bytes.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "errors" 7 | "fmt" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | func PKCS7Pad(buf *bytes.Buffer, blen int) { 14 | padding := 16 - (blen % 16) 15 | for i := 0; i < padding; i++ { 16 | buf.WriteByte(byte(padding)) 17 | } 18 | } 19 | 20 | // Returns slice of the original data without padding. 21 | func PKCS7Unpad(in []byte) []byte { 22 | if len(in) == 0 { 23 | return nil 24 | } 25 | 26 | padding := in[len(in)-1] 27 | if int(padding) > len(in) || padding > aes.BlockSize { 28 | return nil 29 | } else if padding == 0 { 30 | return nil 31 | } 32 | 33 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 34 | if in[i] != padding { 35 | return nil 36 | } 37 | } 38 | return in[:len(in)-int(padding)] 39 | } 40 | 41 | //copy from https://github.com/cloudfoundry/bytefmt/blob/master/bytes.go 42 | const ( 43 | BYTE = 1.0 44 | KILOBYTE = 1024 * BYTE 45 | MEGABYTE = 1024 * KILOBYTE 46 | GIGABYTE = 1024 * MEGABYTE 47 | TERABYTE = 1024 * GIGABYTE 48 | ) 49 | 50 | var bytesPattern *regexp.Regexp = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)([KMGT]B?|B)$`) 51 | 52 | var invalidByteQuantityError = errors.New("Byte quantity must be a positive integer with a unit of measurement like M, MB, G, or GB") 53 | 54 | // ByteSize returns a human-readable byte string of the form 10M, 12.5K, and so forth. The following units are available: 55 | // T: Terabyte 56 | // G: Gigabyte 57 | // M: Megabyte 58 | // K: Kilobyte 59 | // B: Byte 60 | // The unit that results in the smallest number greater than or equal to 1 is always chosen. 61 | func ByteSize(bytes uint64) string { 62 | unit := "" 63 | value := float32(bytes) 64 | 65 | switch { 66 | case bytes >= TERABYTE: 67 | unit = "T" 68 | value = value / TERABYTE 69 | case bytes >= GIGABYTE: 70 | unit = "G" 71 | value = value / GIGABYTE 72 | case bytes >= MEGABYTE: 73 | unit = "M" 74 | value = value / MEGABYTE 75 | case bytes >= KILOBYTE: 76 | unit = "K" 77 | value = value / KILOBYTE 78 | case bytes >= BYTE: 79 | unit = "B" 80 | case bytes == 0: 81 | return "0" 82 | } 83 | 84 | stringValue := fmt.Sprintf("%.1f", value) 85 | stringValue = strings.TrimSuffix(stringValue, ".0") 86 | return fmt.Sprintf("%s%s", stringValue, unit) 87 | } 88 | 89 | // ToMegabytes parses a string formatted by ByteSize as megabytes. 90 | func ToMegabytes(s string) (uint64, error) { 91 | bytes, err := ToBytes(s) 92 | if err != nil { 93 | return 0, err 94 | } 95 | 96 | return bytes / MEGABYTE, nil 97 | } 98 | 99 | // ToBytes parses a string formatted by ByteSize as bytes. 100 | func ToBytes(s string) (uint64, error) { 101 | parts := bytesPattern.FindStringSubmatch(strings.TrimSpace(s)) 102 | if len(parts) < 3 { 103 | return 0, invalidByteQuantityError 104 | } 105 | 106 | value, err := strconv.ParseFloat(parts[1], 64) 107 | if err != nil || value <= 0 { 108 | return 0, invalidByteQuantityError 109 | } 110 | 111 | var bytes uint64 112 | unit := strings.ToUpper(parts[2]) 113 | switch unit[:1] { 114 | case "T": 115 | bytes = uint64(value * TERABYTE) 116 | case "G": 117 | bytes = uint64(value * GIGABYTE) 118 | case "M": 119 | bytes = uint64(value * MEGABYTE) 120 | case "K": 121 | bytes = uint64(value * KILOBYTE) 122 | case "B": 123 | bytes = uint64(value * BYTE) 124 | } 125 | 126 | return bytes, nil 127 | } 128 | -------------------------------------------------------------------------------- /common/helper/ca.go: -------------------------------------------------------------------------------- 1 | // +build !mips,!mipsle 2 | 3 | package helper 4 | 5 | import ( 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "errors" 10 | "fmt" 11 | "os" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "golang.org/x/net/publicsuffix" 17 | 18 | "github.com/google/easypki/pkg/certificate" 19 | "github.com/google/easypki/pkg/easypki" 20 | "github.com/google/easypki/pkg/store" 21 | ) 22 | 23 | var pkiStore *easypki.EasyPKI 24 | var ErrPKIStoreNotInit = errors.New("PKI store is not inited") 25 | var caGenLock sync.Mutex 26 | 27 | func CreateRootCA(dir string) error { 28 | os.Mkdir(dir, 0775) 29 | store.InitCADir(dir) 30 | pkiStore = &easypki.EasyPKI{Store: &store.Local{Root: dir}} 31 | if _, err := pkiStore.GetCA("Root"); nil == err { 32 | return nil 33 | } 34 | filename := "Root" 35 | subject := pkix.Name{CommonName: "GSNOVA Root CA"} 36 | template := &x509.Certificate{ 37 | Subject: subject, 38 | NotAfter: time.Now().AddDate(0, 0, 36500), 39 | MaxPathLen: -1, 40 | IsCA: true, 41 | } 42 | req := &easypki.Request{ 43 | Name: filename, 44 | Template: template, 45 | IsClientCertificate: false, 46 | PrivateKeySize: 2048, 47 | } 48 | 49 | if err := pkiStore.Sign(nil, req); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | 55 | func getCAByDomain(domain string) (*certificate.Bundle, error) { 56 | if nil == pkiStore { 57 | return nil, ErrPKIStoreNotInit 58 | } 59 | caGenLock.Lock() 60 | defer caGenLock.Unlock() 61 | pubsuffix, _ := publicsuffix.PublicSuffix(domain) 62 | if len(pubsuffix) == 0 { 63 | return nil, fmt.Errorf("No publicsuffix found for %s", domain) 64 | } 65 | restLen := len(domain) - len(pubsuffix) - 1 66 | rest := domain[0:restLen] 67 | ss := strings.Split(rest, ".") 68 | var bundleName string 69 | var dnsNames []string 70 | if len(ss) == 1 { 71 | bundleName = domain 72 | dnsNames = []string{domain} 73 | } else { 74 | bundleName = "*." + strings.Join(ss[1:], ".") + "." + pubsuffix 75 | dnsNames = []string{bundleName} 76 | } 77 | bundle, err := pkiStore.GetBundle("Root", bundleName) 78 | if nil == err { 79 | return bundle, nil 80 | } 81 | signer, err := pkiStore.GetCA("Root") 82 | if nil != err { 83 | return nil, err 84 | } 85 | filename := bundleName 86 | subject := pkix.Name{CommonName: bundleName} 87 | template := &x509.Certificate{ 88 | Subject: subject, 89 | NotAfter: time.Now().AddDate(0, 0, 36500), 90 | MaxPathLen: -1, 91 | DNSNames: dnsNames, 92 | //IsCA: true, 93 | } 94 | req := &easypki.Request{ 95 | Name: filename, 96 | Template: template, 97 | IsClientCertificate: false, 98 | PrivateKeySize: 2048, 99 | } 100 | if err = pkiStore.Sign(signer, req); err != nil { 101 | return nil, err 102 | } 103 | return pkiStore.GetBundle("Root", bundleName) 104 | } 105 | 106 | func TLSConfig(domain string) (*tls.Config, error) { 107 | cfg := new(tls.Config) 108 | if strings.Contains(domain, ":") { 109 | domain = strings.Split(domain, ":")[0] 110 | } 111 | bundle, err := getCAByDomain(domain) 112 | if nil != err { 113 | return nil, err 114 | } 115 | 116 | cert := tls.Certificate{} 117 | cert.Certificate = append(cert.Certificate, bundle.Cert.Raw) 118 | cert.PrivateKey = bundle.Key 119 | //cert.Leaf, _ = x509.ParseCertificate(bundle.Cert.Raw) 120 | cfg.Certificates = []tls.Certificate{cert} 121 | return cfg, nil 122 | } 123 | -------------------------------------------------------------------------------- /common/helper/ca_others.go: -------------------------------------------------------------------------------- 1 | // +build mipsle mips 2 | 3 | package helper 4 | 5 | import ( 6 | "crypto/tls" 7 | "errors" 8 | ) 9 | 10 | var errNotSupportedForMIPS = errors.New("Not supported for MIPS") 11 | 12 | func CreateRootCA(dir string) error { 13 | return errNotSupportedForMIPS 14 | } 15 | 16 | func TLSConfig(domain string) (*tls.Config, error) { 17 | return nil, errNotSupportedForMIPS 18 | } 19 | -------------------------------------------------------------------------------- /common/helper/chan.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func AsyncSendErr(ch chan error, err error) { 4 | if ch == nil { 5 | return 6 | } 7 | select { 8 | case ch <- err: 9 | default: 10 | } 11 | } 12 | 13 | // asyncNotify is used to signal a waiting goroutine 14 | func AsyncNotify(ch chan struct{}) { 15 | select { 16 | case ch <- struct{}{}: 17 | default: 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/helper/conn.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | ) 7 | 8 | type BufConn struct { 9 | net.Conn 10 | BR *bufio.Reader 11 | } 12 | 13 | func (c *BufConn) Peek(n int) ([]byte, error) { 14 | return c.BR.Peek(n) 15 | } 16 | 17 | func (c *BufConn) Read(b []byte) (n int, err error) { 18 | return c.BR.Read(b) 19 | } 20 | 21 | func (c *BufConn) Write(b []byte) (n int, err error) { 22 | return c.Conn.Write(b) 23 | } 24 | 25 | func (c *BufConn) Reset(conn net.Conn) { 26 | c.Conn = conn 27 | } 28 | 29 | func NewBufConn(c net.Conn, r *bufio.Reader) *BufConn { 30 | conn := &BufConn{Conn: c} 31 | conn.BR = r 32 | if nil == r { 33 | conn.BR = bufio.NewReader(c) 34 | } 35 | return conn 36 | } 37 | -------------------------------------------------------------------------------- /common/helper/io.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "log" 7 | "net" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | type PeekReader interface { 13 | Peek(n int) ([]byte, error) 14 | } 15 | 16 | type BufferChunkReader struct { 17 | io.Reader 18 | Err error 19 | } 20 | 21 | func (r *BufferChunkReader) Read(p []byte) (int, error) { 22 | n, err := r.Reader.Read(p) 23 | r.Err = err 24 | if nil != err { 25 | return n, err 26 | } 27 | return n, io.EOF 28 | } 29 | 30 | type DebugReader struct { 31 | io.Reader 32 | Buf bytes.Buffer 33 | } 34 | 35 | func (r *DebugReader) Read(p []byte) (int, error) { 36 | n, err := r.Reader.Read(p) 37 | if n > 0 { 38 | r.Buf.Write(p[0:n]) 39 | } 40 | return n, err 41 | } 42 | 43 | func IsTimeoutError(err error) bool { 44 | if err, ok := err.(net.Error); ok && err.Timeout() { 45 | return true 46 | } 47 | return false 48 | } 49 | 50 | type TimeoutReadWriteCloser struct { 51 | io.ReadWriteCloser 52 | readDeadline time.Time 53 | writeDeadline time.Time 54 | } 55 | 56 | func (s *TimeoutReadWriteCloser) SetReadDeadline(t time.Time) error { 57 | s.readDeadline = t 58 | return nil 59 | } 60 | func (s *TimeoutReadWriteCloser) SetWriteDeadline(t time.Time) error { 61 | s.writeDeadline = t 62 | return nil 63 | } 64 | func (s *TimeoutReadWriteCloser) Read(p []byte) (n int, err error) { 65 | var timeout <-chan time.Time 66 | if !s.readDeadline.IsZero() { 67 | delay := s.readDeadline.Sub(time.Now()) 68 | timeout = time.After(delay) 69 | } else { 70 | return s.ReadWriteCloser.Read(p) 71 | } 72 | done := make(chan bool, 1) 73 | go func() { 74 | n, err = s.ReadWriteCloser.Read(p) 75 | done <- true 76 | }() 77 | select { 78 | case <-done: 79 | return 80 | case <-timeout: 81 | return 0, ErrReadTimeout 82 | } 83 | } 84 | 85 | func (s *TimeoutReadWriteCloser) Write(p []byte) (n int, err error) { 86 | var timeout <-chan time.Time 87 | if !s.writeDeadline.IsZero() { 88 | delay := s.writeDeadline.Sub(time.Now()) 89 | timeout = time.After(delay) 90 | } else { 91 | return s.ReadWriteCloser.Write(p) 92 | } 93 | done := make(chan bool, 1) 94 | go func() { 95 | n, err = s.ReadWriteCloser.Write(p) 96 | done <- true 97 | }() 98 | select { 99 | case <-done: 100 | return 101 | case <-timeout: 102 | return 0, ErrWriteTimeout 103 | } 104 | } 105 | 106 | type HttpPostWriter struct { 107 | URL string 108 | } 109 | 110 | func (s *HttpPostWriter) Write(p []byte) (n int, err error) { 111 | res, err := http.Post(s.URL, "text/plain", bytes.NewBuffer(p)) 112 | if nil != err { 113 | log.Printf("Post error:%v", err) 114 | return 0, err 115 | } 116 | if nil != res.Body { 117 | res.Body.Close() 118 | } 119 | return len(p), nil 120 | } 121 | -------------------------------------------------------------------------------- /common/helper/rand.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | rand.Seed(time.Now().UnixNano()) 10 | } 11 | 12 | func RandBetween(min, max int) int { 13 | rand.Seed(time.Now().Unix()) 14 | return rand.Intn(max-min) + min 15 | } 16 | 17 | var letterRunes = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 18 | 19 | func RandAsciiString(n int) string { 20 | b := make([]byte, n) 21 | for i := range b { 22 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 23 | } 24 | return string(b) 25 | } 26 | -------------------------------------------------------------------------------- /common/helper/sni.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/yinqiwen/gsnova/common/logger" 7 | ) 8 | 9 | var ErrTLSIncomplete = errors.New("TLS header incomplete") 10 | var ErrNoSNI = errors.New("No SNI in protocol") 11 | var ErrTLSClientHello = errors.New("Invalid tls client hello") 12 | 13 | func PeekTLSServerName(reader PeekReader) (string, error) { 14 | tlsHederLen := 5 15 | hbuf, err := reader.Peek(tlsHederLen) 16 | if nil != err { 17 | return "", err 18 | } 19 | if hbuf[0] != 0x16 { // recordTypeHandshake 20 | //log.Printf("####1err:%d", hbuf[0]) 21 | return "", ErrTLSClientHello 22 | } 23 | tlsMajorVer := int(hbuf[1]) 24 | tlsMinorVer := int(hbuf[2]) 25 | if tlsMajorVer < 3 { 26 | logger.Error("Invaid tls ver:%d.%v with %v", tlsMajorVer, tlsMinorVer, ErrNoSNI) 27 | return "", ErrNoSNI 28 | } 29 | restLen := (int(hbuf[3]) << 8) + int(hbuf[4]) 30 | restBuf, err := reader.Peek(tlsHederLen + restLen) 31 | if nil != err { 32 | return "", err 33 | } 34 | restBuf = restBuf[tlsHederLen:] 35 | 36 | tlsHandshakeTypeClientHello := 0x01 37 | if int(restBuf[0]) != tlsHandshakeTypeClientHello { 38 | return "", ErrTLSClientHello 39 | } 40 | /* Skip past fixed length records: 41 | 1 Handshake Type 42 | 3 Length 43 | 2 Version (again) 44 | 32 Random 45 | to Session ID Length 46 | */ 47 | if len(restBuf) < 42 { 48 | return "", ErrNoSNI 49 | } 50 | restBuf = restBuf[38:] 51 | sessionIDLen := int(restBuf[0]) 52 | restBuf = restBuf[1+sessionIDLen:] 53 | if len(restBuf) < 2 { 54 | return "", ErrTLSClientHello 55 | } 56 | // cipherSuiteLen is the number of bytes of cipher suite numbers. Since 57 | // they are uint16s, the number must be even. 58 | cipherSuiteLen := int(restBuf[0])<<8 | int(restBuf[1]) 59 | if cipherSuiteLen%2 == 1 || len(restBuf) < 2+cipherSuiteLen { 60 | return "", ErrTLSClientHello 61 | } 62 | restBuf = restBuf[2+cipherSuiteLen:] 63 | 64 | compressionMethodsLen := int(restBuf[0]) 65 | if len(restBuf) < 1+compressionMethodsLen { 66 | return "", ErrTLSClientHello 67 | } 68 | restBuf = restBuf[1+compressionMethodsLen:] 69 | 70 | if len(restBuf) < 2 { 71 | return "", ErrNoSNI 72 | } 73 | extensionsLength := int(restBuf[0])<<8 | int(restBuf[1]) 74 | restBuf = restBuf[2:] 75 | if extensionsLength != len(restBuf) { 76 | return "", ErrTLSClientHello 77 | } 78 | for len(restBuf) != 0 { 79 | if len(restBuf) < 4 { 80 | return "", ErrTLSClientHello 81 | } 82 | extension := uint16(restBuf[0])<<8 | uint16(restBuf[1]) 83 | length := int(restBuf[2])<<8 | int(restBuf[3]) 84 | restBuf = restBuf[4:] 85 | if len(restBuf) < length { 86 | return "", ErrTLSClientHello 87 | } 88 | 89 | switch extension { 90 | case 0x00: 91 | if length < 2 { 92 | return "", ErrTLSClientHello 93 | } 94 | numNames := int(restBuf[0])<<8 | int(restBuf[1]) 95 | d := restBuf[2:] 96 | for i := 0; i < numNames; i++ { 97 | if len(d) < 3 { 98 | return "", ErrTLSClientHello 99 | } 100 | nameType := d[0] 101 | nameLen := int(d[1])<<8 | int(d[2]) 102 | d = d[3:] 103 | if len(d) < nameLen { 104 | return "", ErrTLSClientHello 105 | } 106 | if nameType == 0 { 107 | serverName := string(d[0:nameLen]) 108 | return serverName, nil 109 | } 110 | d = d[nameLen:] 111 | } 112 | } 113 | restBuf = restBuf[length:] 114 | } 115 | return "", ErrTLSClientHello 116 | } 117 | -------------------------------------------------------------------------------- /common/helper/str.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | func GetRequestURLString(req *http.Request) string { 14 | if nil == req { 15 | return "" 16 | } 17 | str := req.URL.String() 18 | if len(req.URL.Scheme) == 0 && strings.EqualFold(req.Method, "Connect") { 19 | str = fmt.Sprintf("https://%s", req.Host) 20 | } 21 | if !strings.HasPrefix(str, "http://") && !strings.HasPrefix(str, "https://") { 22 | scheme := req.URL.Scheme 23 | if len(req.URL.Scheme) == 0 { 24 | scheme = "http" 25 | 26 | } 27 | str = fmt.Sprintf("%s://%s%s", scheme, req.Host, str) 28 | } 29 | return str 30 | } 31 | 32 | func PrepareRegexp(rule string) (*regexp.Regexp, error) { 33 | rule = strings.TrimSpace(rule) 34 | rule = strings.Replace(rule, ".", "\\.", -1) 35 | rule = strings.Replace(rule, "?", "\\?", -1) 36 | rule = strings.Replace(rule, "*", ".*", -1) 37 | return regexp.Compile(rule) 38 | } 39 | 40 | func WildcardMatch(text string, pattern string) bool { 41 | cards := strings.Split(pattern, "*") 42 | for _, str := range cards { 43 | idx := strings.Index(text, str) 44 | if idx == -1 { 45 | return false 46 | } 47 | text = strings.TrimLeft(text, str+"*") 48 | } 49 | return true 50 | } 51 | 52 | func ReadWithoutComment(file string, commentPrefix string) ([]byte, error) { 53 | f, err := os.Open(file) 54 | if err != nil { 55 | return nil, err 56 | } 57 | defer f.Close() 58 | 59 | var total bytes.Buffer 60 | scanner := bufio.NewScanner(f) 61 | for scanner.Scan() { 62 | line := scanner.Text() 63 | line = strings.TrimSpace(line) 64 | if !strings.HasPrefix(line, commentPrefix) { 65 | total.WriteString(line) 66 | total.WriteString("\n") 67 | } 68 | } 69 | return total.Bytes(), nil 70 | } 71 | -------------------------------------------------------------------------------- /common/hosts/hosts.go: -------------------------------------------------------------------------------- 1 | package hosts 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "regexp" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/yinqiwen/gsnova/common/helper" 11 | ) 12 | 13 | const SNIProxy = "sni_proxy" 14 | 15 | type hostMapping struct { 16 | host string 17 | hostRegex *regexp.Regexp 18 | mapping []string 19 | cursor int 20 | } 21 | 22 | func (h *hostMapping) Get() string { 23 | c := h.cursor 24 | if c >= len(h.mapping) { 25 | c = 0 26 | } 27 | s := h.mapping[c] 28 | h.cursor = c + 1 29 | return s 30 | } 31 | 32 | var hostMappingTable = make(map[string]*hostMapping) 33 | var mappingMutex sync.Mutex 34 | 35 | func getHost(host string) (string, bool) { 36 | var ok bool 37 | mapping, exist := hostMappingTable[host] 38 | if exist { 39 | s := mapping.Get() 40 | ok := true 41 | if !strings.Contains(s, ".") { //alials name 42 | s, ok = getHost(s) 43 | } 44 | return s, ok 45 | } 46 | for _, m := range hostMappingTable { 47 | if nil != m.hostRegex { 48 | if m.hostRegex.MatchString(host) { 49 | s := m.Get() 50 | if !strings.Contains(s, ".") { //alials name 51 | s, ok = getHost(s) 52 | } else { 53 | hostMappingTable[host] = m 54 | ok = true 55 | } 56 | return s, ok 57 | } 58 | } 59 | } 60 | return host, false 61 | } 62 | 63 | func GetHost(host string) string { 64 | mappingMutex.Lock() 65 | defer mappingMutex.Unlock() 66 | s, _ := getHost(host) 67 | return s 68 | } 69 | 70 | func GetAddr(addr string, defaultPort string) string { 71 | host, port, err := net.SplitHostPort(addr) 72 | if nil != err { 73 | host = addr 74 | } 75 | if net.ParseIP(host) == nil { 76 | mappingMutex.Lock() 77 | defer mappingMutex.Unlock() 78 | host, _ = getHost(host) 79 | } 80 | if nil == err { 81 | return net.JoinHostPort(host, port) 82 | } 83 | return net.JoinHostPort(host, defaultPort) 84 | } 85 | 86 | func InHosts(host string) bool { 87 | if strings.Contains(host, ":") { 88 | return true 89 | } 90 | mappingMutex.Lock() 91 | defer mappingMutex.Unlock() 92 | if strings.Contains(host, ":") { 93 | host, _, _ = net.SplitHostPort(host) 94 | } 95 | _, ok := getHost(host) 96 | return ok 97 | } 98 | 99 | func Clear() { 100 | mappingMutex.Lock() 101 | defer mappingMutex.Unlock() 102 | hostMappingTable = make(map[string]*hostMapping) 103 | } 104 | 105 | func Init(confile string) error { 106 | //file := "hosts.json" 107 | hs := make(map[string][]string) 108 | data, err := helper.ReadWithoutComment(confile, "//") 109 | if nil == err { 110 | err = json.Unmarshal(data, &hs) 111 | } 112 | if nil != err { 113 | //fmt.Printf("Failed to load hosts config:%s for reason:%v", string(data), err) 114 | return err 115 | } 116 | mappingMutex.Lock() 117 | defer mappingMutex.Unlock() 118 | hostMappingTable = make(map[string]*hostMapping) 119 | for k, vs := range hs { 120 | if len(vs) > 0 { 121 | mapping := new(hostMapping) 122 | mapping.host = k 123 | mapping.mapping = vs 124 | if strings.Contains(k, "*") { 125 | rule := strings.Replace(k, "*", ".*", -1) 126 | mapping.hostRegex, _ = regexp.Compile(rule) 127 | } 128 | hostMappingTable[k] = mapping 129 | } 130 | } 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /common/logger/ansi_color.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func setBoldGreen() { 9 | fmt.Fprint(os.Stdout, "\x1b[1m\x1b[32m") 10 | } 11 | 12 | func unsetBoldGreen() { 13 | fmt.Fprint(os.Stdout, "\x1b[21m\x1b[0m") 14 | } 15 | 16 | func setBoldRed() { 17 | fmt.Fprint(os.Stdout, "\x1b[1m\x1b[31m") 18 | } 19 | 20 | func unsetBoldRed() { 21 | fmt.Fprint(os.Stdout, "\x1b[21m\x1b[0m") 22 | } 23 | 24 | func setYellow() { 25 | fmt.Fprint(os.Stdout, "\x1b[33m") 26 | } 27 | 28 | func unsetYellow() { 29 | fmt.Fprint(os.Stdout, "\x1b[0m") 30 | } 31 | -------------------------------------------------------------------------------- /common/logger/color_console_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package logger 4 | 5 | func setINFOColor() { 6 | setBoldGreen() 7 | } 8 | func unsetINFOColor() { 9 | unsetBoldGreen() 10 | } 11 | func setNoticeColor() { 12 | setYellow() 13 | } 14 | func unsetNoticeColor() { 15 | unsetYellow() 16 | } 17 | func setErrorColor() { 18 | setBoldRed() 19 | } 20 | func unsetErrorColor() { 21 | unsetBoldRed() 22 | } 23 | -------------------------------------------------------------------------------- /common/logger/color_console_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package logger 4 | 5 | import ( 6 | "os" 7 | "strings" 8 | "syscall" 9 | "unicode/utf16" 10 | "unsafe" 11 | ) 12 | 13 | const ( 14 | foregroundBlue = uint16(0x0001) 15 | foregroundGreen = uint16(0x0002) 16 | foregroundRed = uint16(0x0004) 17 | foregroundYellow = uint16(0x0006) 18 | foregroundIntensity = uint16(0x0008) 19 | backgroundBlue = uint16(0x0010) 20 | backgroundGreen = uint16(0x0020) 21 | backgroundRed = uint16(0x0040) 22 | backgroundIntensity = uint16(0x0080) 23 | underscore = uint16(0x8000) 24 | reset = uint16(0x07) 25 | 26 | foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity 27 | backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity 28 | ) 29 | 30 | const ( 31 | fileNameInfo uintptr = 2 32 | fileTypePipe = 3 33 | ) 34 | 35 | var ( 36 | kernel32 = syscall.NewLazyDLL("kernel32.dll") 37 | procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") 38 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") 39 | procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") 40 | procGetFileType = kernel32.NewProc("GetFileType") 41 | stdoutFD = os.Stdout.Fd() 42 | ) 43 | 44 | // Check pipe name is used for cygwin/msys2 pty. 45 | // Cygwin/MSYS2 PTY has a name like: 46 | // \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master 47 | func isCygwinPipeName(name string) bool { 48 | token := strings.Split(name, "-") 49 | if len(token) < 5 { 50 | return false 51 | } 52 | 53 | if token[0] != `\msys` && token[0] != `\cygwin` { 54 | return false 55 | } 56 | 57 | if token[1] == "" { 58 | return false 59 | } 60 | 61 | if !strings.HasPrefix(token[2], "pty") { 62 | return false 63 | } 64 | 65 | if token[3] != `from` && token[3] != `to` { 66 | return false 67 | } 68 | 69 | if token[4] != "master" { 70 | return false 71 | } 72 | 73 | return true 74 | } 75 | 76 | // IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 77 | // terminal. 78 | func IsCygwinTerminal(fd uintptr) bool { 79 | if procGetFileInformationByHandleEx == nil { 80 | return false 81 | } 82 | 83 | // Cygwin/msys's pty is a pipe. 84 | ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) 85 | if ft != fileTypePipe || e != 0 { 86 | return false 87 | } 88 | 89 | var buf [2 + syscall.MAX_PATH]uint16 90 | r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 91 | 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), 92 | uintptr(len(buf)*2), 0, 0) 93 | if r == 0 || e != 0 { 94 | return false 95 | } 96 | 97 | l := *(*uint32)(unsafe.Pointer(&buf)) 98 | return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) 99 | } 100 | 101 | func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { 102 | ret, _, _ := procSetConsoleTextAttribute.Call( 103 | hConsoleOutput, 104 | uintptr(wAttributes)) 105 | return ret != 0 106 | } 107 | 108 | func setINFOColor() { 109 | if IsCygwinTerminal(stdoutFD) { 110 | setBoldGreen() 111 | } else { 112 | setConsoleTextAttribute(stdoutFD, foregroundGreen|foregroundIntensity) 113 | } 114 | } 115 | func unsetINFOColor() { 116 | if IsCygwinTerminal(stdoutFD) { 117 | unsetBoldGreen() 118 | } else { 119 | setConsoleTextAttribute(stdoutFD, reset) 120 | } 121 | } 122 | func setNoticeColor() { 123 | if IsCygwinTerminal(stdoutFD) { 124 | setYellow() 125 | } else { 126 | setConsoleTextAttribute(stdoutFD, foregroundYellow) 127 | } 128 | } 129 | func unsetNoticeColor() { 130 | if IsCygwinTerminal(stdoutFD) { 131 | unsetYellow() 132 | } else { 133 | setConsoleTextAttribute(stdoutFD, reset) 134 | } 135 | } 136 | func setErrorColor() { 137 | if IsCygwinTerminal(stdoutFD) { 138 | setBoldRed() 139 | } else { 140 | setConsoleTextAttribute(stdoutFD, foregroundRed|foregroundIntensity) 141 | } 142 | } 143 | func unsetErrorColor() { 144 | if IsCygwinTerminal(stdoutFD) { 145 | unsetBoldRed() 146 | } else { 147 | setConsoleTextAttribute(stdoutFD, reset) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /common/logger/log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | //"github.com/fatih/color" 10 | //"syscall" 11 | ) 12 | 13 | type colorConsoleWriter struct { 14 | prefix string 15 | postfix string 16 | w *os.File 17 | } 18 | 19 | func (writer *colorConsoleWriter) Write(p []byte) (n int, err error) { 20 | if len(writer.prefix) > 0 { 21 | fmt.Fprint(writer.w, writer.prefix) 22 | n, err = writer.w.Write(p) 23 | fmt.Fprint(writer.w, writer.postfix) 24 | return 25 | } 26 | return writer.w.Write(p) 27 | } 28 | 29 | type logFileWriter struct { 30 | path string 31 | file *os.File 32 | } 33 | 34 | func (writer *logFileWriter) close() { 35 | if nil != writer.file { 36 | writer.file.Close() 37 | } 38 | } 39 | 40 | func (writer *logFileWriter) Write(p []byte) (n int, err error) { 41 | if nil != writer.file { 42 | _, err := writer.file.Write(p) 43 | if nil != err { 44 | fmt.Printf("Failed to write logfile for reason:%v\n", err) 45 | } 46 | fi, err := writer.file.Stat() 47 | //5MB 48 | if nil == err && fi.Size() >= 1*1024*1024 { 49 | os.Remove(writer.path + ".1") 50 | writer.file.Close() 51 | os.Rename(writer.path, writer.path+".1") 52 | writer.file, _ = os.OpenFile(writer.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 53 | } 54 | } else { 55 | fmt.Printf("No log file inited for %s \n", writer.path) 56 | } 57 | return len(p), nil 58 | } 59 | 60 | func initLogWriter(path string) *logFileWriter { 61 | writer := new(logFileWriter) 62 | writer.path = path 63 | file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 64 | //fmt.Printf("file is %s\n", path) 65 | if err != nil { 66 | fmt.Println(err) 67 | } else { 68 | writer.file = file 69 | } 70 | return writer 71 | } 72 | 73 | func IsDebugEnable() bool { 74 | return true 75 | } 76 | 77 | func InitLogger(output []string) { 78 | withFile = false 79 | ws := make([]io.Writer, 0) 80 | for _, name := range output { 81 | if strings.EqualFold(name, "stdout") { 82 | ws = append(ws, os.Stdout) 83 | withFile = true 84 | } else if strings.EqualFold(name, "console") { 85 | ws = append(ws, os.Stdout) 86 | withFile = true 87 | } else if strings.EqualFold(name, "color") { 88 | //ws = append(ws, os.Stderr) 89 | withColorConsole = true 90 | } else { 91 | ws = append(ws, initLogWriter(name)) 92 | withFile = true 93 | } 94 | } 95 | if len(ws) > 0 { 96 | log.SetOutput(io.MultiWriter(ws...)) 97 | } 98 | } 99 | 100 | var withColorConsole bool 101 | var withFile bool 102 | var colorConsoleLogger *log.Logger 103 | 104 | func Debug(format string, v ...interface{}) { 105 | if withFile { 106 | log.Output(2, fmt.Sprintf(format, v...)) 107 | } 108 | if withColorConsole { 109 | colorConsoleLogger.Output(2, fmt.Sprintf(format, v...)) 110 | } 111 | 112 | } 113 | func Notice(format string, v ...interface{}) { 114 | if withFile { 115 | log.Output(2, fmt.Sprintf(format, v...)) 116 | } 117 | if withColorConsole { 118 | setNoticeColor() 119 | colorConsoleLogger.Output(2, fmt.Sprintf(format, v...)) 120 | unsetNoticeColor() 121 | } 122 | } 123 | 124 | func Info(format string, v ...interface{}) { 125 | if withFile { 126 | log.Output(2, fmt.Sprintf(format, v...)) 127 | } 128 | 129 | if withColorConsole { 130 | setINFOColor() 131 | colorConsoleLogger.Output(2, fmt.Sprintf(format, v...)) 132 | unsetINFOColor() 133 | } 134 | } 135 | 136 | func Error(format string, v ...interface{}) { 137 | if withFile { 138 | log.Output(2, fmt.Sprintf(format, v...)) 139 | } 140 | if withColorConsole { 141 | setErrorColor() 142 | colorConsoleLogger.Output(2, fmt.Sprintf(format, v...)) 143 | unsetErrorColor() 144 | } 145 | } 146 | 147 | func Fatal(format string, v ...interface{}) { 148 | if withFile { 149 | log.Output(2, fmt.Sprintf(format, v...)) 150 | } 151 | if withColorConsole { 152 | setErrorColor() 153 | colorConsoleLogger.Output(2, fmt.Sprintf(format, v...)) 154 | unsetErrorColor() 155 | } 156 | os.Exit(1) 157 | } 158 | 159 | func init() { 160 | logFlag := log.LstdFlags | log.Lshortfile 161 | log.SetFlags(logFlag) 162 | log.SetOutput(os.Stdout) 163 | withFile = true 164 | colorConsoleLogger = log.New(&colorConsoleWriter{w: os.Stdout}, "", logFlag) 165 | } 166 | -------------------------------------------------------------------------------- /common/mux/const.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import "errors" 4 | 5 | const ( 6 | DefaultMuxCipherMethod = "chacha20poly1305" 7 | DefaultMuxInitialCipherCounter = uint64(47816489) 8 | AuthOK = 1 9 | 10 | //GZipCompressor = "gzip" 11 | 12 | HTTPMuxSessionIDHeader = "X-Session-ID" 13 | HTTPMuxSessionACKIDHeader = "X-Session-ACK-ID" 14 | HTTPMuxPullPeriodHeader = "X-PullPeriod" 15 | ) 16 | 17 | var ( 18 | ErrToolargeMessage = errors.New("too large message length") 19 | ErrAuthFailed = errors.New("auth failed") 20 | ErrDataReadMissing = errors.New("auth failed") 21 | ) 22 | -------------------------------------------------------------------------------- /common/mux/http2.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/yinqiwen/gsnova/common/helper" 15 | "github.com/yinqiwen/gsnova/common/logger" 16 | "github.com/yinqiwen/pmux" 17 | "golang.org/x/net/http2" 18 | ) 19 | 20 | // type cachedClientConnPool struct { 21 | // conns sync.Map 22 | // } 23 | 24 | // func (c *cachedClientConnPool) GetClientConn(req *http.Request, addr string) (*http2.ClientConn, error) { 25 | // var conn *http2.ClientConn 26 | // c.conns.Range(func(key, value interface{}) bool { 27 | // conn = key.(*http2.ClientConn) 28 | // return false 29 | // }) 30 | // if nil != conn { 31 | // return conn, nil 32 | // } 33 | // return nil, fmt.Errorf("Existing connection closed") 34 | // } 35 | // func (c *cachedClientConnPool) MarkDead(conn *http2.ClientConn) { 36 | // c.conns.Delete(conn) 37 | // } 38 | 39 | // func (c *cachedClientConnPool) add(conn *http2.ClientConn) { 40 | // c.conns.Store(conn, true) 41 | // } 42 | 43 | // var cacheConns cachedClientConnPool 44 | var http2Transport = &http2.Transport{} 45 | 46 | type http2ClientMuxStream struct { 47 | w *io.PipeWriter 48 | r io.ReadCloser 49 | readReady chan struct{} 50 | closed bool 51 | } 52 | 53 | func (s *http2ClientMuxStream) setReader(rr io.ReadCloser) { 54 | s.r = rr 55 | helper.AsyncNotify(s.readReady) 56 | } 57 | 58 | func (s *http2ClientMuxStream) Read(b []byte) (n int, err error) { 59 | for s.r == nil && !s.closed { 60 | select { 61 | case <-s.readReady: 62 | case <-time.After(30 * time.Second): 63 | return 0, pmux.ErrTimeout 64 | } 65 | } 66 | if s.closed { 67 | return 0, io.EOF 68 | } 69 | n, err = s.r.Read(b) 70 | return 71 | } 72 | 73 | func (s *http2ClientMuxStream) Write(b []byte) (n int, err error) { 74 | if s.closed { 75 | return 0, io.EOF 76 | } 77 | n, err = s.w.Write(b) 78 | return 79 | } 80 | 81 | func (s *http2ClientMuxStream) Close() (err error) { 82 | s.closed = true 83 | s.w.Close() 84 | if nil != s.r { 85 | s.r.Close() 86 | } 87 | helper.AsyncNotify(s.readReady) 88 | return nil 89 | } 90 | 91 | type HTTP2MuxSession struct { 92 | streamCounter int64 93 | net.Conn 94 | h2Conn *http2.ClientConn 95 | tr *http2.Transport 96 | ServerHost string 97 | //Client *http.Client 98 | //Client *http2.Transport 99 | AcceptCh chan MuxStream 100 | closeCh chan struct{} 101 | streams sync.Map 102 | } 103 | 104 | func (s *HTTP2MuxSession) RemoteAddr() net.Addr { 105 | return nil 106 | } 107 | func (s *HTTP2MuxSession) LocalAddr() net.Addr { 108 | return nil 109 | } 110 | 111 | func (q *HTTP2MuxSession) CloseStream(stream MuxStream) error { 112 | q.streams.Delete(stream) 113 | atomic.AddInt64(&q.streamCounter, -1) 114 | return nil 115 | } 116 | 117 | func (q *HTTP2MuxSession) OfferStream(stream TimeoutReadWriteCloser) error { 118 | s := &ProxyMuxStream{TimeoutReadWriteCloser: stream, session: q} 119 | select { 120 | case q.AcceptCh <- s: 121 | return nil 122 | default: 123 | stream.Close() 124 | return fmt.Errorf("Can NOT accept new stream") 125 | } 126 | } 127 | 128 | func (q *HTTP2MuxSession) Ping() (time.Duration, error) { 129 | start := time.Now() 130 | if nil != q.h2Conn { 131 | q.h2Conn.Ping(context.Background()) 132 | return time.Now().Sub(start), nil 133 | } 134 | return 0, nil 135 | } 136 | 137 | func (q *HTTP2MuxSession) OpenStream() (MuxStream, error) { 138 | if nil == q.Conn { 139 | return nil, pmux.ErrSessionShutdown 140 | } 141 | pr, pw := io.Pipe() 142 | req := &http.Request{ 143 | Method: http.MethodPost, 144 | URL: &url.URL{Scheme: "https", Host: q.ServerHost}, 145 | Header: make(http.Header), 146 | Proto: "HTTP/2.0", 147 | ProtoMajor: 2, 148 | ProtoMinor: 0, 149 | Body: pr, 150 | Host: q.ServerHost, 151 | ContentLength: -1, 152 | } 153 | stream := &http2ClientMuxStream{ 154 | w: pw, 155 | readReady: make(chan struct{}), 156 | } 157 | go func() { 158 | //opt := http2.RoundTripOpt{OnlyCachedConn: true} 159 | res, err := q.h2Conn.RoundTrip(req) 160 | //res, err := q.Client.Do(req) 161 | if nil != err { 162 | logger.Error("Failed to post http/2 with error:%v", err) 163 | stream.Close() 164 | q.Close() 165 | } else { 166 | stream.setReader(res.Body) 167 | } 168 | }() 169 | muxStream := &ProxyMuxStream{TimeoutReadWriteCloser: &helper.TimeoutReadWriteCloser{ReadWriteCloser: stream}, session: q} 170 | atomic.AddInt64(&q.streamCounter, 1) 171 | q.streams.Store(muxStream, true) 172 | return muxStream, nil 173 | } 174 | 175 | func (q *HTTP2MuxSession) AcceptStream() (MuxStream, error) { 176 | select { 177 | case conn := <-q.AcceptCh: 178 | q.streams.Store(conn, true) 179 | return conn, nil 180 | case <-q.closeCh: 181 | return nil, pmux.ErrSessionShutdown 182 | } 183 | } 184 | 185 | func (q *HTTP2MuxSession) NumStreams() int { 186 | return int(q.streamCounter) 187 | } 188 | 189 | func (q *HTTP2MuxSession) Close() error { 190 | helper.AsyncNotify(q.closeCh) 191 | if nil != q.Conn { 192 | q.Conn.Close() 193 | q.Conn = nil 194 | } 195 | q.streams.Range(func(key, value interface{}) bool { 196 | stream := key.(MuxStream) 197 | stream.Close() 198 | return true 199 | }) 200 | http2Transport.CloseIdleConnections() 201 | //q.streams = sync.Map{} 202 | return nil 203 | } 204 | 205 | func NewHTTP2ServerMuxSession(conn net.Conn) *HTTP2MuxSession { 206 | s := &HTTP2MuxSession{} 207 | s.AcceptCh = make(chan MuxStream, 64) 208 | s.closeCh = make(chan struct{}) 209 | s.Conn = conn 210 | return s 211 | } 212 | 213 | func NewHTTP2ClientMuxSession(conn net.Conn, host string) (MuxSession, error) { 214 | s := &HTTP2MuxSession{} 215 | s.Conn = conn 216 | s.closeCh = make(chan struct{}) 217 | cc, err := http2Transport.NewClientConn(conn) 218 | if nil != err { 219 | return nil, err 220 | } 221 | s.h2Conn = cc 222 | //cacheConns.add(cc) 223 | //tr.ConnPool = &singleClientConnPool{conn: cc} 224 | //client := &http.Client{} 225 | //client.Transport = tr 226 | //s.Client = tr 227 | //s.Client = client 228 | s.ServerHost = host 229 | return s, nil 230 | } 231 | -------------------------------------------------------------------------------- /common/mux/io.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/golang/snappy" 9 | ) 10 | 11 | const ( 12 | SnappyCompressor = "snappy" 13 | NoneCompressor = "none" 14 | ) 15 | 16 | func GetCompressStreamReaderWriter(stream io.ReadWriteCloser, method string) (io.Reader, io.Writer) { 17 | switch method { 18 | case SnappyCompressor: 19 | return snappy.NewReader(stream), snappy.NewWriter(stream) 20 | case NoneCompressor: 21 | fallthrough 22 | default: 23 | return stream, stream 24 | } 25 | } 26 | 27 | func IsValidCompressor(method string) bool { 28 | switch method { 29 | case SnappyCompressor: 30 | case NoneCompressor: 31 | default: 32 | return false 33 | } 34 | return true 35 | } 36 | 37 | type MuxStreamConn struct { 38 | MuxStream 39 | } 40 | 41 | func (s *MuxStreamConn) LocalAddr() net.Addr { 42 | return nil 43 | } 44 | 45 | func (s *MuxStreamConn) RemoteAddr() net.Addr { 46 | return nil 47 | 48 | } 49 | func (s *MuxStreamConn) SetDeadline(t time.Time) error { 50 | s.MuxStream.SetReadDeadline(t) 51 | s.MuxStream.SetWriteDeadline(t) 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /common/mux/mux.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "io/ioutil" 8 | "math/rand" 9 | "net" 10 | "sync/atomic" 11 | "time" 12 | 13 | quic "github.com/lucas-clemente/quic-go" 14 | "github.com/vmihailenco/msgpack" 15 | "github.com/yinqiwen/gsnova/common/helper" 16 | "github.com/yinqiwen/pmux" 17 | ) 18 | 19 | var streamIDSeed int64 20 | 21 | type TimeoutReadWriteCloser interface { 22 | io.ReadWriteCloser 23 | SetReadDeadline(t time.Time) error 24 | SetWriteDeadline(t time.Time) error 25 | } 26 | type ConnectRequest struct { 27 | //ProxySID uint32 28 | Network string 29 | Addr string 30 | DialTimeout int 31 | ReadTimeout int 32 | Hops []string 33 | } 34 | 35 | type AuthRequest struct { 36 | Rand string 37 | User string 38 | CipherCounter uint64 39 | CipherMethod string 40 | CompressMethod string 41 | 42 | P2PToken string 43 | P2PConnID string 44 | P2PPriAddr string 45 | P2PPubAddr string 46 | } 47 | type AuthResponse struct { 48 | Code int 49 | PeerPriAddr string 50 | PeerPubAddr string 51 | PubAddr string 52 | } 53 | 54 | func (res *AuthResponse) Error() error { 55 | if AuthOK == res.Code { 56 | return nil 57 | } 58 | return ErrAuthFailed 59 | } 60 | 61 | func ReadConnectRequest(stream io.Reader) (*ConnectRequest, error) { 62 | var q ConnectRequest 63 | err := ReadMessage(stream, &q) 64 | return &q, err 65 | } 66 | 67 | func ReadAuthRequest(stream io.Reader) (*AuthRequest, error) { 68 | var q AuthRequest 69 | err := ReadMessage(stream, &q) 70 | return &q, err 71 | } 72 | 73 | func WriteMessage(stream io.Writer, req interface{}) error { 74 | buf := &bytes.Buffer{} 75 | buf.Write([]byte{0, 0, 0, 0}) 76 | enc := msgpack.NewEncoder(buf) 77 | err := enc.Encode(req) 78 | if nil != err { 79 | return err 80 | } 81 | binary.BigEndian.PutUint32(buf.Bytes(), uint32(buf.Len()-4)) 82 | _, err = stream.Write(buf.Bytes()) 83 | return err 84 | } 85 | 86 | func ReadMessage(stream io.Reader, res interface{}) error { 87 | lenbuf := make([]byte, 4) 88 | n, err := io.ReadAtLeast(stream, lenbuf, len(lenbuf)) 89 | length := uint32(0) 90 | if n == len(lenbuf) { 91 | length = binary.BigEndian.Uint32(lenbuf) 92 | if length > 1000000 { 93 | return ErrToolargeMessage 94 | } 95 | } else { 96 | return err 97 | } 98 | 99 | buf := make([]byte, length) 100 | n, err = io.ReadAtLeast(stream, buf, len(buf)) 101 | if n == len(buf) { 102 | dec := msgpack.NewDecoder(bytes.NewBuffer(buf)) 103 | return dec.Decode(res) 104 | } 105 | return err 106 | } 107 | 108 | type StreamOptions struct { 109 | DialTimeout int 110 | ReadTimeout int 111 | Hops []string 112 | } 113 | 114 | type SyncCloser interface { 115 | SyncClose() error 116 | } 117 | 118 | type MuxStream interface { 119 | io.ReadWriteCloser 120 | Connect(network string, addr string, opt StreamOptions) error 121 | Auth(req *AuthRequest) *AuthResponse 122 | StreamID() uint32 123 | SetReadDeadline(t time.Time) error 124 | SetWriteDeadline(t time.Time) error 125 | LatestIOTime() time.Time 126 | } 127 | 128 | type MuxSession interface { 129 | OpenStream() (MuxStream, error) 130 | CloseStream(stream MuxStream) error 131 | AcceptStream() (MuxStream, error) 132 | Ping() (time.Duration, error) 133 | NumStreams() int 134 | Close() error 135 | RemoteAddr() net.Addr 136 | LocalAddr() net.Addr 137 | } 138 | 139 | type ProxyMuxStream struct { 140 | TimeoutReadWriteCloser 141 | session MuxSession 142 | sessionID int64 143 | latestIOTime time.Time 144 | } 145 | 146 | func (s *ProxyMuxStream) OnIO(read bool) { 147 | s.latestIOTime = time.Now() 148 | } 149 | func (s *ProxyMuxStream) ReadFrom(r io.Reader) (n int64, err error) { 150 | if readFrom, ok := s.TimeoutReadWriteCloser.(io.ReaderFrom); ok { 151 | return readFrom.ReadFrom(r) 152 | } 153 | var nn int 154 | buf := make([]byte, 8192) 155 | for { 156 | nn, err = r.Read(buf) 157 | if nn > 0 { 158 | n += int64(nn) 159 | _, werr := s.Write(buf[0:nn]) 160 | if nil != werr { 161 | return n, werr 162 | } 163 | } 164 | if nil != err { 165 | return n, err 166 | } 167 | } 168 | } 169 | 170 | func (s *ProxyMuxStream) WriteTo(w io.Writer) (n int64, err error) { 171 | if writerTo, ok := s.TimeoutReadWriteCloser.(io.WriterTo); ok { 172 | return writerTo.WriteTo(w) 173 | } 174 | var nn int 175 | buf := make([]byte, 8192) 176 | for { 177 | nn, err = s.Read(buf) 178 | if nn > 0 { 179 | n += int64(nn) 180 | _, werr := w.Write(buf[0:nn]) 181 | if nil != werr { 182 | return n, werr 183 | } 184 | } 185 | if nil != err { 186 | return n, err 187 | } 188 | } 189 | } 190 | 191 | func (s *ProxyMuxStream) Read(p []byte) (int, error) { 192 | s.latestIOTime = time.Now() 193 | return s.TimeoutReadWriteCloser.Read(p) 194 | } 195 | func (s *ProxyMuxStream) Write(p []byte) (int, error) { 196 | s.latestIOTime = time.Now() 197 | return s.TimeoutReadWriteCloser.Write(p) 198 | } 199 | func (s *ProxyMuxStream) LatestIOTime() time.Time { 200 | return s.latestIOTime 201 | } 202 | 203 | func (s *ProxyMuxStream) StreamID() uint32 { 204 | if ps, ok := s.TimeoutReadWriteCloser.(*pmux.Stream); ok { 205 | return ps.ID() 206 | } else if qs, ok := s.TimeoutReadWriteCloser.(quic.Stream); ok { 207 | return uint32(qs.StreamID()) 208 | } 209 | if 0 == s.sessionID { 210 | s.sessionID = atomic.AddInt64(&streamIDSeed, 1) 211 | } 212 | return uint32(s.sessionID) 213 | } 214 | 215 | func (s *ProxyMuxStream) Close() error { 216 | if nil != s.session { 217 | s.session.CloseStream(s) 218 | } 219 | return s.TimeoutReadWriteCloser.Close() 220 | } 221 | 222 | func (s *ProxyMuxStream) Connect(network string, addr string, opt StreamOptions) error { 223 | req := &ConnectRequest{ 224 | Network: network, 225 | Addr: addr, 226 | DialTimeout: opt.DialTimeout, 227 | ReadTimeout: opt.ReadTimeout, 228 | Hops: opt.Hops, 229 | } 230 | return WriteMessage(s, req) 231 | } 232 | func (s *ProxyMuxStream) Auth(req *AuthRequest) *AuthResponse { 233 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 234 | req.Rand = helper.RandAsciiString(int(r.Int31n(128))) 235 | err := WriteMessage(s, req) 236 | res := &AuthResponse{Code: -1} 237 | if nil != err { 238 | return res 239 | } 240 | 241 | err = ReadMessage(s, res) 242 | if nil != err { 243 | return res 244 | } 245 | if nil == err { 246 | //wait remote close 247 | ioutil.ReadAll(s) 248 | } 249 | return res 250 | } 251 | 252 | type ConnAddr interface { 253 | LocalAddr() net.Addr 254 | RemoteAddr() net.Addr 255 | } 256 | 257 | type ProxyMuxSession struct { 258 | *pmux.Session 259 | NetConn ConnAddr 260 | } 261 | 262 | func (s *ProxyMuxSession) CloseStream(stream MuxStream) error { 263 | return nil 264 | } 265 | 266 | func (s *ProxyMuxSession) OpenStream() (MuxStream, error) { 267 | ss, err := s.Session.OpenStream() 268 | if nil != err { 269 | return nil, err 270 | } 271 | stream := &ProxyMuxStream{TimeoutReadWriteCloser: ss} 272 | ss.IOCallback = stream 273 | return stream, nil 274 | } 275 | 276 | func (s *ProxyMuxSession) AcceptStream() (MuxStream, error) { 277 | ss, err := s.Session.AcceptStream() 278 | if nil != err { 279 | return nil, err 280 | } 281 | stream := &ProxyMuxStream{TimeoutReadWriteCloser: ss} 282 | ss.IOCallback = stream 283 | return stream, nil 284 | } 285 | 286 | func (s *ProxyMuxSession) RemoteAddr() net.Addr { 287 | if nil != s.NetConn { 288 | return s.NetConn.RemoteAddr() 289 | } 290 | return nil 291 | } 292 | func (s *ProxyMuxSession) LocalAddr() net.Addr { 293 | if nil != s.NetConn { 294 | return s.NetConn.LocalAddr() 295 | } 296 | return nil 297 | } 298 | 299 | func init() { 300 | //msgpack.RegisterExt(1, (*AuthRequest)(nil)) 301 | // msgpack.RegisterExt(2, (*AuthResponse)(nil)) 302 | // msgpack.RegisterExt(3, (*ConnectRequest)(nil)) 303 | //msgpack.RegisterExt(1, (*B)(nil)) 304 | } 305 | -------------------------------------------------------------------------------- /common/mux/mux_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | type A struct { 10 | VV int 11 | } 12 | 13 | func TestXYZ(t *testing.T) { 14 | var buffer bytes.Buffer 15 | req := &AuthRequest{} 16 | req.CipherCounter = 121 17 | req.CipherMethod = "asasas" 18 | req.User = "asdasdas" 19 | //r := rand.New(rand.NewSource(time.Now().UnixNano())) 20 | //req.Rand = []byte(helper.RandAsciiString(int(r.Int31n(128)))) 21 | WriteMessage(&buffer, req) 22 | 23 | log.Printf("#### %d", buffer.Len()) 24 | 25 | zz := &AuthRequest{} 26 | err := ReadMessage(&buffer, zz) 27 | log.Printf("#### %v %v", zz, err) 28 | 29 | // req := &A{} 30 | // req.VV = 121 31 | // WriteMessage(&buffer, req) 32 | 33 | // log.Printf("#### %d", buffer.Len()) 34 | 35 | // zz := &A{} 36 | // err := ReadMessage(&buffer, zz) 37 | // log.Printf("#### %v %v", zz, err) 38 | } 39 | -------------------------------------------------------------------------------- /common/mux/quic.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | "time" 7 | 8 | quic "github.com/lucas-clemente/quic-go" 9 | ) 10 | 11 | type QUICMuxSession struct { 12 | streamCounter int64 13 | quic.Session 14 | } 15 | 16 | func (q *QUICMuxSession) Ping() (time.Duration, error) { 17 | return 0, nil 18 | } 19 | 20 | func (q *QUICMuxSession) CloseStream(stream MuxStream) error { 21 | atomic.AddInt64(&q.streamCounter, -1) 22 | return nil 23 | } 24 | 25 | func (q *QUICMuxSession) OpenStream() (MuxStream, error) { 26 | s, err := q.OpenStreamSync() 27 | if nil != err { 28 | return nil, err 29 | } 30 | atomic.AddInt64(&q.streamCounter, 1) 31 | return &ProxyMuxStream{TimeoutReadWriteCloser: s, session: q}, nil 32 | } 33 | 34 | func (q *QUICMuxSession) AcceptStream() (MuxStream, error) { 35 | s, err := q.Session.AcceptStream() 36 | if nil != err { 37 | return nil, err 38 | } 39 | return &ProxyMuxStream{TimeoutReadWriteCloser: s}, nil 40 | } 41 | 42 | func (q *QUICMuxSession) NumStreams() int { 43 | return int(q.streamCounter) 44 | } 45 | 46 | func (q *QUICMuxSession) Close() error { 47 | q.streamCounter = 0 48 | return q.Session.Close() 49 | } 50 | func (s *QUICMuxSession) RemoteAddr() net.Addr { 51 | return nil 52 | } 53 | func (s *QUICMuxSession) LocalAddr() net.Addr { 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /common/mux/ws.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/gorilla/websocket" 8 | "github.com/yinqiwen/gsnova/common/logger" 9 | ) 10 | 11 | type WsConn struct { 12 | *websocket.Conn 13 | msgReader io.Reader 14 | } 15 | 16 | func (ws *WsConn) writeBuffers(bufs *net.Buffers) (n int64, err error) { 17 | w, err := ws.NextWriter(websocket.BinaryMessage) 18 | if nil != err { 19 | return 0, err 20 | } 21 | defer w.Close() 22 | for _, b := range *bufs { 23 | nn, err := w.Write(b) 24 | if nil != err { 25 | return n, err 26 | } 27 | n += int64(nn) 28 | } 29 | 30 | return n, nil 31 | } 32 | 33 | func (ws *WsConn) Write(p []byte) (int, error) { 34 | c := ws.Conn 35 | if nil == c { 36 | return 0, io.EOF 37 | } 38 | err := c.WriteMessage(websocket.BinaryMessage, p) 39 | if nil != err { 40 | logger.Error("Failed to write websocket binary messgage:%v", err) 41 | return 0, err 42 | } 43 | return len(p), nil 44 | } 45 | 46 | func (ws *WsConn) readData(p []byte) (int, error) { 47 | if nil == ws.msgReader { 48 | return 0, io.EOF 49 | } 50 | n, err := ws.msgReader.Read(p) 51 | if nil != err { 52 | ws.msgReader = nil 53 | } 54 | return n, nil 55 | } 56 | 57 | func (ws *WsConn) Read(p []byte) (int, error) { 58 | if nil != ws.msgReader { 59 | return ws.readData(p) 60 | } 61 | if nil == ws.Conn { 62 | return 0, io.EOF 63 | } 64 | mt, reader, err := ws.Conn.NextReader() 65 | //mt, data, err := c.ReadMessage() 66 | if err != nil { 67 | return 0, err 68 | } 69 | switch mt { 70 | case websocket.BinaryMessage: 71 | ws.msgReader = reader 72 | return ws.readData(p) 73 | default: 74 | logger.Error("Invalid websocket message type:%d", mt) 75 | return 0, io.EOF 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /common/netx/netx.go: -------------------------------------------------------------------------------- 1 | // Package netx provides additional libraries that extend some of the behaviors 2 | // in the net standard package. 3 | package netx 4 | 5 | import ( 6 | "context" 7 | "net" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | var ( 13 | dial atomic.Value 14 | resolveTCPAddr atomic.Value 15 | listenUDP atomic.Value 16 | dialUDP atomic.Value 17 | 18 | defaultDialTimeout = 1 * time.Minute 19 | ) 20 | 21 | func init() { 22 | Reset() 23 | } 24 | 25 | // Dial is like DialTimeout using a default timeout of 1 minute. 26 | func Dial(net string, addr string) (net.Conn, error) { 27 | return DialTimeout(net, addr, defaultDialTimeout) 28 | } 29 | 30 | // DialTimeout dials the given addr on the given net type using the configured 31 | // dial function, timing out after the given timeout. 32 | func DialTimeout(network string, addr string, timeout time.Duration) (net.Conn, error) { 33 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 34 | conn, err := DialContext(ctx, network, addr) 35 | cancel() 36 | return conn, err 37 | } 38 | 39 | func ListenUDP(network string, laddr *net.UDPAddr) (net.PacketConn, error) { 40 | return listenUDP.Load().(func(network string, laddr *net.UDPAddr) (net.PacketConn, error))(network, laddr) 41 | } 42 | 43 | func DialUDP(network string, laddr, raddr *net.UDPAddr) (net.PacketConn, error) { 44 | return dialUDP.Load().(func(network string, laddr, raddr *net.UDPAddr) (net.PacketConn, error))(network, laddr, raddr) 45 | } 46 | 47 | // DialContext dials the given addr on the given net type using the configured 48 | // dial function, with the given context. 49 | func DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { 50 | return dial.Load().(func(context.Context, string, string) (net.Conn, error))(ctx, network, addr) 51 | } 52 | 53 | // OverrideDial overrides the global dial function. 54 | func OverrideDial(dialFN func(ctx context.Context, net string, addr string) (net.Conn, error)) { 55 | dial.Store(dialFN) 56 | } 57 | 58 | // Resolve resolves the given tcp address using the configured resolve function. 59 | func Resolve(network string, addr string) (*net.TCPAddr, error) { 60 | return resolveTCPAddr.Load().(func(string, string) (*net.TCPAddr, error))(network, addr) 61 | } 62 | 63 | // OverrideResolve overrides the global resolve function. 64 | func OverrideResolve(resolveFN func(net string, addr string) (*net.TCPAddr, error)) { 65 | resolveTCPAddr.Store(resolveFN) 66 | } 67 | 68 | func OverrideListenUDP(listen func(network string, laddr *net.UDPAddr) (net.PacketConn, error)) { 69 | listenUDP.Store(listen) 70 | } 71 | func OverrideDialUDP(dial func(nnetwork string, laddr, raddr *net.UDPAddr) (net.PacketConn, error)) { 72 | dialUDP.Store(dial) 73 | } 74 | 75 | // Reset resets netx to its default settings 76 | func Reset() { 77 | var d net.Dialer 78 | OverrideDial(d.DialContext) 79 | OverrideResolve(net.ResolveTCPAddr) 80 | OverrideListenUDP(func(network string, laddr *net.UDPAddr) (net.PacketConn, error) { 81 | return net.ListenUDP(network, laddr) 82 | }) 83 | OverrideDialUDP(func(network string, laddr, raddr *net.UDPAddr) (net.PacketConn, error) { 84 | return net.DialUDP(network, laddr, raddr) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /common/protector/config.go: -------------------------------------------------------------------------------- 1 | package protector 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | defaultDnsServer = "8.8.4.4" 9 | connectTimeOut = 15 * time.Second 10 | readDeadline = 15 * time.Second 11 | writeDeadline = 15 * time.Second 12 | socketError = -1 13 | dnsPort = 53 14 | ) 15 | 16 | type NetOptions struct { 17 | ReusePort bool 18 | LocalAddr string 19 | DialTimeout time.Duration 20 | } 21 | 22 | type Protect func(fileDescriptor int) error 23 | 24 | var ( 25 | currentProtect Protect 26 | currentDnsServer string 27 | ) 28 | 29 | func Configure(protect Protect, dnsServer string) { 30 | currentProtect = protect 31 | if dnsServer != "" { 32 | currentDnsServer = dnsServer 33 | } else { 34 | dnsServer = defaultDnsServer 35 | } 36 | } 37 | 38 | func SetDNSServer(server string) { 39 | if len(currentDnsServer) > 0 { 40 | currentDnsServer = server 41 | } 42 | } 43 | 44 | func init() { 45 | currentProtect = func(fd int) error { 46 | return nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/protector/protected_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package protector 4 | 5 | import ( 6 | "context" 7 | "net" 8 | ) 9 | 10 | func SupportReusePort() bool { 11 | return true 12 | } 13 | 14 | func ListenTCP(laddr *net.TCPAddr, options *NetOptions) (net.Listener, error) { 15 | return net.ListenTCP("tcp", laddr) 16 | } 17 | 18 | func DialContextOptions(ctx context.Context, network, addr string, opt *NetOptions) (net.Conn, error) { 19 | dialer := &net.Dialer{} 20 | if nil != opt { 21 | dialer.LocalAddr, _ = net.ResolveTCPAddr(network, opt.LocalAddr) 22 | dialer.Timeout = opt.DialTimeout 23 | } 24 | return dialer.Dial(network, addr) 25 | } 26 | -------------------------------------------------------------------------------- /common/protector/resolver.go: -------------------------------------------------------------------------------- 1 | package protector 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "log" 7 | "math/big" 8 | "net" 9 | "time" 10 | 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | type DNSRecord struct { 15 | IP net.IP 16 | ExpireAt time.Time 17 | } 18 | 19 | type DnsResponse struct { 20 | records []DNSRecord 21 | } 22 | 23 | // PickRandomIP picks a random IP address from a DNS response 24 | func (response *DnsResponse) PickRandomIP() (net.IP, error) { 25 | length := int64(len(response.records)) 26 | if length < 1 { 27 | return nil, errors.New("no IP address") 28 | } 29 | 30 | index, err := rand.Int(rand.Reader, big.NewInt(length)) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | record := response.records[index.Int64()] 36 | return record.IP, nil 37 | } 38 | 39 | func (response *DnsResponse) PickRecord() (*DNSRecord, error) { 40 | length := int64(len(response.records)) 41 | if length < 1 { 42 | return nil, errors.New("no IP address") 43 | } 44 | return &response.records[0], nil 45 | } 46 | 47 | // dnsLookup is used whenever we need to conduct a DNS query over a given TCP connection 48 | func DnsLookup(addr string, conn net.Conn) (*DnsResponse, error) { 49 | //log.Printf("Doing a DNS lookup on %s", addr) 50 | 51 | dnsResponse := &DnsResponse{ 52 | records: make([]DNSRecord, 0), 53 | } 54 | 55 | // create the connection to the DNS server 56 | dnsConn := &dns.Conn{Conn: conn} 57 | defer dnsConn.Close() 58 | 59 | m := new(dns.Msg) 60 | m.Id = dns.Id() 61 | // set the question section in the dns query 62 | // Fqdn returns the fully qualified domain name 63 | m.SetQuestion(dns.Fqdn(addr), dns.TypeA) 64 | m.RecursionDesired = true 65 | 66 | dnsConn.WriteMsg(m) 67 | 68 | response, err := dnsConn.ReadMsg() 69 | if err != nil { 70 | log.Printf("Could not process DNS response: %v", err) 71 | return nil, err 72 | } 73 | now := time.Now() 74 | // iterate over RRs containing the DNS answer 75 | for _, answer := range response.Answer { 76 | if a, ok := answer.(*dns.A); ok { 77 | // append the result to our list of records 78 | // the A records in the RDATA section of the DNS answer 79 | // contains the actual IP address 80 | dnsResponse.records = append(dnsResponse.records, 81 | DNSRecord{ 82 | IP: a.A, 83 | ExpireAt: now.Add(time.Duration(a.Hdr.Ttl) * time.Second), 84 | }) 85 | //log.Printf("###TTL:%d", a.Hdr.Ttl) 86 | } 87 | } 88 | return dnsResponse, nil 89 | } 90 | -------------------------------------------------------------------------------- /common/socks/args.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // Key–value mappings for the representation of client and server options. 11 | 12 | // Args maps a string key to a list of values. It is similar to url.Values. 13 | type Args map[string][]string 14 | 15 | // Get the first value associated with the given key. If there are any values 16 | // associated with the key, the value return has the value and ok is set to 17 | // true. If there are no values for the given key, value is "" and ok is false. 18 | // If you need access to multiple values, use the map directly. 19 | func (args Args) Get(key string) (value string, ok bool) { 20 | if args == nil { 21 | return "", false 22 | } 23 | vals, ok := args[key] 24 | if !ok || len(vals) == 0 { 25 | return "", false 26 | } 27 | return vals[0], true 28 | } 29 | 30 | // Append value to the list of values for key. 31 | func (args Args) Add(key, value string) { 32 | args[key] = append(args[key], value) 33 | } 34 | 35 | // Return the index of the next unescaped byte in s that is in the term set, or 36 | // else the length of the string if no terminators appear. Additionally return 37 | // the unescaped string up to the returned index. 38 | func indexUnescaped(s string, term []byte) (int, string, error) { 39 | var i int 40 | unesc := make([]byte, 0) 41 | for i = 0; i < len(s); i++ { 42 | b := s[i] 43 | // A terminator byte? 44 | if bytes.IndexByte(term, b) != -1 { 45 | break 46 | } 47 | if b == '\\' { 48 | i++ 49 | if i >= len(s) { 50 | return 0, "", fmt.Errorf("nothing following final escape in %q", s) 51 | } 52 | b = s[i] 53 | } 54 | unesc = append(unesc, b) 55 | } 56 | return i, string(unesc), nil 57 | } 58 | 59 | // Parse a name–value mapping as from an encoded SOCKS username/password. 60 | // 61 | // "If any [k=v] items are provided, they are configuration parameters for the 62 | // proxy: Tor should separate them with semicolons ... If a key or value value 63 | // must contain [an equals sign or] a semicolon or a backslash, it is escaped 64 | // with a backslash." 65 | func parseClientParameters(s string) (args Args, err error) { 66 | args = make(Args) 67 | if len(s) == 0 { 68 | return 69 | } 70 | i := 0 71 | for { 72 | var key, value string 73 | var offset, begin int 74 | 75 | begin = i 76 | // Read the key. 77 | offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) 78 | if err != nil { 79 | return 80 | } 81 | i += offset 82 | // End of string or no equals sign? 83 | if i >= len(s) || s[i] != '=' { 84 | err = fmt.Errorf("no equals sign in %q", s[begin:i]) 85 | return 86 | } 87 | // Skip the equals sign. 88 | i++ 89 | // Read the value. 90 | offset, value, err = indexUnescaped(s[i:], []byte{';'}) 91 | if err != nil { 92 | return 93 | } 94 | i += offset 95 | if len(key) == 0 { 96 | err = fmt.Errorf("empty key in %q", s[begin:i]) 97 | return 98 | } 99 | args.Add(key, value) 100 | if i >= len(s) { 101 | break 102 | } 103 | // Skip the semicolon. 104 | i++ 105 | } 106 | return args, nil 107 | } 108 | 109 | // Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. 110 | // 111 | // " is a k=v string value with options that are to be passed to the 112 | // transport. Colons, semicolons, equal signs and backslashes must be escaped 113 | // with a backslash." 114 | // Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes 115 | func parseServerTransportOptions(s string) (opts map[string]Args, err error) { 116 | opts = make(map[string]Args) 117 | if len(s) == 0 { 118 | return 119 | } 120 | i := 0 121 | for { 122 | var methodName, key, value string 123 | var offset, begin int 124 | 125 | begin = i 126 | // Read the method name. 127 | offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) 128 | if err != nil { 129 | return 130 | } 131 | i += offset 132 | // End of string or no colon? 133 | if i >= len(s) || s[i] != ':' { 134 | err = fmt.Errorf("no colon in %q", s[begin:i]) 135 | return 136 | } 137 | // Skip the colon. 138 | i++ 139 | // Read the key. 140 | offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) 141 | if err != nil { 142 | return 143 | } 144 | i += offset 145 | // End of string or no equals sign? 146 | if i >= len(s) || s[i] != '=' { 147 | err = fmt.Errorf("no equals sign in %q", s[begin:i]) 148 | return 149 | } 150 | // Skip the equals sign. 151 | i++ 152 | // Read the value. 153 | offset, value, err = indexUnescaped(s[i:], []byte{';'}) 154 | if err != nil { 155 | return 156 | } 157 | i += offset 158 | if len(methodName) == 0 { 159 | err = fmt.Errorf("empty method name in %q", s[begin:i]) 160 | return 161 | } 162 | if len(key) == 0 { 163 | err = fmt.Errorf("empty key in %q", s[begin:i]) 164 | return 165 | } 166 | if opts[methodName] == nil { 167 | opts[methodName] = make(Args) 168 | } 169 | opts[methodName].Add(key, value) 170 | if i >= len(s) { 171 | break 172 | } 173 | // Skip the semicolon. 174 | i++ 175 | } 176 | return opts, nil 177 | } 178 | 179 | // Escape backslashes and all the bytes that are in set. 180 | func backslashEscape(s string, set []byte) string { 181 | var buf bytes.Buffer 182 | for _, b := range []byte(s) { 183 | if b == '\\' || bytes.IndexByte(set, b) != -1 { 184 | buf.WriteByte('\\') 185 | } 186 | buf.WriteByte(b) 187 | } 188 | return buf.String() 189 | } 190 | 191 | // Encode a name–value mapping so that it is suitable to go in the ARGS option 192 | // of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not 193 | // added. 194 | // 195 | // "Equal signs and commas [and backslashes] must be escaped with a backslash." 196 | func encodeSmethodArgs(args Args) string { 197 | if args == nil { 198 | return "" 199 | } 200 | 201 | keys := make([]string, 0, len(args)) 202 | for key := range args { 203 | keys = append(keys, key) 204 | } 205 | sort.Strings(keys) 206 | 207 | escape := func(s string) string { 208 | return backslashEscape(s, []byte{'=', ','}) 209 | } 210 | 211 | var pairs []string 212 | for _, key := range keys { 213 | for _, value := range args[key] { 214 | pairs = append(pairs, escape(key)+"="+escape(value)) 215 | } 216 | } 217 | 218 | return strings.Join(pairs, ",") 219 | } 220 | -------------------------------------------------------------------------------- /hosts.json: -------------------------------------------------------------------------------- 1 | { 2 | //this is just a example, do not use the ip in your env 3 | 4 | //"sni_proxy":["10.10.10.10", "11.11.11.11"], 5 | // "cn_sni_proxy" :["10.10.10.10", "11.11.11.11"], 6 | // "google_https":["sni_proxy"], 7 | // "*.appspot.com":["sni_proxy"], 8 | // "*.google.com":["google_https"], 9 | // "*.googlevideo.com":["google_https"], 10 | // "*.gstatic.com":["google_https"], 11 | // "*.googleusercontent.com":["google_https"], 12 | // "*.ytimg.com":["google_https"], 13 | // "*.ggpht.com":["google_https"], 14 | // "*.blogspot.com":["google_https"], 15 | // "*.blogger.com":["google_https"], 16 | // "*.blogblog.com":["google_https"], 17 | // "*.google.co.*":["google_https"], 18 | // "*.googleapis.com":["google_https"], 19 | // "*.youtube-nocookie.com":["google_https"], 20 | // "*.google-analytics.com":["google_https"], 21 | // "golang.org":["google_https"], 22 | // "*.golang.org":["google_https"], 23 | // "android.com":["google_https"], 24 | // "*.android.com":["google_https"], 25 | // "*.googlecode.com":["google_https"], 26 | // "*.doubleclick.net":["google_https"], 27 | // "*.youtube.com":["google_https"] 28 | } -------------------------------------------------------------------------------- /local/admin.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "net/http" 11 | //_ "net/http/pprof" 12 | "os" 13 | "time" 14 | 15 | "github.com/yinqiwen/gotoolkit/iotools" 16 | "github.com/yinqiwen/gotoolkit/ots" 17 | "github.com/yinqiwen/gsnova/common/channel" 18 | "github.com/yinqiwen/gsnova/common/helper" 19 | "github.com/yinqiwen/gsnova/common/logger" 20 | "github.com/yinqiwen/gsnova/common/netx" 21 | ) 22 | 23 | var httpDumpLog *iotools.RotateFile 24 | 25 | func getConfigList(w http.ResponseWriter, r *http.Request) { 26 | var confs []string 27 | files, _ := ioutil.ReadDir(GConf.Admin.ConfigDir) 28 | for _, f := range files { 29 | if f.IsDir() { 30 | confs = append(confs, f.Name()) 31 | } 32 | } 33 | w.Header().Set("Content-Type", "application/json") 34 | js, _ := json.Marshal(confs) 35 | w.Write(js) 36 | } 37 | 38 | func statCallback(w http.ResponseWriter, r *http.Request) { 39 | w.WriteHeader(200) 40 | fmt.Fprintf(w, "Version: %s\n", channel.Version) 41 | //fmt.Fprintf(w, "NumSession: %d\n", getProxySessionSize()) 42 | ots.Handle("stat", w) 43 | fmt.Fprintf(w, "RunningProxyStreamNum: %d\n", runningProxyStreamCount) 44 | channel.DumpLoaclChannelStat(w) 45 | } 46 | func stackdumpCallback(w http.ResponseWriter, req *http.Request) { 47 | w.WriteHeader(200) 48 | ots.Handle("stackdump", w) 49 | } 50 | func memdumpCallback(w http.ResponseWriter, req *http.Request) { 51 | w.WriteHeader(200) 52 | ots.Handle("MemProfile", w) 53 | } 54 | func gcCallback(w http.ResponseWriter, req *http.Request) { 55 | w.WriteHeader(200) 56 | ots.Handle("gc", w) 57 | } 58 | 59 | func httpDumpCallback(w http.ResponseWriter, r *http.Request) { 60 | w.WriteHeader(200) 61 | if nil != httpDumpLog { 62 | io.Copy(httpDumpLog, r.Body) 63 | } else { 64 | r.Body.Close() 65 | } 66 | } 67 | 68 | func startAdminServer() { 69 | if len(GConf.Admin.Listen) == 0 { 70 | return 71 | } 72 | if len(GConf.Admin.ConfigDir) == 0 { 73 | logger.Error("[WARN]The ConfigDir's Dir is empty, use current dir instead") 74 | GConf.Admin.ConfigDir = "./" 75 | } 76 | if len(GConf.Admin.BroadcastAddr) > 0 { 77 | ticker := time.NewTicker(3 * time.Second) 78 | localIP := helper.GetLocalIPv4() 79 | _, adminPort, _ := net.SplitHostPort(GConf.Admin.Listen) 80 | go func() { 81 | for _ = range ticker.C { 82 | addr, err := net.ResolveUDPAddr("udp", GConf.Admin.BroadcastAddr) 83 | var c *net.UDPConn 84 | if nil == err { 85 | c, err = net.DialUDP("udp", nil, addr) 86 | } 87 | if err != nil { 88 | logger.Error("Failed to resolve multicast addr.") 89 | } else { 90 | for _, ip := range localIP { 91 | c.Write([]byte(net.JoinHostPort(ip, adminPort))) 92 | } 93 | } 94 | } 95 | }() 96 | } 97 | httpDumpLog = &iotools.RotateFile{ 98 | Path: proxyHome + "httpdump.log", 99 | MaxBackupIndex: 2, 100 | MaxFileSize: 1024 * 1024, 101 | SyncBytesPeriod: 1024 * 1024, 102 | } 103 | 104 | mux := http.NewServeMux() 105 | fs := http.FileServer(http.Dir(GConf.Admin.ConfigDir)) 106 | mux.Handle("/", fs) 107 | mux.HandleFunc("/_conflist", getConfigList) 108 | mux.HandleFunc("/stat", statCallback) 109 | mux.HandleFunc("/stackdump", stackdumpCallback) 110 | mux.HandleFunc("/gc", gcCallback) 111 | mux.HandleFunc("/memdump", memdumpCallback) 112 | mux.HandleFunc("/httpdump", httpDumpCallback) 113 | err := http.ListenAndServe(GConf.Admin.Listen, mux) 114 | if nil != err { 115 | logger.Error("Failed to start config store server:%v", err) 116 | } 117 | } 118 | 119 | var syncClient *http.Client 120 | 121 | func syncConfigFile(addr string, localDir, remoteDir string, fileName string) (bool, error) { 122 | filePath := remoteDir + "/" + fileName 123 | localFilePath := localDir + "/" + filePath 124 | localInfo, err := os.Stat(localFilePath) 125 | fileURL := "http://" + addr + "/" + filePath 126 | if err == nil { 127 | localFileModTime := localInfo.ModTime() 128 | headResp, err := syncClient.Head(fileURL) 129 | if nil != err { 130 | return false, err 131 | } 132 | lastModifiedHeader, _ := http.ParseTime(headResp.Header.Get("Last-Modified")) 133 | if lastModifiedHeader.Before(localFileModTime) { 134 | //log.Printf("Config file:%v is not update.", localFilePath) 135 | return false, nil 136 | } 137 | } 138 | resp, err := syncClient.Get(fileURL) 139 | if nil != err { 140 | return false, err 141 | } 142 | if resp.StatusCode != 200 || nil == resp.Body { 143 | if nil != resp.Body { 144 | resp.Body.Close() 145 | } 146 | if resp.StatusCode == 404 { 147 | return false, nil 148 | } 149 | return false, fmt.Errorf("Invalid response:%v for %s", resp, filePath) 150 | } 151 | 152 | data, _ := ioutil.ReadAll(resp.Body) 153 | resp.Body.Close() 154 | os.Mkdir(localDir+"/"+remoteDir, 0775) 155 | ioutil.WriteFile(localFilePath, data, 0660) 156 | log.Printf("Persistent config:%s.", localDir+"/"+filePath) 157 | return true, nil 158 | } 159 | 160 | func SyncConfig(addr string, localDir string) (bool, error) { 161 | if nil == syncClient { 162 | tr := &http.Transport{} 163 | tr.ResponseHeaderTimeout = 15 * time.Second 164 | tr.Dial = netx.Dial 165 | syncClient = &http.Client{} 166 | syncClient.Timeout = 15 * time.Second 167 | syncClient.Transport = tr 168 | } 169 | resp, err := syncClient.Get("http://" + addr + "/_conflist") 170 | if nil != err { 171 | log.Printf("Error %v with local ip:%v", err, helper.GetLocalIPv4()) 172 | return false, err 173 | } 174 | data, _ := ioutil.ReadAll(resp.Body) 175 | var confList []string 176 | json.Unmarshal(data, &confList) 177 | 178 | update := false 179 | for _, conf := range confList { 180 | u := false 181 | files := []string{"client.json", "hosts.json", "cnipset.txt"} 182 | for _, file := range files { 183 | u, err = syncConfigFile(addr, localDir, conf, file) 184 | if nil != err { 185 | return false, err 186 | } 187 | if u { 188 | update = true 189 | } 190 | if u { 191 | log.Printf("Synced config:%s success", conf) 192 | } 193 | } 194 | } 195 | return update, nil 196 | } 197 | -------------------------------------------------------------------------------- /local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/yinqiwen/gsnova/common/channel" 10 | "github.com/yinqiwen/gsnova/common/dns" 11 | "github.com/yinqiwen/gsnova/common/helper" 12 | "github.com/yinqiwen/gsnova/common/hosts" 13 | "github.com/yinqiwen/gsnova/common/logger" 14 | ) 15 | 16 | var GConf LocalConfig 17 | 18 | const ( 19 | BlockedByGFWRule = "BlockedByGFW" 20 | InHostsRule = "InHosts" 21 | IsCNIPRule = "IsCNIP" 22 | IsPrivateIPRule = "IsPrivateIP" 23 | ) 24 | 25 | func matchHostnames(pattern, host string) bool { 26 | host = strings.TrimSuffix(host, ".") 27 | pattern = strings.TrimSuffix(pattern, ".") 28 | 29 | if len(pattern) == 0 || len(host) == 0 { 30 | return false 31 | } 32 | 33 | patternParts := strings.Split(pattern, ".") 34 | hostParts := strings.Split(host, ".") 35 | 36 | if len(patternParts) != len(hostParts) { 37 | return false 38 | } 39 | 40 | for i, patternPart := range patternParts { 41 | if i == 0 && patternPart == "*" { 42 | continue 43 | } 44 | if patternPart != hostParts[i] { 45 | return false 46 | } 47 | } 48 | return true 49 | } 50 | 51 | type PACConfig struct { 52 | Method []string 53 | Host []string 54 | URL []string 55 | Rule []string 56 | Protocol []string 57 | Remote string 58 | } 59 | 60 | func (pac *PACConfig) ruleInHosts(req *http.Request) bool { 61 | return hosts.InHosts(req.Host) 62 | } 63 | 64 | func (pac *PACConfig) matchProtocol(protocol string) bool { 65 | if len(pac.Protocol) == 0 { 66 | return true 67 | } 68 | for _, p := range pac.Protocol { 69 | if p == "*" || strings.EqualFold(p, protocol) { 70 | return true 71 | } 72 | } 73 | return false 74 | } 75 | 76 | func (pac *PACConfig) matchRules(ip string, req *http.Request) bool { 77 | if len(pac.Rule) == 0 { 78 | return true 79 | } 80 | 81 | ok := true 82 | 83 | for _, rule := range pac.Rule { 84 | not := false 85 | if strings.HasPrefix(rule, "!") { 86 | not = true 87 | rule = rule[1:] 88 | } 89 | if strings.EqualFold(rule, InHostsRule) { 90 | if nil == req { 91 | ok = false 92 | } else { 93 | ok = pac.ruleInHosts(req) 94 | } 95 | } else if strings.EqualFold(rule, BlockedByGFWRule) { 96 | gfwList := getGFWList() 97 | if nil != gfwList && nil != req { 98 | ok = gfwList.IsBlockedByGFW(req) 99 | if !ok { 100 | logger.Debug("#### %s is NOT BlockedByGFW", req.Host) 101 | } 102 | } else { 103 | ok = true 104 | logger.Debug("NIL GFWList object or request") 105 | } 106 | } else if strings.EqualFold(rule, IsCNIPRule) { 107 | if len(ip) == 0 || nil == dns.CNIPSet { 108 | logger.Debug("NIL CNIP content or IP/Domain") 109 | ok = false 110 | } else { 111 | var err error 112 | if net.ParseIP(ip) == nil { 113 | ip, err = dns.DnsGetDoaminIP(ip) 114 | } 115 | if nil == err { 116 | ok = dns.CNIPSet.IsInCountry(net.ParseIP(ip), "CN") 117 | } 118 | logger.Debug("ip:%s is CNIP:%v", ip, ok) 119 | } 120 | } else if strings.EqualFold(rule, IsPrivateIPRule) { 121 | if len(ip) == 0 { 122 | ok = false 123 | } else { 124 | ok = helper.IsPrivateIP(ip) 125 | } 126 | } else { 127 | logger.Error("###Invalid rule:%s", rule) 128 | } 129 | if not { 130 | ok = ok != true 131 | } 132 | if !ok { 133 | break 134 | } 135 | } 136 | return ok 137 | } 138 | 139 | func MatchPatterns(str string, rules []string) bool { 140 | if len(rules) == 0 { 141 | return true 142 | } 143 | str = strings.ToLower(str) 144 | for _, pattern := range rules { 145 | matched, err := filepath.Match(pattern, str) 146 | if nil != err { 147 | logger.Error("Invalid pattern:%s with reason:%v", pattern, err) 148 | continue 149 | } 150 | if matched { 151 | return true 152 | } 153 | } 154 | return false 155 | } 156 | 157 | func (pac *PACConfig) Match(protocol string, ip string, req *http.Request) bool { 158 | ret := pac.matchProtocol(protocol) 159 | if !ret { 160 | return false 161 | } 162 | ret = pac.matchRules(ip, req) 163 | if !ret { 164 | return false 165 | } 166 | if nil == req { 167 | if len(pac.Host) > 0 || len(pac.Method) > 0 || len(pac.URL) > 0 { 168 | return false 169 | } 170 | return true 171 | } 172 | host := req.Host 173 | if len(pac.Host) > 0 && strings.Contains(host, ":") { 174 | host, _, _ = net.SplitHostPort(host) 175 | } 176 | return MatchPatterns(host, pac.Host) && MatchPatterns(req.Method, pac.Method) && MatchPatterns(req.URL.String(), pac.URL) 177 | } 178 | 179 | type HTTPDumpConfig struct { 180 | Dump string 181 | Domain []string 182 | ExcludeBody []string 183 | IncludeBody []string 184 | } 185 | 186 | func (dump *HTTPDumpConfig) MatchDomain(host string) bool { 187 | for _, dm := range dump.Domain { 188 | matched, _ := filepath.Match(dm, host) 189 | if matched { 190 | return true 191 | } 192 | } 193 | return false 194 | } 195 | 196 | type ProxyConfig struct { 197 | Local string 198 | Forward string 199 | MITM bool //Man-in-the-middle 200 | Transparent bool 201 | HTTPDump HTTPDumpConfig 202 | PAC []PACConfig 203 | } 204 | 205 | func (cfg *ProxyConfig) getProxyChannelByHost(proto string, host string) string { 206 | creq, _ := http.NewRequest("Connect", "https://"+host, nil) 207 | return cfg.findProxyChannelByRequest(proto, host, creq) 208 | } 209 | 210 | func (cfg *ProxyConfig) findProxyChannelByRequest(proto string, ip string, req *http.Request) string { 211 | var channelName string 212 | // if len(ip) > 0 && helper.IsPrivateIP(ip) { 213 | // //channel = "direct" 214 | // return channel.DirectChannelName 215 | // } 216 | for _, pac := range cfg.PAC { 217 | if pac.Match(proto, ip, req) { 218 | channelName = pac.Remote 219 | break 220 | } 221 | } 222 | if len(channelName) == 0 { 223 | logger.Error("No proxy channel found.") 224 | } 225 | return channelName 226 | } 227 | 228 | type AdminConfig struct { 229 | Listen string 230 | BroadcastAddr string 231 | ConfigDir string 232 | } 233 | 234 | type UDPGWConfig struct { 235 | Addr string 236 | } 237 | 238 | type SNIConfig struct { 239 | Redirect map[string]string 240 | } 241 | 242 | func (sni *SNIConfig) redirect(domain string) (string, bool) { 243 | for k, v := range sni.Redirect { 244 | matched, err := filepath.Match(k, domain) 245 | if nil != err { 246 | logger.Error("Invalid pattern:%s with reason:%v", k, err) 247 | continue 248 | } 249 | if matched { 250 | return v, true 251 | } 252 | } 253 | return "", false 254 | } 255 | 256 | type GFWListConfig struct { 257 | URL string 258 | UserRule []string 259 | Proxy string 260 | RefershPeriodMiniutes int 261 | } 262 | 263 | type LocalConfig struct { 264 | Log []string 265 | Cipher channel.CipherConfig 266 | Mux channel.MuxConfig 267 | ProxyLimit channel.ProxyLimitConfig 268 | UserAgent string 269 | UPNPExposePort int 270 | LocalDNS dns.LocalDNSConfig 271 | UDPGW UDPGWConfig 272 | SNI SNIConfig 273 | Admin AdminConfig 274 | GFWList GFWListConfig 275 | TransparentMark int 276 | Proxy []ProxyConfig 277 | Channel []channel.ProxyChannelConfig 278 | } 279 | 280 | func (cfg *LocalConfig) init() error { 281 | haveDirect := false 282 | for i := range GConf.Channel { 283 | if GConf.Channel[i].Name == channel.DirectChannelName && GConf.Channel[i].Enable { 284 | haveDirect = true 285 | GConf.Channel[i].ServerList = []string{"direct://0.0.0.0:0"} 286 | GConf.Channel[i].ConnsPerServer = 1 287 | } 288 | if len(GConf.Channel[i].Cipher.Key) == 0 { 289 | GConf.Channel[i].Cipher = GConf.Cipher 290 | } 291 | if len(GConf.Channel[i].HTTP.UserAgent) == 0 { 292 | GConf.Channel[i].HTTP.UserAgent = GConf.UserAgent 293 | } 294 | GConf.Channel[i].Adjust() 295 | } 296 | 297 | if !haveDirect { 298 | directProxyChannel := make([]channel.ProxyChannelConfig, 1) 299 | directProxyChannel[0].Name = channel.DirectChannelName 300 | directProxyChannel[0].Enable = true 301 | directProxyChannel[0].ConnsPerServer = 1 302 | directProxyChannel[0].LocalDialMSTimeout = 5000 303 | directProxyChannel[0].ServerList = []string{"direct://0.0.0.0:0"} 304 | GConf.Channel = append(directProxyChannel, GConf.Channel...) 305 | } 306 | return nil 307 | } 308 | -------------------------------------------------------------------------------- /local/gsnova/gsnova.go: -------------------------------------------------------------------------------- 1 | package gsnova 2 | 3 | import ( 4 | _ "github.com/yinqiwen/gsnova/common/channel/common" 5 | "github.com/yinqiwen/gsnova/common/helper" 6 | "github.com/yinqiwen/gsnova/common/netx" 7 | "github.com/yinqiwen/gsnova/local" 8 | ) 9 | 10 | func init() { 11 | 12 | } 13 | 14 | // type testProc struct { 15 | // } 16 | 17 | // func (t *testProc) Protect(fileDescriptor int) error { 18 | // return nil 19 | // } 20 | 21 | func StartLocalProxy(home string, conf string, hosts string, cnip string, watchConf bool) error { 22 | options := local.ProxyOptions{ 23 | Home: home, 24 | Config: conf, 25 | Hosts: hosts, 26 | CNIP: cnip, 27 | WatchConf: watchConf, 28 | } 29 | // ProtectConnections("114.114.114.114", &testProc{}) 30 | return local.Start(options) 31 | } 32 | 33 | func StopLocalProxy() error { 34 | netx.Reset() 35 | return local.Stop() 36 | } 37 | 38 | //SyncConfig sync config files from running gsnova instance 39 | func SyncConfig(addr string, localDir string) (bool, error) { 40 | return local.SyncConfig(addr, localDir) 41 | } 42 | 43 | func CreateMITMCA(dir string) error { 44 | return helper.CreateRootCA(dir + "/MITM") 45 | } 46 | -------------------------------------------------------------------------------- /local/gsnova/gsnova_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package gsnova 4 | 5 | import ( 6 | "github.com/yinqiwen/gsnova/common/netx" 7 | "github.com/yinqiwen/gsnova/common/protector" 8 | ) 9 | 10 | // SocketProtector is an interface for classes that can protect Android sockets, 11 | // meaning those sockets will not be passed through the VPN. 12 | type SocketProtector interface { 13 | Protect(fileDescriptor int) error 14 | } 15 | 16 | // ProtectConnections allows connections made by Lantern to be protected from 17 | // routing via a VPN. This is useful when running Lantern as a VPN on Android, 18 | // because it keeps Lantern's own connections from being captured by the VPN and 19 | // resulting in an infinite loop. 20 | func ProtectConnections(dnsServer string, p SocketProtector) { 21 | protector.Configure(p.Protect, dnsServer) 22 | //p := New(protector.Protect, dnsServer) 23 | netx.OverrideDial(protector.DialContext) 24 | netx.OverrideResolve(protector.Resolve) 25 | netx.OverrideListenUDP(protector.ListenUDP) 26 | netx.OverrideDialUDP(protector.DialUDP) 27 | } 28 | -------------------------------------------------------------------------------- /local/proxy.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/fsnotify/fsnotify" 11 | "github.com/yinqiwen/gotoolkit/gfwlist" 12 | "github.com/yinqiwen/gsnova/common/channel" 13 | "github.com/yinqiwen/gsnova/common/dns" 14 | "github.com/yinqiwen/gsnova/common/helper" 15 | "github.com/yinqiwen/gsnova/common/hosts" 16 | "github.com/yinqiwen/gsnova/common/logger" 17 | ) 18 | 19 | var proxyHome string 20 | 21 | var localGFWList atomic.Value 22 | var fetchGFWListRunning bool 23 | 24 | func init() { 25 | proxyHome = "." 26 | } 27 | 28 | var clientConfName = "client.json" 29 | var hostsConfName = "hosts.json" 30 | 31 | func loadClientConf(conf string) error { 32 | confdata, err := helper.ReadWithoutComment(conf, "//") 33 | if nil != err { 34 | logger.Error("Failed to load conf:%s with reason:%v", conf, err) 35 | } 36 | GConf = LocalConfig{} 37 | err = json.Unmarshal(confdata, &GConf) 38 | if nil != err { 39 | logger.Error("Failed to unmarshal json:%s to config for reason:%v", string(confdata), err) 40 | } 41 | return GConf.init() 42 | } 43 | 44 | func loadHostsConf(conf string) error { 45 | err := hosts.Init(conf) 46 | if nil != err { 47 | logger.Error("Failed to init local hosts with reason:%v.", err) 48 | } 49 | return err 50 | } 51 | 52 | func watchConf(watcher *fsnotify.Watcher) { 53 | for { 54 | select { 55 | case event := <-watcher.Events: 56 | logger.Debug("fsnotify event:%v", event) 57 | if (event.Op & fsnotify.Write) == fsnotify.Write { 58 | loadClientConf(event.Name) 59 | } 60 | case err := <-watcher.Errors: 61 | logger.Error("error:%v", err) 62 | } 63 | } 64 | } 65 | 66 | type ProxyOptions struct { 67 | Config string 68 | Hosts string 69 | CNIP string 70 | Home string 71 | WatchConf bool 72 | } 73 | 74 | func getGFWList() *gfwlist.GFWList { 75 | v := localGFWList.Load() 76 | if nil != v { 77 | return v.(*gfwlist.GFWList) 78 | } 79 | return nil 80 | } 81 | 82 | func loadGFWList(hc *http.Client) error { 83 | resp, err := hc.Get(GConf.GFWList.URL) 84 | if nil != err { 85 | logger.Error("Failed to fetch GFWList") 86 | return err 87 | } 88 | defer resp.Body.Close() 89 | if resp.StatusCode != 200 { 90 | logger.Error("Failed to fetch GFWList with res:%v", resp) 91 | return err 92 | } 93 | body, err := ioutil.ReadAll(resp.Body) 94 | if nil != err { 95 | logger.Error("Failed to read GFWList with err:%v", err) 96 | return err 97 | } 98 | gfw, err := gfwlist.NewFromString(string(body), true) 99 | if nil != err { 100 | logger.Error("Invalid GFWList content:%v", err) 101 | return err 102 | } 103 | for _, rule := range GConf.GFWList.UserRule { 104 | gfw.Add(rule) 105 | } 106 | logger.Info("GFWList sync success.") 107 | localGFWList.Store(gfw) 108 | return nil 109 | } 110 | 111 | func initGFWList() { 112 | if fetchGFWListRunning { 113 | return 114 | } 115 | fetchGFWListRunning = true 116 | if len(GConf.GFWList.URL) > 0 { 117 | hc, _ := channel.NewHTTPClient(&channel.ProxyChannelConfig{Proxy: GConf.GFWList.Proxy}, "http") 118 | for { 119 | err := loadGFWList(hc) 120 | var nextRefreshTime time.Duration 121 | if nil == err { 122 | if GConf.GFWList.RefershPeriodMiniutes <= 0 { 123 | GConf.GFWList.RefershPeriodMiniutes = 1440 124 | } 125 | nextRefreshTime = time.Duration(GConf.GFWList.RefershPeriodMiniutes) * time.Minute 126 | } else { 127 | nextRefreshTime = 5 * time.Second 128 | } 129 | logger.Info("Refresh GFWList after %v.", nextRefreshTime) 130 | time.Sleep(nextRefreshTime) 131 | } 132 | } 133 | } 134 | 135 | func StartProxy() error { 136 | GConf.init() 137 | logger.InitLogger(GConf.Log) 138 | channel.SetDefaultMuxConfig(GConf.Mux) 139 | 140 | channel.UPNPExposePort = GConf.UPNPExposePort 141 | if GConf.TransparentMark > 0 { 142 | enableTransparentSocketMark(GConf.TransparentMark) 143 | } 144 | dns.Init(&GConf.LocalDNS) 145 | go initGFWList() 146 | 147 | logger.Notice("Allowed proxy channel with schema:%v", channel.AllowedSchema()) 148 | singalCh := make(chan bool, len(GConf.Channel)) 149 | channelCount := 0 150 | for _, conf := range GConf.Channel { 151 | if !conf.Enable { 152 | continue 153 | } 154 | channel := channel.NewProxyChannel(&conf) 155 | channel.Conf = conf 156 | channelCount++ 157 | go func() { 158 | channel.Init(true) 159 | singalCh <- true 160 | }() 161 | } 162 | for i := 0; i < channelCount; i++ { 163 | <-singalCh 164 | } 165 | 166 | err := helper.CreateRootCA(proxyHome + "/MITM") 167 | if nil != err { 168 | logger.Notice("Create MITM Root CA:%v", err) 169 | } 170 | 171 | logger.Info("Started GSnova %s.", channel.Version) 172 | 173 | go startAdminServer() 174 | startLocalServers() 175 | return nil 176 | } 177 | 178 | func Start(options ProxyOptions) error { 179 | clientConf := options.Config 180 | hostsConf := options.Hosts 181 | proxyHome = options.Home 182 | 183 | if options.WatchConf { 184 | confWatcher, err := fsnotify.NewWatcher() 185 | if err != nil { 186 | logger.Fatal("%v", err) 187 | return err 188 | } 189 | confWatcher.Add(clientConf) 190 | //confWatcher.Add(hostsConf) 191 | go watchConf(confWatcher) 192 | } 193 | 194 | if len(clientConf) > 0 { 195 | err := loadClientConf(clientConf) 196 | if nil != err { 197 | //log.Println(err) 198 | return err 199 | } 200 | } else { 201 | // if len(GConf.Proxy) == 0 { 202 | // return errors.New("Can NOT start proxy without any config") 203 | // } 204 | } 205 | GConf.LocalDNS.CNIPSet = options.CNIP 206 | channel.SetDefaultProxyLimitConfig(GConf.ProxyLimit) 207 | loadHostsConf(hostsConf) 208 | return StartProxy() 209 | } 210 | 211 | func Stop() error { 212 | stopLocalServers() 213 | channel.StopLocalChannels() 214 | hosts.Clear() 215 | return nil 216 | } 217 | -------------------------------------------------------------------------------- /local/transparent_other.go: -------------------------------------------------------------------------------- 1 | // +build android !linux 2 | 3 | package local 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | 9 | "github.com/yinqiwen/gsnova/common/logger" 10 | ) 11 | 12 | func getOrinalTCPRemoteAddr(conn net.Conn) (net.Conn, net.IP, uint16, error) { 13 | return nil, nil, 0, fmt.Errorf("'getOrinalTCPRemoteAddr' Not supported in current system") 14 | } 15 | 16 | func startTransparentUDProxy(addr string, proxy *ProxyConfig) { 17 | logger.Error("'startTransparentUDProxy' Not supported in current system") 18 | } 19 | 20 | func enableTransparentSocketMark(v int) { 21 | logger.Error("'enableTransparentSocketMark' Not supported in current system") 22 | } 23 | 24 | func supportTransparentProxy() bool { 25 | return false 26 | } 27 | -------------------------------------------------------------------------------- /local/udpgw.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "net" 10 | "sync" 11 | "time" 12 | 13 | "github.com/google/btree" 14 | "github.com/yinqiwen/gsnova/common/channel" 15 | "github.com/yinqiwen/gsnova/common/dns" 16 | "github.com/yinqiwen/gsnova/common/logger" 17 | "github.com/yinqiwen/gsnova/common/mux" 18 | ) 19 | 20 | const ( 21 | addrTypeIPv4 = 1 22 | addrTypeIPv6 = 6 23 | flagKeepAlive = uint8(1 << 0) 24 | flagReBind = uint8(1 << 1) 25 | flagDNS = uint8(1 << 2) 26 | flagIPv6 = uint8(1 << 3) 27 | ) 28 | 29 | type udpSessionId struct { 30 | id uint16 31 | activeTime time.Time 32 | } 33 | 34 | func (s *udpSessionId) Less(than btree.Item) bool { 35 | other := than.(*udpSessionId) 36 | if !s.activeTime.Equal(other.activeTime) { 37 | return s.activeTime.Before(other.activeTime) 38 | } 39 | return s.id < other.id 40 | } 41 | 42 | type udpgwAddr struct { 43 | ip net.IP 44 | port uint16 45 | } 46 | 47 | type udpgwPacket struct { 48 | length uint16 49 | flags uint8 50 | conid uint16 51 | addr udpgwAddr 52 | content []byte 53 | } 54 | 55 | func (u *udpgwPacket) address() string { 56 | if len(u.addr.ip) == 16 { 57 | u.addr.ip = u.addr.ip.To16() 58 | } else { 59 | u.addr.ip = u.addr.ip.To4() 60 | } 61 | return fmt.Sprintf("%s:%d", u.addr.ip.String(), u.addr.port) 62 | } 63 | 64 | func (u *udpgwPacket) write(w io.Writer) error { 65 | var buf bytes.Buffer 66 | u.length = 1 + 2 + uint16(len(u.addr.ip)) + 2 + uint16(len(u.content)) 67 | binary.Write(&buf, binary.LittleEndian, u.length) 68 | binary.Write(&buf, binary.BigEndian, u.flags) 69 | binary.Write(&buf, binary.BigEndian, u.conid) 70 | buf.Write(u.addr.ip) 71 | binary.Write(&buf, binary.BigEndian, u.addr.port) 72 | buf.Write(u.content) 73 | _, err := w.Write(buf.Bytes()) 74 | return err 75 | } 76 | 77 | func (u *udpgwPacket) read(r *bufio.Reader) error { 78 | bs, err := r.Peek(2) 79 | if nil != err { 80 | return err 81 | } 82 | u.length = binary.LittleEndian.Uint16(bs) 83 | //binary.Read(r, binary.BigEndian, &u.length) 84 | r.Discard(2) 85 | //log.Printf("###First %d %d %d %p", u.length, binary.BigEndian.Uint16(bs), len(bs), r) 86 | _, err = r.Peek(int(u.length)) 87 | if nil != err { 88 | //log.Printf("### %v", err) 89 | return err 90 | } 91 | bodylen := u.length 92 | binary.Read(r, binary.BigEndian, &u.flags) 93 | binary.Read(r, binary.BigEndian, &u.conid) 94 | bodylen -= 3 95 | if bodylen > 0 { 96 | if (u.flags & flagIPv6) != 0 { 97 | u.addr.ip = make(net.IP, 16) 98 | 99 | } else { 100 | u.addr.ip = make(net.IP, 4) 101 | } 102 | r.Read(u.addr.ip) 103 | bodylen -= uint16(len(u.addr.ip)) 104 | binary.Read(r, binary.BigEndian, &u.addr.port) 105 | bodylen -= 2 106 | u.content = make([]byte, int(bodylen)) 107 | r.Read(u.content) 108 | } 109 | return nil 110 | } 111 | 112 | type udpSession struct { 113 | udpSessionId 114 | addr udpgwAddr 115 | targetAddr string 116 | localConn net.Conn 117 | stream mux.MuxStream 118 | streamWriter io.Writer 119 | streamReader io.Reader 120 | proxyChannelName string 121 | } 122 | 123 | func (u *udpSession) closeStream() { 124 | if nil != u.stream { 125 | u.stream.Close() 126 | u.stream = nil 127 | u.streamWriter = nil 128 | u.streamReader = nil 129 | } 130 | } 131 | func (u *udpSession) close() { 132 | u.closeStream() 133 | removeUdpSession(&u.udpSessionId, 0) 134 | updateUdpSession(u, true) 135 | } 136 | 137 | func (u *udpSession) Write(content []byte) error { 138 | var packet udpgwPacket 139 | packet.content = content 140 | packet.addr = u.addr 141 | packet.conid = u.udpSessionId.id 142 | if len(u.addr.ip) == 16 { 143 | packet.flags = flagIPv6 144 | } 145 | return packet.write(u.localConn) 146 | } 147 | 148 | func (u *udpSession) handlePacket(proxy *ProxyConfig, packet *udpgwPacket) error { 149 | if nil != u.streamWriter { 150 | u.streamWriter.Write(packet.content) 151 | return nil 152 | } 153 | 154 | remoteAddr := packet.address() 155 | if packet.addr.port == 53 { 156 | selectProxy := proxy.findProxyChannelByRequest("dns", packet.addr.ip.String(), nil) 157 | if selectProxy == channel.DirectChannelName { 158 | res, err := dns.LocalDNS.QueryRaw(packet.content) 159 | if nil == err { 160 | err = u.Write(res) 161 | } 162 | if nil != err { 163 | logger.Error("[ERROR]Failed to query dns with reason:%v", err) 164 | } 165 | u.close() 166 | return err 167 | } 168 | u.proxyChannelName = selectProxy 169 | if len(GConf.LocalDNS.TrustedDNS) > 0 { 170 | remoteAddr = GConf.LocalDNS.TrustedDNS[0] 171 | } 172 | } 173 | if len(u.proxyChannelName) == 0 { 174 | u.proxyChannelName = proxy.findProxyChannelByRequest("udp", packet.addr.ip.String(), nil) 175 | } 176 | if len(u.proxyChannelName) == 0 { 177 | logger.Error("[ERROR]No proxy found for udp to %s", packet.addr.ip.String()) 178 | return nil 179 | } 180 | 181 | if len(u.targetAddr) > 0 { 182 | if u.targetAddr != packet.address() { 183 | u.closeStream() 184 | } 185 | } 186 | stream, conf, err := channel.GetMuxStreamByChannel(u.proxyChannelName) 187 | readTimeoutMS := conf.RemoteUDPReadMSTimeout 188 | if packet.addr.port == 53 { 189 | readTimeoutMS = conf.RemoteDNSReadMSTimeout 190 | } 191 | if nil != stream { 192 | opt := mux.StreamOptions{ 193 | DialTimeout: conf.RemoteDialMSTimeout, 194 | ReadTimeout: readTimeoutMS, 195 | } 196 | err = stream.Connect("udp", remoteAddr, opt) 197 | } 198 | if nil != err { 199 | logger.Error("[ERROR]Failed to create mux stream:%v for proxy:%s by address:%v", err, u.proxyChannelName, packet.addr) 200 | return err 201 | } 202 | 203 | u.stream = stream 204 | u.streamReader, u.streamWriter = mux.GetCompressStreamReaderWriter(stream, conf.Compressor) 205 | go func() { 206 | b := make([]byte, 8192) 207 | for { 208 | stream.SetReadDeadline(time.Now().Add(time.Duration(readTimeoutMS) * time.Millisecond)) 209 | n, err := u.streamReader.Read(b) 210 | if n > 0 { 211 | err = u.Write(b[0:n]) 212 | } 213 | if nil != err { 214 | break 215 | } 216 | } 217 | 218 | }() 219 | u.streamWriter.Write(packet.content) 220 | return nil 221 | } 222 | 223 | var udpSessionTable sync.Map 224 | var udpSessionIdSet = btree.New(4) 225 | var cidTable = make(map[uint32]uint16) 226 | var udpSessionMutex sync.Mutex 227 | 228 | func closeAllUDPSession() { 229 | udpSessionTable.Range(func(key, value interface{}) bool { 230 | session := value.(*udpSession) 231 | session.closeStream() 232 | return true 233 | }) 234 | udpSessionTable = sync.Map{} 235 | cidTable = make(map[uint32]uint16) 236 | } 237 | 238 | func removeUdpSession(id *udpSessionId, expireTime time.Duration) { 239 | v, exist := udpSessionTable.Load(id.id) 240 | if exist { 241 | if expireTime > 0 { 242 | logger.Debug("Delete udpsession:%d since it's not active since %v ago.", id.id, expireTime) 243 | } 244 | v.(*udpSession).closeStream() 245 | udpSessionTable.Delete(id.id) 246 | //delete(udpSessionTable, s.id) 247 | } 248 | } 249 | 250 | func init() { 251 | go expireUdpSessions() 252 | } 253 | 254 | func expireUdpSessions() { 255 | ticker := time.NewTicker(1 * time.Second) 256 | removeExpiredSession := func() { 257 | udpSessionMutex.Lock() 258 | defer udpSessionMutex.Unlock() 259 | for i := 0; i < 5; i++ { 260 | tmp := udpSessionIdSet.Min() 261 | if nil != tmp { 262 | id := tmp.(*udpSessionId) 263 | expireTime := time.Now().Sub(id.activeTime) 264 | if expireTime >= 30*time.Second { 265 | udpSessionIdSet.Delete(id) 266 | removeUdpSession(id, expireTime) 267 | } else { 268 | return 269 | } 270 | } 271 | } 272 | } 273 | for { 274 | select { 275 | case <-ticker.C: 276 | removeExpiredSession() 277 | } 278 | } 279 | } 280 | 281 | func updateUdpSession(u *udpSession, remove bool) { 282 | udpSessionMutex.Lock() 283 | defer udpSessionMutex.Unlock() 284 | if !u.activeTime.IsZero() { 285 | udpSessionIdSet.Delete(&u.udpSessionId) 286 | } 287 | if !remove { 288 | u.activeTime = time.Now() 289 | udpSessionIdSet.ReplaceOrInsert(&u.udpSessionId) 290 | } 291 | } 292 | 293 | func getUDPSession(id uint16, conn net.Conn, createIfMissing bool) *udpSession { 294 | udpSessionMutex.Lock() 295 | defer udpSessionMutex.Unlock() 296 | usession, exist := udpSessionTable.Load(id) 297 | if !exist { 298 | if createIfMissing { 299 | s := new(udpSession) 300 | s.localConn = conn 301 | s.id = id 302 | udpSessionTable.Store(id, s) 303 | //cidTable[s.session.id] = id 304 | return s 305 | } 306 | return nil 307 | } 308 | return usession.(*udpSession) 309 | } 310 | func getCid(sid uint32) (uint16, bool) { 311 | udpSessionMutex.Lock() 312 | defer udpSessionMutex.Unlock() 313 | cid, exist := cidTable[sid] 314 | return cid, exist 315 | } 316 | 317 | func handleUDPGatewayConn(localConn net.Conn, proxy *ProxyConfig) { 318 | bufconn := bufio.NewReader(localConn) 319 | defer func() { 320 | localConn.Close() 321 | }() 322 | for { 323 | var packet udpgwPacket 324 | err := packet.read(bufconn) 325 | if nil != err { 326 | logger.Error("Failed to read udpgw packet:%v", err) 327 | localConn.Close() 328 | return 329 | } 330 | if len(packet.content) == 0 { 331 | continue 332 | //log.Printf("###Recv udpgw packet to %s:%d", packet.addr.ip.String(), packet.addr.port) 333 | } 334 | 335 | usession := getUDPSession(packet.conid, localConn, true) 336 | usession.addr = packet.addr 337 | updateUdpSession(usession, false) 338 | go usession.handlePacket(proxy, &packet) 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /remote/Procfile: -------------------------------------------------------------------------------- 1 | web: gsnova -cmd -server -listen http://:$PORT -key 809240d3a021449f6e67aa73221d42df942a308a -log console -user "*" 2 | -------------------------------------------------------------------------------- /remote/config.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | import ( 4 | "github.com/yinqiwen/gsnova/common/channel" 5 | ) 6 | 7 | type ServerListenConfig struct { 8 | Listen string 9 | Cert string 10 | Key string 11 | KCParams channel.KCPConfig 12 | } 13 | 14 | type ServerConfig struct { 15 | Cipher channel.CipherConfig 16 | RateLimit channel.RateLimitConfig 17 | ProxyLimit channel.ProxyLimitConfig 18 | Mux channel.MuxConfig 19 | Log []string 20 | Server []ServerListenConfig 21 | } 22 | 23 | var ServerConf ServerConfig 24 | 25 | func InitDefaultConf() { 26 | ServerConf.Mux.StreamIdleTimeout = 10 27 | ServerConf.Mux.SessionIdleTimeout = 300 28 | for _, lis := range ServerConf.Server { 29 | lis.KCParams.InitDefaultConf() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /remote/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - disk_quota: 1024M 4 | buildpack: go_buildpack 5 | name: paas 6 | path: . 7 | domain: mybluemix.net 8 | instances: 1 9 | memory: 128M -------------------------------------------------------------------------------- /remote/proxy.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/url" 6 | 7 | "github.com/yinqiwen/gsnova/common/helper" 8 | "github.com/yinqiwen/gsnova/common/logger" 9 | 10 | "github.com/yinqiwen/gsnova/common/channel/http2" 11 | "github.com/yinqiwen/gsnova/common/channel/kcp" 12 | "github.com/yinqiwen/gsnova/common/channel/quic" 13 | "github.com/yinqiwen/gsnova/common/channel/tcp" 14 | ) 15 | 16 | func generateTLSConfig(cert, key string) (*tls.Config, error) { 17 | if len(cert) > 0 { 18 | tlscfg := &tls.Config{} 19 | tlscfg.Certificates = make([]tls.Certificate, 1) 20 | var err error 21 | tlscfg.Certificates[0], err = tls.LoadX509KeyPair(cert, key) 22 | return tlscfg, err 23 | } 24 | return helper.GenerateTLSConfig(), nil 25 | } 26 | 27 | func StartRemoteProxy() { 28 | for _, lis := range ServerConf.Server { 29 | u, err := url.Parse(lis.Listen) 30 | if nil != err { 31 | logger.Error("Invalid listen url:%s for reason:%v", lis.Listen, err) 32 | continue 33 | } 34 | scheme := u.Scheme 35 | switch scheme { 36 | case "quic": 37 | { 38 | tlscfg, err := generateTLSConfig(lis.Cert, lis.Key) 39 | if nil != err { 40 | logger.Error("Failed to create TLS config by cert/key: %s/%s", lis.Cert, lis.Key) 41 | } else { 42 | go func() { 43 | quic.StartQuicProxyServer(u.Host, tlscfg) 44 | }() 45 | } 46 | } 47 | case "kcp": 48 | { 49 | go func() { 50 | kcp.StartKCPProxyServer(u.Host, &lis.KCParams) 51 | }() 52 | } 53 | case "tcp": 54 | { 55 | go func() { 56 | tcp.StartTcpProxyServer(u.Host) 57 | }() 58 | } 59 | case "tls": 60 | { 61 | tlscfg, err := generateTLSConfig(lis.Cert, lis.Key) 62 | if nil != err { 63 | logger.Error("Failed to create TLS config by cert/key: %s/%s", lis.Cert, lis.Key) 64 | } else { 65 | go func() { 66 | tcp.StartTLSProxyServer(u.Host, tlscfg) 67 | }() 68 | } 69 | } 70 | case "http": 71 | { 72 | go func() { 73 | startHTTPProxyServer(u.Host, "", "") 74 | }() 75 | } 76 | case "https": 77 | { 78 | go func() { 79 | startHTTPProxyServer(u.Host, lis.Cert, lis.Key) 80 | }() 81 | } 82 | case "http2": 83 | { 84 | tlscfg, err := generateTLSConfig(lis.Cert, lis.Key) 85 | if nil != err { 86 | logger.Error("Failed to create TLS config by cert/key: %s/%s", lis.Cert, lis.Key) 87 | } else { 88 | go func() { 89 | http2.StartHTTTP2ProxyServer(u.Host, tlscfg) 90 | }() 91 | } 92 | } 93 | default: 94 | logger.Error("Invalid listen scheme:%s in listen url:%s", scheme, lis.Listen) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /remote/web.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/yinqiwen/gotoolkit/ots" 10 | "github.com/yinqiwen/gsnova/common/channel" 11 | httpChannel "github.com/yinqiwen/gsnova/common/channel/http" 12 | "github.com/yinqiwen/gsnova/common/channel/websocket" 13 | "github.com/yinqiwen/gsnova/common/logger" 14 | ) 15 | 16 | // hello world, the web server 17 | func indexCallback(w http.ResponseWriter, req *http.Request) { 18 | io.WriteString(w, strings.Replace(html, "${Version}", channel.Version, -1)) 19 | } 20 | 21 | func statCallback(w http.ResponseWriter, req *http.Request) { 22 | w.WriteHeader(200) 23 | fmt.Fprintf(w, "Version: %s\n", channel.Version) 24 | ots.Handle("stat", w) 25 | } 26 | 27 | func stackdumpCallback(w http.ResponseWriter, req *http.Request) { 28 | w.WriteHeader(200) 29 | ots.Handle("stackdump", w) 30 | } 31 | 32 | func startHTTPProxyServer(listenAddr string, cert, key string) { 33 | mux := http.NewServeMux() 34 | mux.HandleFunc("/", indexCallback) 35 | mux.HandleFunc("/stat", statCallback) 36 | mux.HandleFunc("/stackdump", stackdumpCallback) 37 | mux.HandleFunc("/ws", websocket.WebsocketInvoke) 38 | mux.HandleFunc("/http/pull", httpChannel.HTTPInvoke) 39 | mux.HandleFunc("/http/push", httpChannel.HTTPInvoke) 40 | mux.HandleFunc("/http/test", httpChannel.HttpTest) 41 | 42 | logger.Info("Listen on HTTP address:%s", listenAddr) 43 | var err error 44 | if len(cert) == 0 { 45 | err = http.ListenAndServe(listenAddr, mux) 46 | } else { 47 | err = http.ListenAndServeTLS(listenAddr, cert, key, mux) 48 | } 49 | 50 | if nil != err { 51 | logger.Error("Listen HTTP server error:%v", err) 52 | } 53 | } 54 | 55 | const html = ` 56 | 58 | 59 | 60 | 61 | 62 | GSnova PAAS Server 63 | 64 | 65 | 66 |
67 | 68 |

GSnova 69 | by @yinqiwen

70 | 71 |
72 | Welcome to use GSnova HTTP/WebSocket Server ${Version}! 73 |
74 | 75 |

Code

76 |

You can clone the project with Git 77 | by running: 78 |

$ git clone https://github.com/yinqiwen/gsnova.git
79 |

80 | 81 | 84 | 85 |
86 | 87 | 88 | ` 89 | -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- 1 | { 2 | "AdminListen": "127.0.0.1:60000", 3 | "DialTimeout": 15, 4 | "UDPReadTimeout": 30, 5 | "Log": ["server.log"], 6 | //cipher config 7 | "Cipher":{ 8 | "Key":"809240d3a021449f6e67aa73221d42df942a308a", 9 | //AllowedUser 10 | "User": "*,gsnova" 11 | }, 12 | //Limit user 'abc' bandwidth to 256KB/s 13 | "RateLimit":{ 14 | "abc": "256K", 15 | "*":"-1" 16 | }, 17 | "Mux":{ 18 | "MaxStreamWindow": "512K", 19 | "StreamMinRefresh":"32K", 20 | "StreamIdleTimeout":10, 21 | "SessionIdleTimeout":300 22 | }, 23 | "Server":[ 24 | { 25 | "Listen":"tcp://:48100" 26 | }, 27 | { 28 | "Listen":"quic://:48100" 29 | }, 30 | { 31 | "Listen":"http://:48101" 32 | }, 33 | { 34 | "Listen":"kcp://:48101", 35 | "KCParams":{ 36 | "Mode":"fast2" 37 | } 38 | }, 39 | { 40 | "Listen":"tls://:48102", 41 | "Key": "", 42 | "Cert":"" 43 | ///"Key":"/etc/letsencrypt/live/testdomain.tk/privkey.pem", 44 | //"Cert":"/etc/letsencrypt/live/testdomain.tk/fullchain.pem" 45 | }, 46 | { 47 | "Listen":"http2://:48103", 48 | "Key": "", 49 | "Cert":"" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /shell/centos_deploy_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GSNOVA_SERVICE_DIR=gsnova_server 4 | GSNOVA_VER=latest 5 | GSNOVA_FILE=gsnova_linux_amd64-$GSNOVA_VER.tar.bz2 6 | GSNOVA_RELEASE_URL=https://github.com/yinqiwen/gsnova/releases/download/$GSNOVA_VER/$GSNOVA_FILE 7 | 8 | function install_dependencies(){ 9 | yum install git python-setuptools -y -q 10 | if [ $? -ne 0 ]; then 11 | echo "Failed to install git." 12 | exit 1 13 | fi 14 | easy_install supervisor 15 | if [ ! -f /etc/supervisord.conf ]; then 16 | echo_supervisord_conf > /etc/supervisord.conf 17 | fi 18 | } 19 | 20 | 21 | 22 | function download_gsnova_server(){ 23 | mkdir -p $GSNOVA_SERVICE_DIR; cd $GSNOVA_SERVICE_DIR 24 | echo ">>>>> Syncing gsnova server" 25 | wget $GSNOVA_RELEASE_URL -O $GSNOVA_FILE 26 | if [ $? -ne 0 ]; then 27 | echo "Sync gsnova server code failed." 28 | return 29 | fi 30 | echo "<<<<< Done syncing gsnova server" 31 | echo ">>>>> Building gsnova server" 32 | 33 | tar jxf $GSNOVA_FILE 34 | if [ ! -f myserver.json ]; then 35 | cp ./server.json ./myserver.json 36 | fi 37 | 38 | echo "<<<<< Done building gsnova server" 39 | echo "Please edit $GSNOVA_SERVICE_DIR/myserver.json before start gsnova_server." 40 | cd .. 41 | } 42 | 43 | function generate_supervise_conf(){ 44 | cd $GSNOVA_SERVICE_DIR 45 | echo "[program:gsnova_server] 46 | command=$(pwd)/gsnova -server -conf $(pwd)/myserver.json -admin :60000 47 | autostart=true 48 | autorestart=true 49 | startsecs=3 50 | directory=$(pwd) 51 | stderr_logfile=$(pwd)/gsnova_server_err.log 52 | stdout_logfile=$(pwd)/gsnova_server.log" > gsnova_server_supervise.conf 53 | 54 | echo "Plase add $(pwd)/gsnova_server_supervise.conf into /etc/supervisord.conf include section first." 55 | echo "Then exec \"supervisord -c /etc/supervisord.conf\" to run gsnova server." 56 | #kill existing process 57 | kill -9 `cat .gsnova.pid 2>/dev/null` 2>/dev/null 58 | } 59 | 60 | install_dependencies 61 | #install_golang 62 | #env_setting 63 | download_gsnova_server 64 | generate_supervise_conf 65 | 66 | 67 | -------------------------------------------------------------------------------- /shell/cnipset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | wget -c http://ftp.apnic.net/stats/apnic/delegated-apnic-latest 3 | cat delegated-apnic-latest | awk -F '|' '/CN/&&/ipv4/ {print $4 "/" 32-log($5)/log(2)}' > cnipset.txt 4 | 5 | ipset -N cnipset hash:net maxelem 65536 6 | for ip in $(cat 'cnipset.txt'); do 7 | ipset add cnipset $ip 8 | done -------------------------------------------------------------------------------- /shell/iptables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | insmod nf_tproxy_core 3 | insmod xt_TPROXY 4 | 5 | iptables -t nat -N GSNOVA 6 | iptables -t nat -A GSNOVA -p tcp -m mark --mark 48100 -j RETURN 7 | iptables -t nat -A GSNOVA -d 0.0.0.0/8 -j RETURN 8 | iptables -t nat -A GSNOVA -d 10.0.0.0/8 -j RETURN 9 | iptables -t nat -A GSNOVA -d 127.0.0.0/8 -j RETURN 10 | iptables -t nat -A GSNOVA -d 169.254.0.0/16 -j RETURN 11 | iptables -t nat -A GSNOVA -d 172.16.0.0/12 -j RETURN 12 | iptables -t nat -A GSNOVA -d 192.168.0.0/16 -j RETURN 13 | iptables -t nat -A GSNOVA -d 224.0.0.0/4 -j RETURN 14 | iptables -t nat -A GSNOVA -d 240.0.0.0/4 -j RETURN 15 | iptables -t nat -A GSNOVA -p tcp -m set --match-set cnipset dst -j RETURN 16 | #iptables -t nat -A GSNOVA -p tcp -j REDIRECT --to-ports 48100 17 | iptables -t nat -A GSNOVA -p tcp -j REDIRECT -s 192.168.1.5 --to-ports 48100 18 | iptables -t nat -I PREROUTING -p tcp -j GSNOVA 19 | 20 | 21 | # for openwrt 22 | #iptables -t nat -I PREROUTING -p tcp -j GSNOVA 23 | # for local linux 24 | #iptables -t nat -A OUTPUT -p tcp -j GSNOVA 25 | iptables -t mangle -N GSNOVA 26 | iptables -t mangle -A GSNOVA -p udp -m mark --mark 48100 -j RETURN 27 | iptables -t mangle -A GSNOVA -p udp --dport 53 -s 192.168.1.5 -j TPROXY --on-port 48101 --tproxy-mark 0x01/0x01 28 | #iptables -t mangle -A GSNOVA -p udp --dport 53 -s 192.168.1.75 -j TPROXY --on-port 48101 --tproxy-mark 0x01/0x01 29 | iptables -t mangle -A PREROUTING -j GSNOVA --------------------------------------------------------------------------------