mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 09:25: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
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
# custom
|
||||||
|
glider
|
||||||
|
doc/
|
||||||
|
111
README.md
111
README.md
@ -1,2 +1,113 @@
|
|||||||
# glider
|
# glider
|
||||||
glider is a forward proxy with several protocols support.
|
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