Last active
August 17, 2021 06:16
-
-
Save ayanamist/cb8f2e09fd2f4541052ae4739766acf1 to your computer and use it in GitHub Desktop.
FCM proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"errors" | |
"flag" | |
"fmt" | |
"io" | |
"log" | |
"net" | |
"net/http" | |
"net/url" | |
"os" | |
"os/signal" | |
"runtime" | |
"strings" | |
"syscall" | |
"time" | |
"github.com/hashicorp/yamux" | |
"github.com/polvi/sni" | |
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" | |
"golang.org/x/net/proxy" | |
) | |
const ( | |
connectTimeout = 30 * time.Second | |
) | |
var ( | |
dialer = &net.Dialer{ | |
Timeout: connectTimeout, | |
} | |
ciph *ss.Cipher | |
proxyDial = dialer.Dial | |
localPort uint | |
remotePort uint | |
) | |
// httpProxy is a HTTP/HTTPS connect proxy. | |
type httpProxy struct { | |
host string | |
haveAuth bool | |
username string | |
password string | |
forward proxy.Dialer | |
} | |
func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { | |
// Dial and create the https client connection. | |
c, err := s.forward.Dial("tcp", s.host) | |
if err != nil { | |
return nil, err | |
} | |
// HACK. http.ReadRequest also does this. | |
reqURL, err := url.Parse("http://" + addr) | |
if err != nil { | |
c.Close() | |
return nil, err | |
} | |
reqURL.Scheme = "" | |
req, err := http.NewRequest("CONNECT", reqURL.String(), nil) | |
if err != nil { | |
c.Close() | |
return nil, err | |
} | |
req.Close = false | |
if s.haveAuth { | |
req.SetBasicAuth(s.username, s.password) | |
} | |
err = req.Write(c) | |
if err != nil { | |
c.Close() | |
return nil, err | |
} | |
resp, err := http.ReadResponse(bufio.NewReader(c), req) | |
if err != nil { | |
if resp != nil { | |
resp.Body.Close() | |
} | |
c.Close() | |
return nil, err | |
} | |
resp.Body.Close() | |
if resp.StatusCode != 200 { | |
c.Close() | |
err = fmt.Errorf("connect server using proxy error %s", resp.Status) | |
return nil, err | |
} | |
return c, nil | |
} | |
func newClientProxyFunc(srvAddrs []string) func(net.Conn) { | |
return func(conn net.Conn) { | |
localConn := conn | |
defer localConn.Close() | |
sniHost, localConn, err := sniServerNameFromConn(localConn) | |
if err != nil { | |
log.Printf("sniServerNameFromConn from %s error: %v", conn.RemoteAddr(), err) | |
return | |
} | |
log.Printf("sniServernameFromConn got %s from %s", sniHost, localConn.RemoteAddr()) | |
var remoteConn net.Conn | |
connCh := make(chan net.Conn) | |
closeCh := make(chan struct{}) | |
connectTimer := time.NewTimer(2 * connectTimeout) | |
delayTimer := time.NewTimer(0) | |
LOOP: | |
for _, srvAddr := range srvAddrs { | |
select { | |
case <-delayTimer.C: | |
delayTimer.Reset(time.Second) | |
log.Printf("try serverConnect %s from %s", srvAddr, localConn.RemoteAddr()) | |
go serverConnect(localConn.RemoteAddr().String(), srvAddr, connCh, closeCh) | |
case remoteConn = <-connCh: | |
delayTimer.Stop() | |
close(closeCh) | |
break LOOP | |
} | |
} | |
if remoteConn == nil { | |
select { | |
case <-connectTimer.C: | |
log.Printf("remote conn not got, local conn: %s", localConn.RemoteAddr()) | |
close(closeCh) | |
return | |
case remoteConn = <-connCh: | |
close(closeCh) | |
} | |
} | |
connectTimer.Stop() | |
defer remoteConn.Close() | |
go relay(localConn, remoteConn) | |
relay(remoteConn, localConn) | |
} | |
} | |
func serverConnect(localAddr, remoteAddr string, connCh chan<- net.Conn, closeCh <-chan struct{}) { | |
remoteConn, err := proxyDial("tcp", remoteAddr) | |
if err != nil { | |
log.Printf("dial from %s to %s error: %v", localAddr, remoteAddr, err) | |
return | |
} | |
remoteConn = ss.NewConn(remoteConn, ciph.Copy()) | |
sess, _ := yamux.Client(remoteConn, yamux.DefaultConfig()) | |
if _, err := sess.Ping(); err != nil { | |
log.Printf("sess.Ping from %s to %s error: %v", localAddr, remoteAddr, err) | |
sess.Close() | |
return | |
} | |
stream, err := sess.Open() | |
if err != nil { | |
log.Printf("sess.Open from %s to %s error: %v", localAddr, remoteAddr, err) | |
sess.Close() | |
return | |
} | |
select { | |
case <-closeCh: | |
sess.Close() | |
case connCh <- stream: | |
} | |
} | |
func sniServerNameFromConn(conn net.Conn) (string, net.Conn, error) { | |
host, conn, err := sni.ServerNameFromConn(conn) | |
if err != nil { | |
return "", nil, fmt.Errorf("invalid sni: %v", err) | |
} | |
if !strings.Contains(host, ":") { | |
host = fmt.Sprintf("%s:%d", host, remotePort) | |
} | |
if !strings.Contains(host, ".google.com:") { | |
return "", nil, fmt.Errorf("not allowed host: %s", host) | |
} | |
return host, conn, nil | |
} | |
func serverProxyFunc(localConn net.Conn) { | |
defer localConn.Close() | |
localConn = ss.NewConn(localConn, ciph.Copy()) | |
sess, _ := yamux.Server(localConn, yamux.DefaultConfig()) | |
defer sess.Close() | |
localStream, err := sess.Accept() | |
if err != nil { | |
log.Printf("sess.Accept from %s error: %v", localConn.RemoteAddr(), err) | |
return | |
} | |
remoteAddr, localStream, err := sniServerNameFromConn(localStream) | |
if err != nil { | |
log.Printf("sniServerNameFromConn from %s error: %v", localConn.RemoteAddr(), err) | |
return | |
} | |
log.Printf("sniConnect got %s from %s", remoteAddr, localConn.RemoteAddr()) | |
remoteConn, err := dialer.Dial("tcp", remoteAddr) | |
if err != nil { | |
log.Printf("dial from %s to %s error: %v", localConn.RemoteAddr(), remoteAddr, err) | |
return | |
} | |
defer remoteConn.Close() | |
if c, ok := remoteConn.(*net.TCPConn); ok { | |
c.SetNoDelay(true) | |
} | |
go relay(localStream, remoteConn) | |
relay(remoteConn, localStream) | |
} | |
func sigQuitHandle() { | |
ch := make(chan os.Signal, 5) | |
signal.Notify(ch, syscall.SIGQUIT) | |
for range ch { | |
buf := make([]byte, 1024*1024) | |
n := runtime.Stack(buf, true) | |
buf = buf[:n] | |
log.Printf("SIGQUIT got, stack:\n%s", buf) | |
} | |
} | |
func relay(src, dst net.Conn) { | |
log.Printf("relay %s <-> %s start", src.RemoteAddr(), dst.RemoteAddr()) | |
n, err := io.Copy(dst, src) | |
log.Printf("relay %s <-> %s %d bytes, error: %v", src.RemoteAddr(), dst.RemoteAddr(), n, err) | |
src.Close() | |
dst.Close() | |
} | |
type ssProxyDialer struct { | |
server string | |
ciph *ss.Cipher | |
upstreamDialer proxy.Dialer | |
} | |
func (s *ssProxyDialer) Dial(network, addr string) (net.Conn, error) { | |
rawaddr, err := ss.RawAddr(addr) | |
if err != nil { | |
return nil, err | |
} | |
conn, err := s.upstreamDialer.Dial("tcp", s.server) | |
if err != nil { | |
return nil, err | |
} | |
if c, ok := conn.(*net.TCPConn); ok { | |
c.SetNoDelay(true) | |
} | |
ssConn := ss.NewConn(conn, s.ciph.Copy()) | |
if _, err := ssConn.Write(rawaddr); err != nil { | |
conn.Close() | |
return nil, err | |
} | |
log.Printf("dial %s via shadowsocks %s", addr, s.server) | |
return ssConn, nil | |
} | |
func ssDial(u *url.URL, d proxy.Dialer) (proxy.Dialer, error) { | |
if u.User == nil { | |
return nil, errors.New("no shadowsocks method") | |
} | |
method := u.User.Username() | |
p, _ := u.User.Password() | |
ciph, err := ss.NewCipher(method, p) | |
if err != nil { | |
return nil, err | |
} | |
log.Printf("using proxy ss://%s:%s@%s", method, p, u.Host) | |
return &ssProxyDialer{ | |
server: u.Host, | |
ciph: ciph, | |
upstreamDialer: d, | |
}, nil | |
} | |
func init() { | |
proxy.RegisterDialerType("http", func(u *url.URL, forward proxy.Dialer) (d proxy.Dialer, err error) { | |
s := new(httpProxy) | |
s.host = u.Host | |
s.forward = forward | |
if u.User != nil { | |
s.haveAuth = true | |
s.username = u.User.Username() | |
s.password, _ = u.User.Password() | |
} | |
return s, nil | |
}) | |
proxy.RegisterDialerType("ss", ssDial) | |
} | |
func main() { | |
log.SetFlags(log.Lmicroseconds) | |
go sigQuitHandle() | |
var ( | |
proxyStr string | |
shadowPass string | |
) | |
flag.StringVar(&proxyStr, "proxy", "", "Set proxy url") | |
flag.UintVar(&localPort, "port", 5228, "Local port") | |
flag.UintVar(&remotePort, "remote-port", 5228, "Remote port") | |
flag.StringVar(&shadowPass, "key", "fcmproxy", "Encrypt key") | |
flag.Parse() | |
if proxyStr != "" { | |
u, err := url.Parse(proxyStr) | |
if err != nil { | |
log.Fatalf("invalid proxy url: %v", err) | |
} | |
d, err := proxy.FromURL(u, dialer) | |
if err != nil { | |
log.Fatalf("parse proxy failed: %v", err) | |
} | |
proxyDial = d.Dial | |
} | |
ciph, _ = ss.NewCipher("rc4-md5", shadowPass) | |
srvAddr := flag.Args() | |
var proxyFunc func(net.Conn) | |
if len(srvAddr) > 0 { | |
proxyFunc = newClientProxyFunc(srvAddr) | |
log.Printf("working on client, servers: %v", srvAddr) | |
} else { | |
proxyFunc = serverProxyFunc | |
log.Printf("working on server") | |
} | |
l, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort)) | |
if err != nil { | |
log.Fatalf("listen: %v", err) | |
} | |
for { | |
conn, err := l.Accept() | |
if err != nil { | |
log.Fatalf("accept: %v", err) | |
} | |
if c, ok := conn.(*net.TCPConn); ok { | |
c.SetNoDelay(true) | |
} | |
go proxyFunc(conn) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Unit] | |
Description=Google DOH proxy | |
After=network.target | |
After=v2ray.service | |
[Service] | |
User=doh-proxy | |
Type=simple | |
Restart=always | |
EnvironmentFile=/etc/default/doh-proxy | |
ExecStart=/usr/local/bin/doh-proxy $ARGS | |
LimitNOFILE=65536 | |
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE | |
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE | |
NoNewPrivileges=true | |
[Install] | |
WantedBy=multi-user.target |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module test/golang/fcmproxy | |
go 1.16 | |
require ( | |
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect | |
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c | |
github.com/polvi/sni v0.0.0-20151108162443-a08e954206fd | |
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 | |
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect | |
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d | |
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= | |
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= | |
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY= | |
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= | |
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c h1:nqkErwUGfpZZMqj29WZ9U/wz2OpJVDuiokLhE/3Y7IQ= | |
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= | |
github.com/polvi/sni v0.0.0-20151108162443-a08e954206fd h1:moB2BJUF99MbSLkvr+Y++oiZvadc9ToYX4p5oHRMWlQ= | |
github.com/polvi/sni v0.0.0-20151108162443-a08e954206fd/go.mod h1:0tydZ0+WbhXnymEaRDY/DzO3GvKQAiYune6xvHRFzSw= | |
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ= | |
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY= | |
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= | |
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= | |
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= | |
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | |
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | |
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= | |
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= | |
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= | |
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= | |
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
看了下栈,没有我自己写的代码,都是三方库的代码,另外看了下报错的地方,不知道是什么go版本,go1.11这里不应该会报错。
实际在我自己环境里测了下,怎么重启也无法复现。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
当第一个server1重启的时候,境内端会出错退出。错误log
`use of closed network connection
00:42:56.916414 sniServernameFromConn got mtalk.google.com:5228 from 124.226.57.185:56500
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x46b809]
goroutine 27 [running]:
io.ReadAtLeast(0x0, 0x0, 0xc00001a6f0, 0x10, 0x10, 0x10, 0x53ef20, 0x523401, 0xc00001a6f0)
/usr/local/go/src/io/io.go:310 +0x59
io.ReadFull(0x0, 0x0, 0xc00001a6f0, 0x10, 0x10, 0x10, 0x43be4e, 0xc00002ef58)
/usr/local/go/src/io/io.go:329 +0x58
github.com/shadowsocks/shadowsocks-go/shadowsocks.(*Conn).Read(0xc000072a50, 0xc0000f5000, 0x1000, 0x1000, 0xc0000b6c00, 0x4, 0x5998b0)
/root/go/src/github.com/shadowsocks/shadowsocks-go/shadowsocks/conn.go:96 +0x21d
bufio.(*Reader).Read(0xc000055680, 0xc00001a6e0, 0xc, 0xc, 0xc, 0xc00001a6e0, 0x0)
/usr/local/go/src/bufio/bufio.go:216 +0x22f
io.ReadAtLeast(0x59a0a0, 0xc000055680, 0xc00001a6e0, 0xc, 0xc, 0xc, 0x53ef20, 0xc000055501, 0xc00001a6e0)
/usr/local/go/src/io/io.go:310 +0x88
io.ReadFull(0x59a0a0, 0xc000055680, 0xc00001a6e0, 0xc, 0xc, 0xc, 0x0, 0x0)
/usr/local/go/src/io/io.go:329 +0x58
github.com/hashicorp/yamux.(*Session).recvLoop(0xc0000da0b0, 0x0, 0x0)
/root/go/src/github.com/hashicorp/yamux/session.go:458 +0xef
github.com/hashicorp/yamux.(*Session).recv(0xc0000da0b0)
/root/go/src/github.com/hashicorp/yamux/session.go:437 +0x2b
created by github.com/hashicorp/yamux.newSession
/root/go/src/github.com/hashicorp/yamux/session.go:113 +0x2aa`