Skip to content

Instantly share code, notes, and snippets.

@ayanamist
Last active August 17, 2021 06:16
Show Gist options
  • Select an option

  • Save ayanamist/cb8f2e09fd2f4541052ae4739766acf1 to your computer and use it in GitHub Desktop.

Select an option

Save ayanamist/cb8f2e09fd2f4541052ae4739766acf1 to your computer and use it in GitHub Desktop.
FCM proxy
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"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,
Resolver: &net.Resolver{
PreferGo: true,
},
}
ciph, _ = ss.NewCipher("rc4-md5", "fcmproxy")
proxyStr string
proxyDial = func(network, addr string) (net.Conn, error) {
conn, err := dialer.Dial(network, addr)
if err == nil {
if c, ok := conn.(*net.TCPConn); ok {
c.SetNoDelay(true)
}
}
return conn, err
}
localPort uint
)
func newClientProxyFunc(srvAddrs []string) func(net.Conn) {
return func(localConn net.Conn) {
defer localConn.Close()
sniHost, localConn, err := sniServerNameFromConn(localConn)
if err != nil {
log.Printf("sniServerNameFromConn from %s error: %v", localConn.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, localPort)
}
if !strings.HasSuffix(host, fmt.Sprintf(".google.com:%d", localPort)) {
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()
}
func main() {
log.SetFlags(log.Lmicroseconds)
go sigQuitHandle()
flag.StringVar(&proxyStr, "proxy", "", "Set proxy url")
flag.UintVar(&localPort, "port", 5228, "Local port")
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
}
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)
}
}
@phoenixxie0
Copy link

当第一个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`

@ayanamist
Copy link
Author

看了下栈,没有我自己写的代码,都是三方库的代码,另外看了下报错的地方,不知道是什么go版本,go1.11这里不应该会报错。
实际在我自己环境里测了下,怎么重启也无法复现。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment