mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 01:15:41 +08:00
first commit
This commit is contained in:
parent
78b7f8ead8
commit
668d5bc470
4
.gitignore
vendored
4
.gitignore
vendored
@ -12,3 +12,7 @@
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# custom
|
||||
glider
|
||||
doc/
|
||||
|
111
README.md
111
README.md
@ -1,2 +1,113 @@
|
||||
# glider
|
||||
glider is a forward proxy with several protocols support.
|
||||
|
||||
## Install
|
||||
|
||||
go get -u github.com/nadoo/glider
|
||||
|
||||
## Build
|
||||
|
||||
cd $GOPATH/src/github.com/nadoo/glider
|
||||
go build
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
glider v0.1 usage:
|
||||
-checksite string
|
||||
proxy check address (default "www.apple.com:443")
|
||||
-duration int
|
||||
proxy check duration(seconds) (default 30)
|
||||
-f value
|
||||
forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]
|
||||
-l value
|
||||
listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT
|
||||
-s string
|
||||
forward strategy, default: rr (default "rr")
|
||||
-v verbose mode
|
||||
|
||||
Available Schemas:
|
||||
mixed: serve as a http/socks5 proxy on the same port. (default)
|
||||
ss: ss proxy
|
||||
socks5: socks5 proxy
|
||||
http: http proxy
|
||||
redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)
|
||||
tcptun: a simple tcp tunnel
|
||||
dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)
|
||||
|
||||
Available schemas for different modes:
|
||||
listen: mixed ss socks5 http redir tcptun dnstun
|
||||
forward: ss socks5 http
|
||||
|
||||
Available methods for ss:
|
||||
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20
|
||||
|
||||
Available forward strategies:
|
||||
rr: Round Robin mode
|
||||
ha: High Availability mode
|
||||
|
||||
Examples:
|
||||
glider -l :8443 -v
|
||||
-listen on :8443, serve as http/socks5 proxy on the same port.
|
||||
|
||||
glider -l ss://AEAD_CHACHA20_POLY1305:pass@:8443
|
||||
-listen on 0.0.0.0:8443 as a shadowsocks server.
|
||||
|
||||
glider -l socks5://:1080 -v
|
||||
-listen on :1080 as a socks5 proxy server, in verbose mode.
|
||||
|
||||
glider -l http://:8080 -f socks5://127.0.0.1:1080 -v
|
||||
-listen on :8080 as a http proxy server, forward all requests via socks5 server.
|
||||
|
||||
glider -l redir://:1081 -f ss://method:pass@1.1.1.1:443
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ss server.
|
||||
|
||||
glider -l tcptun://:80=2.2.2.2:80 -f ss://method:pass@1.1.1.1:443
|
||||
-listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.
|
||||
|
||||
glider -l socks5://:1080 -l http://:8080 -f ss://method:pass@1.1.1.1:443
|
||||
-listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.
|
||||
|
||||
glider -l redir://:1081 -l dnstun://:53=8.8.8.8:53 -f ss://method:pass@server1:port1,ss://method:pass@server2:port2
|
||||
-listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.
|
||||
|
||||
glider -l socks5://:1080 -f ss://method:pass@server1:port1 -f ss://method:pass@server2:port2 -s rr
|
||||
-listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.
|
||||
|
||||
glider -l mixed://:8443 -f ss://method:pass@server1:port1
|
||||
-listen on :8443, serve as http/socks5 proxy, forward requests via server1.
|
||||
|
||||
glider -l mixed://:8443?http=1.1.1.1:80 -f ss://method:pass@server1:port1
|
||||
-listen on :8443, serve as socks5 proxy, and forward all HTTP requests to 1.1.1.1:80.
|
||||
```
|
||||
|
||||
## Service
|
||||
```bash
|
||||
cd /etc/systemd/system/
|
||||
vim glider.service
|
||||
```
|
||||
|
||||
```bash
|
||||
[Unit]
|
||||
Description=glider
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/mkdir -p /run/glider
|
||||
ExecStartPre=/bin/chown nobody:nobody /run/glider
|
||||
ExecStart=/opt/glider/glider -l redir://:7070 -l dnstun://:5353=8.8.8.8:53 -f ss://AEAD_CHACHA20_POLY1305:pass@yourhost:8443
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
ExecStop=/bin/kill -INT $MAINPID
|
||||
Restart=always
|
||||
User=nobody
|
||||
Group=nobody
|
||||
UMask=0027
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl enable glider.service
|
||||
systemctl start glider.service
|
||||
```
|
||||
|
54
conn.go
Normal file
54
conn.go
Normal file
@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
r *bufio.Reader
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func newConn(c net.Conn) conn {
|
||||
return conn{bufio.NewReader(c), c}
|
||||
}
|
||||
|
||||
func newConnSize(c net.Conn, n int) conn {
|
||||
return conn{bufio.NewReaderSize(c, n), c}
|
||||
}
|
||||
|
||||
func (c conn) Peek(n int) ([]byte, error) {
|
||||
return c.r.Peek(n)
|
||||
}
|
||||
|
||||
func (c conn) Read(p []byte) (int, error) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
func relay(left, right net.Conn) (int64, int64, error) {
|
||||
type res struct {
|
||||
N int64
|
||||
Err error
|
||||
}
|
||||
ch := make(chan res)
|
||||
|
||||
go func() {
|
||||
n, err := io.Copy(right, left)
|
||||
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||
ch <- res{n, err}
|
||||
}()
|
||||
|
||||
n, err := io.Copy(left, right)
|
||||
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||
rs := <-ch
|
||||
|
||||
if err == nil {
|
||||
err = rs.Err
|
||||
}
|
||||
return n, rs.N, err
|
||||
}
|
24
direct.go
Normal file
24
direct.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "net"
|
||||
|
||||
// direct proxy
|
||||
type direct struct {
|
||||
Proxy
|
||||
}
|
||||
|
||||
// Direct proxy
|
||||
var Direct = &direct{Proxy: &proxy{addr: "127.0.0.1"}}
|
||||
|
||||
// Direct proxy always enabled
|
||||
func (d *direct) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *direct) Dial(network, addr string) (net.Conn, error) {
|
||||
c, err := net.Dial(network, addr)
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
return c, err
|
||||
}
|
81
dnstun.go
Normal file
81
dnstun.go
Normal file
@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
)
|
||||
|
||||
type dnstun struct {
|
||||
Proxy
|
||||
addr string
|
||||
raddr string
|
||||
}
|
||||
|
||||
// DNSTunProxy returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr
|
||||
func DNSTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
||||
s := &dnstun{
|
||||
Proxy: upProxy,
|
||||
addr: addr,
|
||||
raddr: raddr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe redirected requests as a server.
|
||||
func (s *dnstun) ListenAndServe() {
|
||||
l, err := net.ListenPacket("udp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening UDP on %s", s.addr)
|
||||
|
||||
for {
|
||||
defer l.Close()
|
||||
|
||||
data := make([]byte, 512)
|
||||
n, clientAddr, err := l.ReadFrom(data)
|
||||
if err != nil {
|
||||
logf("DNS local read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
data = data[:n]
|
||||
go func() {
|
||||
rc, err := s.GetProxy().Dial("tcp", s.raddr)
|
||||
if err != nil {
|
||||
logf("failed to connect to server %v: %v", s.raddr, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
logf("proxy-dnstun %s[dns.udp] <-> %s[dns.tcp]", clientAddr.String(), s.raddr)
|
||||
|
||||
// 2 bytes length after tcp header, before dns message
|
||||
length := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(length, uint16(len(data)))
|
||||
rc.Write(length)
|
||||
rc.Write(data)
|
||||
|
||||
buf, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
logf("error in ioutil.ReadAll: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// length is not needed in udp dns response. (2 bytes)
|
||||
// SEE RFC1035, section 4.2.2 TCP: The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field.
|
||||
if len(buf) > 2 {
|
||||
msg := buf[2:]
|
||||
_, err = l.WriteTo(msg, clientAddr)
|
||||
if err != nil {
|
||||
logf("error in local write: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
250
http.go
Normal file
250
http.go
Normal file
@ -0,0 +1,250 @@
|
||||
// http proxy
|
||||
// NOTE: never keep-alive so the implementation can be much easier.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// httpproxy
|
||||
type httpproxy struct {
|
||||
Proxy
|
||||
addr string
|
||||
}
|
||||
|
||||
// HTTPProxy returns a http proxy.
|
||||
func HTTPProxy(addr string, upProxy Proxy) (Proxy, error) {
|
||||
s := &httpproxy{
|
||||
Proxy: upProxy,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe .
|
||||
func (s *httpproxy) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve .
|
||||
func (s *httpproxy) Serve(c net.Conn) {
|
||||
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
reqR := bufio.NewReader(c)
|
||||
reqTP := textproto.NewReader(reqR)
|
||||
method, requestURI, proto, ok := parseFirstLine(reqTP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if method == "CONNECT" {
|
||||
s.servHTTPS(method, requestURI, proto, c)
|
||||
return
|
||||
}
|
||||
|
||||
reqHeader, err := reqTP.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
logf("read header error:%s", err)
|
||||
return
|
||||
}
|
||||
cleanHeaders(reqHeader)
|
||||
// tell the remote server not to keep alive
|
||||
reqHeader.Set("Connection", "close")
|
||||
|
||||
url, err := url.ParseRequestURI(requestURI)
|
||||
if err != nil {
|
||||
logf("parse request url error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var tgt = url.Host
|
||||
if !strings.Contains(url.Host, ":") {
|
||||
tgt += ":80"
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy().Dial("tcp", tgt)
|
||||
if err != nil {
|
||||
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto)
|
||||
logf("failed to dial: %v", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// GET http://example.com/a/index.htm HTTP/1.1 -->
|
||||
// GET /a/index.htm HTTP/1.1
|
||||
url.Scheme = ""
|
||||
url.Host = ""
|
||||
uri := url.String()
|
||||
|
||||
var reqBuf bytes.Buffer
|
||||
writeFirstLine(method, uri, proto, &reqBuf)
|
||||
writeHeaders(reqHeader, &reqBuf)
|
||||
|
||||
// send request to remote server
|
||||
rc.Write(reqBuf.Bytes())
|
||||
|
||||
// copy the left request bytes to remote server. eg. length specificed or chunked body.
|
||||
go func() {
|
||||
io.Copy(rc, reqR)
|
||||
rc.SetDeadline(time.Now())
|
||||
c.SetDeadline(time.Now())
|
||||
}()
|
||||
|
||||
respR := bufio.NewReader(rc)
|
||||
respTP := textproto.NewReader(respR)
|
||||
proto, code, status, ok := parseFirstLine(respTP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
respHeader, err := respTP.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
logf("read header error:%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
respHeader.Set("Proxy-Connection", "close")
|
||||
respHeader.Set("Connection", "close")
|
||||
|
||||
var respBuf bytes.Buffer
|
||||
writeFirstLine(proto, code, status, &respBuf)
|
||||
writeHeaders(respHeader, &respBuf)
|
||||
|
||||
logf("proxy-http %s <-> %s", c.RemoteAddr(), tgt)
|
||||
c.Write(respBuf.Bytes())
|
||||
|
||||
io.Copy(c, respR)
|
||||
|
||||
}
|
||||
|
||||
func (s *httpproxy) servHTTPS(method, requestURI, proto string, c net.Conn) {
|
||||
rc, err := s.GetProxy().Dial("tcp", requestURI)
|
||||
if err != nil {
|
||||
c.Write([]byte(proto))
|
||||
c.Write([]byte(" 502 ERROR\r\n\r\n"))
|
||||
logf("failed to dial: %v", err)
|
||||
return
|
||||
}
|
||||
c.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n"))
|
||||
|
||||
logf("proxy-https %s <-> %s", c.RemoteAddr(), requestURI)
|
||||
|
||||
_, _, err = relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
logf("relay error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *httpproxy) Dial(network, addr string) (net.Conn, error) {
|
||||
c, err := s.GetProxy().Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c.Write([]byte("CONNECT " + addr + " HTTP/1.0\r\n"))
|
||||
// c.Write([]byte("Proxy-Connection: Keep-Alive\r\n"))
|
||||
|
||||
var b [1024]byte
|
||||
n, err := c.Read(b[:])
|
||||
if bytes.Contains(b[:n], []byte("200")) {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts.
|
||||
func parseFirstLine(tp *textproto.Reader) (r1, r2, r3 string, ok bool) {
|
||||
line, err := tp.ReadLine()
|
||||
// logf("first line: %s", line)
|
||||
if err != nil {
|
||||
logf("read request line error:%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
func cleanHeaders(header textproto.MIMEHeader) {
|
||||
header.Del("Proxy-Connection")
|
||||
header.Del("Connection")
|
||||
header.Del("Keep-Alive")
|
||||
header.Del("Proxy-Authenticate")
|
||||
header.Del("Proxy-Authorization")
|
||||
header.Del("TE")
|
||||
header.Del("Trailers")
|
||||
header.Del("Transfer-Encoding")
|
||||
header.Del("Upgrade")
|
||||
}
|
||||
|
||||
func writeFirstLine(s1, s2, s3 string, buf *bytes.Buffer) {
|
||||
buf.Write([]byte(s1))
|
||||
buf.Write([]byte(" "))
|
||||
buf.Write([]byte(s2))
|
||||
buf.Write([]byte(" "))
|
||||
buf.Write([]byte(s3))
|
||||
buf.Write([]byte("\r\n"))
|
||||
}
|
||||
|
||||
func writeHeaders(header textproto.MIMEHeader, buf *bytes.Buffer) {
|
||||
for key, values := range header {
|
||||
buf.Write([]byte(key))
|
||||
buf.Write([]byte(": "))
|
||||
for k, v := range values {
|
||||
buf.Write([]byte(v))
|
||||
if k > 0 {
|
||||
buf.Write([]byte(" "))
|
||||
}
|
||||
}
|
||||
buf.Write([]byte("\r\n"))
|
||||
}
|
||||
|
||||
//header ended
|
||||
buf.Write([]byte("\r\n"))
|
||||
}
|
165
main.go
Normal file
165
main.go
Normal file
@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const version = "0.1"
|
||||
|
||||
var config struct {
|
||||
Verbose bool
|
||||
Strategy string
|
||||
CheckSite string
|
||||
CheckDuration int
|
||||
}
|
||||
|
||||
func logf(f string, v ...interface{}) {
|
||||
if config.Verbose {
|
||||
log.Printf(f, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
app := os.Args[0]
|
||||
fmt.Fprintf(os.Stderr, "%s v%s usage:\n", app, version)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available Schemas:\n")
|
||||
fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n")
|
||||
fmt.Fprintf(os.Stderr, " ss: ss proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " http: http proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n")
|
||||
fmt.Fprintf(os.Stderr, " tcptun: a simple tcp tunnel\n")
|
||||
fmt.Fprintf(os.Stderr, " dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available schemas for different modes:\n")
|
||||
fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir tcptun dnstun\n")
|
||||
fmt.Fprintf(os.Stderr, " forward: ss socks5 http\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available methods for ss:\n")
|
||||
fmt.Fprintf(os.Stderr, " "+ListCipher())
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available forward strategies:\n")
|
||||
fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n")
|
||||
fmt.Fprintf(os.Stderr, " ha: High Availability mode\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l :8443 -v\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l ss://AEAD_CHACHA20_POLY1305:pass@:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a shadowsocks server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -v\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l http://:8080 -f socks5://127.0.0.1:1080 -v\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l redir://:1081 -f ss://method:pass@1.1.1.1:443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l tcptun://:80=2.2.2.2:80 -f ss://method:pass@1.1.1.1:443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -l http://:8080 -f ss://method:pass@1.1.1.1:443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l redir://:1081 -l dnstun://:53=8.8.8.8:53 -f ss://method:pass@server1:port1,ss://method:pass@server2:port2\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l socks5://:1080 -f ss://method:pass@server1:port1 -f ss://method:pass@server2:port2 -s rr\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l mixed://:8443 -f ss://method:pass@server1:port1\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy, forward requests via server1.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -l mixed://:8443?http=1.1.1.1:80 -f ss://method:pass@server1:port1\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as socks5 proxy, and forward all HTTP requests to 1.1.1.1:80.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
}
|
||||
|
||||
type arrFlags []string
|
||||
|
||||
// implement flag.Value interface
|
||||
func (i *arrFlags) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// implement flag.Value interface
|
||||
func (i *arrFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var flags struct {
|
||||
Listen arrFlags
|
||||
Forward arrFlags
|
||||
}
|
||||
|
||||
flag.BoolVar(&config.Verbose, "v", false, "verbose mode")
|
||||
flag.StringVar(&config.Strategy, "s", "rr", "forward strategy, default: rr")
|
||||
flag.StringVar(&config.CheckSite, "checksite", "www.apple.com:443", "proxy check address")
|
||||
flag.IntVar(&config.CheckDuration, "duration", 30, "proxy check duration(seconds)")
|
||||
|
||||
flag.Var(&flags.Listen, "l", "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT")
|
||||
flag.Var(&flags.Forward, "f", "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]")
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if len(flags.Listen) == 0 {
|
||||
flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||
return
|
||||
}
|
||||
|
||||
var forwarders []Proxy
|
||||
if len(flags.Forward) > 0 {
|
||||
var err error
|
||||
for _, chain := range flags.Forward {
|
||||
var forward Proxy
|
||||
for _, url := range strings.Split(chain, ",") {
|
||||
forward, err = ProxyFromURL(url, forward)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
forwarders = append(forwarders, forward)
|
||||
}
|
||||
}
|
||||
|
||||
for _, forward := range forwarders {
|
||||
go check(forward, config.CheckSite, config.CheckDuration)
|
||||
}
|
||||
|
||||
if len(flags.Listen) > 0 {
|
||||
for _, listen := range flags.Listen {
|
||||
local, err := ProxyFromURL(listen, forwarders...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go local.ListenAndServe()
|
||||
}
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
}
|
105
mixed.go
Normal file
105
mixed.go
Normal file
@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
)
|
||||
|
||||
// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase.
|
||||
var httpMethods = [][]byte{
|
||||
[]byte("GET"),
|
||||
[]byte("POST"),
|
||||
[]byte("PUT"),
|
||||
[]byte("DELETE"),
|
||||
[]byte("CONNECT"),
|
||||
[]byte("HEAD"),
|
||||
[]byte("OPTIONS"),
|
||||
[]byte("TRACE"),
|
||||
}
|
||||
|
||||
// mixedproxy
|
||||
type mixedproxy struct {
|
||||
Proxy
|
||||
http Proxy
|
||||
socks5 Proxy
|
||||
ss Proxy
|
||||
}
|
||||
|
||||
// MixedProxy returns a http mixed proxy.
|
||||
func MixedProxy(network, addr, user, pass string, upProxy Proxy) (Proxy, error) {
|
||||
p := &mixedproxy{
|
||||
Proxy: upProxy,
|
||||
}
|
||||
|
||||
p.http, _ = HTTPProxy(addr, upProxy)
|
||||
p.socks5, _ = SOCKS5Proxy(network, addr, user, pass, upProxy)
|
||||
|
||||
if user != "" && pass != "" {
|
||||
p.ss, _ = SSProxy(user, pass, upProxy)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// mixedproxy .
|
||||
func (p *mixedproxy) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", p.Addr())
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", p.Addr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", p.Addr())
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c := newConn(c)
|
||||
|
||||
if p.socks5 != nil {
|
||||
head, err := c.Peek(1)
|
||||
if err != nil {
|
||||
logf("peek error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// check socks5, client send socksversion: 5 as the first byte
|
||||
if head[0] == socks5Version {
|
||||
p.socks5.Serve(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.http != nil {
|
||||
head, err := c.Peek(8)
|
||||
if err != nil {
|
||||
logf("peek error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, method := range httpMethods {
|
||||
if bytes.HasPrefix(head, method) {
|
||||
p.http.Serve(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.ss != nil {
|
||||
p.ss.Serve(c)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
152
proxy.go
Normal file
152
proxy.go
Normal file
@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Proxy means to establish a connection and relay it.
|
||||
type Proxy interface {
|
||||
// ListenAndServe as proxy server, use only in server mode.
|
||||
ListenAndServe()
|
||||
|
||||
// Serve as proxy server, use only in server mode.
|
||||
Serve(c net.Conn)
|
||||
|
||||
// Get address
|
||||
Addr() string
|
||||
|
||||
// Get current proxy
|
||||
CurrentProxy() Proxy
|
||||
|
||||
// Get a proxy according to the strategy
|
||||
GetProxy() Proxy
|
||||
|
||||
// Switch to the next proxy
|
||||
NextProxy() Proxy
|
||||
|
||||
// Get the status of proxy
|
||||
Enabled() bool
|
||||
|
||||
// Set the proxy status
|
||||
SetEnable(enable bool)
|
||||
|
||||
// Dial connects to the given address via the proxy.
|
||||
Dial(network, addr string) (c net.Conn, err error)
|
||||
}
|
||||
|
||||
// proxy
|
||||
type proxy struct {
|
||||
addr string
|
||||
forward Proxy
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// newProxy .
|
||||
func newProxy(addr string, forward Proxy) Proxy {
|
||||
if forward == nil {
|
||||
forward = Direct
|
||||
}
|
||||
|
||||
return &proxy{addr: addr, forward: forward, enabled: false}
|
||||
}
|
||||
|
||||
func (p *proxy) ListenAndServe() { logf("base proxy ListenAndServe") }
|
||||
func (p *proxy) Serve(c net.Conn) { logf("base proxy Serve") }
|
||||
func (p *proxy) CurrentProxy() Proxy { return p.forward }
|
||||
func (p *proxy) GetProxy() Proxy { return p.forward }
|
||||
func (p *proxy) NextProxy() Proxy { return p.forward }
|
||||
func (p *proxy) Enabled() bool { return p.enabled }
|
||||
func (p *proxy) SetEnable(enable bool) { p.enabled = enable }
|
||||
func (p *proxy) Addr() string { return p.addr }
|
||||
|
||||
func (p *proxy) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.forward.Dial(network, addr)
|
||||
}
|
||||
|
||||
// ProxyFromURL parses url and get a Proxy
|
||||
// TODO: table
|
||||
func ProxyFromURL(s string, forwarders ...Proxy) (Proxy, error) {
|
||||
if !strings.Contains(s, "://") {
|
||||
s = "mixed://" + s
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
logf("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
var user, pass string
|
||||
if u.User != nil {
|
||||
user = u.User.Username()
|
||||
pass, _ = u.User.Password()
|
||||
}
|
||||
|
||||
var proxy Proxy
|
||||
if forwarders == nil || len(forwarders) == 0 {
|
||||
proxy = newProxy(addr, Direct)
|
||||
} else if len(forwarders) == 1 {
|
||||
proxy = newProxy(addr, forwarders[0])
|
||||
} else if len(forwarders) > 1 {
|
||||
switch config.Strategy {
|
||||
case "rr":
|
||||
proxy = newRRProxy(addr, forwarders)
|
||||
logf("forward to remote servers in round robin mode.")
|
||||
case "ha":
|
||||
proxy = newHAProxy(addr, forwarders)
|
||||
logf("forward to remote servers in high availability mode.")
|
||||
default:
|
||||
logf("not supported forward mode '%s', just use the first forward server.", config.Strategy)
|
||||
proxy = newProxy(addr, forwarders[0])
|
||||
}
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "ss":
|
||||
p, err := SSProxy(user, pass, proxy)
|
||||
return p, err
|
||||
case "socks5":
|
||||
return SOCKS5Proxy("tcp", addr, user, pass, proxy)
|
||||
case "redir":
|
||||
return RedirProxy(addr, proxy)
|
||||
case "tcptun":
|
||||
d := strings.Split(addr, "=")
|
||||
return TCPTunProxy(d[0], d[1], proxy)
|
||||
case "dnstun":
|
||||
d := strings.Split(addr, "=")
|
||||
return DNSTunProxy(d[0], d[1], proxy)
|
||||
case "http":
|
||||
return HTTPProxy(addr, proxy)
|
||||
case "mixed":
|
||||
return MixedProxy("tcp", addr, user, pass, proxy)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||
}
|
||||
|
||||
// Check proxy
|
||||
func check(p Proxy, target string, duration int) {
|
||||
firstTime := true
|
||||
for {
|
||||
if !firstTime {
|
||||
time.Sleep(time.Duration(duration) * time.Second)
|
||||
}
|
||||
firstTime = false
|
||||
|
||||
c, err := p.Dial("tcp", target)
|
||||
if err != nil {
|
||||
logf("proxy %s check error: %s, set to disabled.", p.Addr(), err)
|
||||
p.SetEnable(false)
|
||||
continue
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
p.SetEnable(true)
|
||||
logf("proxy %s check ok.", p.Addr())
|
||||
}
|
||||
}
|
114
redir.go
Normal file
114
redir.go
Normal file
@ -0,0 +1,114 @@
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const SO_ORIGINAL_DST = 80
|
||||
|
||||
type redir struct {
|
||||
Proxy
|
||||
addr string
|
||||
}
|
||||
|
||||
// RedirProxy returns a redirect proxy.
|
||||
func RedirProxy(addr string, upProxy Proxy) (Proxy, error) {
|
||||
s := &redir{
|
||||
Proxy: upProxy,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe redirected requests as a server.
|
||||
func (s *redir) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
tgt, c, err := getOriginalDstAddr(c)
|
||||
if err != nil {
|
||||
logf("failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy().Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
logf("proxy-redir %s <-> %s", c.RemoteAddr(), tgt)
|
||||
// go io.Copy(rc, c)
|
||||
// io.Copy(c, rc)
|
||||
|
||||
_, _, err = relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
logf("relay error: %v", err)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func getOriginalDstAddr(conn net.Conn) (addr net.Addr, c *net.TCPConn, err error) {
|
||||
defer conn.Close()
|
||||
|
||||
fc, err := conn.(*net.TCPConn).File()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fc.Close()
|
||||
|
||||
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// only ipv4 support
|
||||
ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7])
|
||||
port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3])
|
||||
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cc, err := net.FileConn(fc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, ok := cc.(*net.TCPConn)
|
||||
if !ok {
|
||||
err = errors.New("not a TCP connection")
|
||||
}
|
||||
return
|
||||
}
|
17
redir_win.go
Normal file
17
redir_win.go
Normal file
@ -0,0 +1,17 @@
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import "log"
|
||||
|
||||
type redir struct{ Proxy }
|
||||
|
||||
// RedirProxy returns a redirect proxy.
|
||||
func RedirProxy(addr string, upProxy Proxy) (Proxy, error) {
|
||||
return &redir{Proxy: upProxy}, nil
|
||||
}
|
||||
|
||||
// ListenAndServe redirected requests as a server.
|
||||
func (s *redir) ListenAndServe() {
|
||||
log.Fatal("redir not supported on windows")
|
||||
}
|
436
socks5.go
Normal file
436
socks5.go
Normal file
@ -0,0 +1,436 @@
|
||||
// socks5 client:
|
||||
// https://github.com/golang/net/tree/master/proxy
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// socks5 server:
|
||||
// https://github.com/shadowsocks/go-shadowsocks2/tree/master/socks
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const socks5Version = 5
|
||||
|
||||
const (
|
||||
socks5AuthNone = 0
|
||||
socks5AuthPassword = 2
|
||||
)
|
||||
|
||||
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||
const (
|
||||
socks5Connect = 1
|
||||
socks5Bind = 2
|
||||
socks5UDPAssociate = 3
|
||||
)
|
||||
|
||||
// SOCKS address types as defined in RFC 1928 section 5.
|
||||
const (
|
||||
socks5IP4 = 1
|
||||
socks5Domain = 3
|
||||
socks5IP6 = 4
|
||||
)
|
||||
|
||||
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||
const MaxAddrLen = 1 + 1 + 255 + 2
|
||||
|
||||
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
||||
type Addr []byte
|
||||
|
||||
var socks5Errors = []string{
|
||||
"",
|
||||
"general failure",
|
||||
"connection forbidden",
|
||||
"network unreachable",
|
||||
"host unreachable",
|
||||
"connection refused",
|
||||
"TTL expired",
|
||||
"command not supported",
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
type socks5 struct {
|
||||
Proxy
|
||||
network, addr string
|
||||
user, password string
|
||||
}
|
||||
|
||||
// SOCKS5Proxy returns a Proxy that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928.
|
||||
func SOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (Proxy, error) {
|
||||
s := &socks5{
|
||||
Proxy: upProxy,
|
||||
addr: addr,
|
||||
user: user,
|
||||
password: pass,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *socks5) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *socks5) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
tgt, err := s.handshake(c)
|
||||
if err != nil {
|
||||
logf("failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy().Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
logf("proxy-socks5 %s <-> %s", c.RemoteAddr(), tgt)
|
||||
|
||||
_, _, err = relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
logf("relay error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||
}
|
||||
|
||||
c, err := s.GetProxy().Dial(s.network, s.addr)
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
if err := s.connect(c, addr); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *socks5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, socks5Version)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword)
|
||||
} else {
|
||||
buf = append(buf, 1 /* num auth methods */, socks5AuthNone)
|
||||
}
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
if buf[1] == socks5AuthPassword {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, 1 /* password protocol version */)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
|
||||
buf = buf[:0]
|
||||
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */)
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
buf = append(buf, socks5IP4)
|
||||
ip = ip4
|
||||
} else {
|
||||
buf = append(buf, socks5IP6)
|
||||
}
|
||||
buf = append(buf, ip...)
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return errors.New("proxy: destination hostname too long: " + host)
|
||||
}
|
||||
buf = append(buf, socks5Domain)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(socks5Errors) {
|
||||
failure = socks5Errors[buf[1]]
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case socks5IP4:
|
||||
bytesToDiscard = net.IPv4len
|
||||
case socks5IP6:
|
||||
bytesToDiscard = net.IPv6len
|
||||
case socks5Domain:
|
||||
_, err := io.ReadFull(conn, buf[:1])
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
// Also need to discard the port number
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
||||
func (s *socks5) handshake(rw io.ReadWriter) (Addr, error) {
|
||||
// Read RFC 1928 for request and reply structure and sizes.
|
||||
buf := make([]byte, MaxAddrLen)
|
||||
// read VER, NMETHODS, METHODS
|
||||
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nmethods := buf[1]
|
||||
if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// write VER METHOD
|
||||
if _, err := rw.Write([]byte{5, 0}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||
if _, err := io.ReadFull(rw, buf[:3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf[1] != socks5Connect {
|
||||
return nil, errors.New(socks5Errors[7])
|
||||
}
|
||||
addr, err := readAddr(rw, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// write VER REP RSV ATYP BND.ADDR BND.PORT
|
||||
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
|
||||
return addr, err
|
||||
}
|
||||
|
||||
// String serializes SOCKS address a to string form.
|
||||
func (a Addr) String() string {
|
||||
var host, port string
|
||||
|
||||
switch a[0] { // address type
|
||||
case socks5Domain:
|
||||
host = string(a[2 : 2+int(a[1])])
|
||||
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
|
||||
case socks5IP4:
|
||||
host = net.IP(a[1 : 1+net.IPv4len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
|
||||
case socks5IP6:
|
||||
host = net.IP(a[1 : 1+net.IPv6len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
|
||||
}
|
||||
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
func readAddr(r io.Reader, b []byte) (Addr, error) {
|
||||
if len(b) < MaxAddrLen {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case socks5Domain:
|
||||
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
|
||||
return b[:1+1+int(b[1])+2], err
|
||||
case socks5IP4:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
|
||||
return b[:1+net.IPv4len+2], err
|
||||
case socks5IP6:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
|
||||
return b[:1+net.IPv6len+2], err
|
||||
}
|
||||
|
||||
return nil, errors.New(socks5Errors[8])
|
||||
}
|
||||
|
||||
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
||||
func ReadAddr(r io.Reader) (Addr, error) {
|
||||
return readAddr(r, make([]byte, MaxAddrLen))
|
||||
}
|
||||
|
||||
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
||||
func SplitAddr(b []byte) Addr {
|
||||
addrLen := 1
|
||||
if len(b) < addrLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case socks5Domain:
|
||||
if len(b) < 2 {
|
||||
return nil
|
||||
}
|
||||
addrLen = 1 + 1 + int(b[1]) + 2
|
||||
case socks5IP4:
|
||||
addrLen = 1 + net.IPv4len + 2
|
||||
case socks5IP6:
|
||||
addrLen = 1 + net.IPv6len + 2
|
||||
default:
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
if len(b) < addrLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
return b[:addrLen]
|
||||
}
|
||||
|
||||
// ParseAddr parses the address in string s. Returns nil if failed.
|
||||
func ParseAddr(s string) Addr {
|
||||
var addr Addr
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = make([]byte, 1+net.IPv4len+2)
|
||||
addr[0] = socks5IP4
|
||||
copy(addr[1:], ip4)
|
||||
} else {
|
||||
addr = make([]byte, 1+net.IPv6len+2)
|
||||
addr[0] = socks5IP6
|
||||
copy(addr[1:], ip)
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return nil
|
||||
}
|
||||
addr = make([]byte, 1+1+len(host)+2)
|
||||
addr[0] = socks5Domain
|
||||
addr[1] = byte(len(host))
|
||||
copy(addr[2:], host)
|
||||
}
|
||||
|
||||
portnum, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum)
|
||||
|
||||
return addr
|
||||
}
|
116
ss.go
Normal file
116
ss.go
Normal file
@ -0,0 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
// Shadowsocks
|
||||
type shadowsocks struct {
|
||||
Proxy
|
||||
core.StreamConnCipher
|
||||
}
|
||||
|
||||
// SSProxy returns a shadowsocks proxy.
|
||||
func SSProxy(method, pass string, upProxy Proxy) (Proxy, error) {
|
||||
ciph, err := core.PickCipher(method, nil, pass)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := &shadowsocks{
|
||||
Proxy: upProxy,
|
||||
StreamConnCipher: ciph,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe shadowsocks requests as a server.
|
||||
func (s *shadowsocks) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.Addr())
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.Addr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", s.Addr())
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *shadowsocks) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c = s.StreamConnCipher.StreamConn(c)
|
||||
|
||||
tgt, err := ReadAddr(c)
|
||||
if err != nil {
|
||||
logf("failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy().Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
logf("proxy-ss %s <-> %s", c.RemoteAddr(), tgt)
|
||||
|
||||
_, _, err = relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
logf("relay error: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *shadowsocks) Dial(network, addr string) (net.Conn, error) {
|
||||
target := ParseAddr(addr)
|
||||
if target == nil {
|
||||
return nil, errors.New("Unable to parse address: " + addr)
|
||||
}
|
||||
|
||||
c, err := s.GetProxy().Dial("tcp", s.Addr())
|
||||
if err != nil {
|
||||
logf("dial to %s error: %s", s.Addr(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c = s.StreamConn(c)
|
||||
if _, err = c.Write(target); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// ListCipher .
|
||||
func ListCipher() string {
|
||||
return strings.Join(core.ListCipher(), " ")
|
||||
}
|
82
strategy.go
Normal file
82
strategy.go
Normal file
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// strategyProxy
|
||||
type strategyProxy struct {
|
||||
addr string
|
||||
forwarders []Proxy
|
||||
idx int
|
||||
}
|
||||
|
||||
// newStrategyProxy .
|
||||
func newStrategyProxy(addr string, forwarders []Proxy) Proxy {
|
||||
if len(forwarders) == 0 {
|
||||
return Direct
|
||||
} else if len(forwarders) == 1 {
|
||||
return newProxy(addr, forwarders[0])
|
||||
}
|
||||
|
||||
return &strategyProxy{addr: addr, forwarders: forwarders}
|
||||
}
|
||||
|
||||
func (p *strategyProxy) ListenAndServe() {}
|
||||
func (p *strategyProxy) Serve(c net.Conn) {}
|
||||
func (p *strategyProxy) CurrentProxy() Proxy { return p.forwarders[p.idx] }
|
||||
func (p *strategyProxy) GetProxy() Proxy { return p.NextProxy() }
|
||||
|
||||
func (p *strategyProxy) NextProxy() Proxy {
|
||||
n := len(p.forwarders)
|
||||
if n == 1 {
|
||||
return p.forwarders[0]
|
||||
}
|
||||
|
||||
p.idx = (p.idx + 1) % n
|
||||
if !p.forwarders[p.idx].Enabled() {
|
||||
return p.NextProxy()
|
||||
}
|
||||
|
||||
return p.forwarders[p.idx]
|
||||
}
|
||||
|
||||
func (p *strategyProxy) Enabled() bool { return true }
|
||||
func (p *strategyProxy) SetEnable(enable bool) {}
|
||||
|
||||
func (p *strategyProxy) Check(proxy Proxy, target string, duration time.Duration) {}
|
||||
|
||||
func (p *strategyProxy) Addr() string { return p.addr }
|
||||
|
||||
func (p *strategyProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
return p.NextProxy().Dial(network, addr)
|
||||
}
|
||||
|
||||
// round robin proxy
|
||||
type rrproxy struct {
|
||||
Proxy
|
||||
}
|
||||
|
||||
// newRRProxy .
|
||||
func newRRProxy(addr string, forwarders []Proxy) Proxy {
|
||||
return newStrategyProxy(addr, forwarders)
|
||||
}
|
||||
|
||||
// high availability proxy
|
||||
type haproxy struct {
|
||||
Proxy
|
||||
}
|
||||
|
||||
// newHAProxy .
|
||||
func newHAProxy(addr string, forwarders []Proxy) Proxy {
|
||||
return newStrategyProxy(addr, forwarders)
|
||||
}
|
||||
|
||||
func (p *haproxy) GetProxy() Proxy {
|
||||
proxy := p.CurrentProxy()
|
||||
if proxy.Enabled() == false {
|
||||
return p.NextProxy()
|
||||
}
|
||||
return proxy
|
||||
}
|
66
tcptun.go
Normal file
66
tcptun.go
Normal file
@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import "net"
|
||||
|
||||
type tcptun struct {
|
||||
Proxy
|
||||
addr string
|
||||
raddr string
|
||||
}
|
||||
|
||||
// TCPTunProxy returns a redirect proxy.
|
||||
func TCPTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
||||
s := &tcptun{
|
||||
Proxy: upProxy,
|
||||
addr: addr,
|
||||
raddr: raddr,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ListenAndServe redirected requests as a server.
|
||||
func (s *tcptun) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
logf("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logf("listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
logf("failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
rc, err := s.GetProxy().Dial("tcp", s.raddr)
|
||||
if err != nil {
|
||||
|
||||
logf("failed to connect to target: %v", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
logf("proxy-tcptun %s <-> %s", c.RemoteAddr(), s.raddr)
|
||||
|
||||
_, _, err = relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
logf("relay error: %v", err)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user