first commit

This commit is contained in:
nadoo 2017-07-13 21:55:41 +08:00
parent 78b7f8ead8
commit 668d5bc470
15 changed files with 1777 additions and 0 deletions

4
.gitignore vendored
View File

@ -12,3 +12,7 @@
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# custom
glider
doc/

111
README.md
View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}()
}
}