├── HISTORY.md ├── README.md ├── build.bat ├── clientconnect.go ├── config.go ├── config.toml ├── iobind.go ├── main.go ├── natserver.go ├── publicserver.go ├── tcpproxy.go ├── version.go └── zip.exe /HISTORY.md: -------------------------------------------------------------------------------- 1 | # v1.1 - Mar 18, 2018 2 | * 解决ID冲突的问题 https://github.com/LubyRuffy/tcptunnel/issues/3 3 | * 解决ID不存在不提示的问题 https://github.com/LubyRuffy/tcptunnel/issues/2 4 | 5 | # v1.0 - Feb 9, 2018 6 | 经过了几周的测试,稳定运行。 7 | * 支持任意tcp端口 8 | * 支持HTTP协议 9 | 10 | # v0.1 - Jan 13, 2018 11 | alpha -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tcptunnel 2 | 用于两种场景: 3 | 1. 直接的端口转发,这个好理解 4 | 2. 做内网服务器到公网的映射访问,用于解决内网服务器没有公网IP或者无法进行端口映射的场景 5 | 6 | 想要完成ngrok和lcx等类似的功能,对于lcx定义的slave啊,listen啊,tran啊我觉得很大歧义,半天理解不了。所以我发明了三个简单易懂的连接方式: 7 | * 公网服务器: publicserver,用于做转发的,监听一个对外开放的端口就行(对,我这里只要一个端口就行) 8 | * 内网服务器:natserver,也就是实际希望被外网访问的局域网服务器(或者是局域网代理) 9 | * 客户端:client,连接客户端,这个很好理解,实际的访问者,本地启动后,通过其他客户端连接本地监听的端口就相当于访问内网服务器 10 | 11 | 是不是更容易理解?是的话直接夸我。 12 | 13 | **注意:没有经过大量实际测试,请谨慎用于生产环境。** 14 | 15 | ## 编译 16 | 由于用到了1.9才有的sync.Map, 所以编译环境必须是1.9+,见谅见谅 ;) 17 | ``` 18 | git clone https://github.com/LubyRuffy/tcptunnel 19 | go get 20 | go build 21 | ``` 22 | 生成tcptunnel文件 23 | 24 | 跨平台编译,比如到NAS服务器或者树莓派等ARM平台,执行: 25 | ``` 26 | GOOS=linux GOARCH=arm GOARM=5 go build 27 | ``` 28 | 29 | ## 运行 30 | 直接执行是读取config.toml配置文件中的内容,最主要的是Mode和对应的配置内容,后续在配置文件中说明。 31 | ``` 32 | ./tcptunnel 33 | ``` 34 | 35 | ### 作为内网映射运行: 36 | - 作为publciserver执行,放到公网服务器 37 | ``` 38 | ./tcptunnel -m publicserver 39 | ``` 40 | - 作为natserver执行,放到内网的服务器 41 | ``` 42 | ./tcptunnel -m natserver 43 | ``` 44 | - 作为client执行,放到需要访问内网服务器的客户端 45 | ``` 46 | ./tcptunnel -m client 47 | 48 | 然后连接本地端口就相当于连接natserver里面对应的服务器了 49 | 50 | ``` 51 | natserver 和 client 通信是通过约定好一致的唯一ID来进行。 52 | 53 | ### 作为tcpproxy执行,也就是端口转发 54 | ``` 55 | ./tcptunnel -m tcpproxy 56 | ``` 57 | 58 | ## 配置文件说明 59 | 默认读取config.toml文件, 60 | ``` 61 | # 模式: 支持publicserver,natserver,client,tcpproxy。可以通过命令行的-m参数覆盖 62 | Mode = "publicserver" 63 | 64 | # 连接公网服务器的地址,格式为 host:port 65 | # 在Mode为 natserver 和 clientconnect 时有效 66 | PublicServerAddr = "127.0.0.1:10011" 67 | 68 | # 端口转发模式,仅仅在Mode为 tcpproxy 时有效 69 | [TcpProxies] 70 | # 数组,可以多个映射关系 71 | [TcpProxies.proxy80] 72 | LocalBindAddr = "127.0.0.1:1234" 73 | RemoteServerAddr = "192.168.1.1:80" 74 | Type = "http" 75 | 76 | [TcpProxies.proxy22] 77 | LocalBindAddr = "127.0.0.1:1235" 78 | RemoteServerAddr = "192.168.1.1:22" 79 | 80 | # 公网服务器监听的地址,仅仅在Mode为 publicserver 时有效,格式为 ip:port 81 | [PublicServer] 82 | LocalBindAddr = "127.0.0.1:10011" 83 | 84 | # 端口转发模式,仅仅在Mode为 natserver 时有效 85 | [NatServer] 86 | # 数组,可以多个映射关系,ID用于注册,客户端连接的时候直接通过ID来进行查找 87 | [NatServer.test] 88 | RemoteServerAddr = "192.168.1.1:80" 89 | ID = "test" 90 | Type = "http" 91 | 92 | [NatServer.test1] 93 | RemoteServerAddr = "192.168.1.1:22" 94 | ID = "test1" 95 | 96 | # 端口转发模式,仅仅在Mode为 client 时有效 97 | [ClientConnect] 98 | # 数组,可以多个映射关系,ID用于标示连接时指定NAT后的服务器对象 99 | [ClientConnect.test] 100 | LocalBindAddr = "127.0.0.1:1234" 101 | ID = "test" 102 | ``` 103 | 104 | ## 流程说明 105 | ``` 106 | natserver REGISTER -> publicserver 107 | publicserver <- CONNECT client <- application tcp connect 108 | natserver NEWDATASTREAM <- publicserver 109 | natserver DATASTREAM -> publicserver 110 | publicserver -> 200 OK client <-> application tcp connect 111 | ``` -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | mkdir .\releases 2 | 3 | call :build linux arm 4 | call :build linux amd64 5 | call :build linux 386 6 | call :build linux mips64le 7 | call :build linux mips64 8 | call :build darwin amd64 9 | call :build darwin 386 10 | call :build freebsd 386 11 | call :build freebsd amd64 12 | call :build windows 386 .exe 13 | call :build windows amd64 .exe 14 | call :armv5 15 | goto :end 16 | 17 | :build 18 | mkdir .\%1\%2 19 | set GOOS=%1 20 | set GOARCH=%2 21 | go build -o %1/%2/tcptunnel%3 -i -ldflags "-w -s" . 22 | copy config.toml .\%1\%2\ 23 | zip -r -o .\releases\tcptunnel_%1_%2.zip .\%1\%2\ 24 | goto :eof 25 | 26 | :armv5 27 | mkdir .\linux\armv5 28 | set GOOS=linux 29 | set GOARCH=arm 30 | set GOARM=5 31 | go build -o linux/armv5/tcptunnel -i -ldflags "-w -s" . 32 | copy config.toml .\linux\armv5\ 33 | zip -r -o .\releases\tcptunnel_linux_armv5.zip .\linux\armv5\ 34 | goto :eof 35 | 36 | :end -------------------------------------------------------------------------------- /clientconnect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/xtaci/smux" 13 | ) 14 | 15 | type ControlSession struct { 16 | Session *smux.Session 17 | } 18 | 19 | // 控制stream通道 20 | func getControlSession(addr string) (session *smux.Session, err error) { 21 | cli, err := net.Dial("tcp", addr) 22 | if err != nil { 23 | return 24 | } 25 | session, err = smux.Client(cli, nil) 26 | if err != nil { 27 | return 28 | } 29 | return 30 | } 31 | 32 | func bindConnToServer(id string, newconn net.Conn, session *ControlSession) { 33 | var stream *smux.Stream 34 | var err error 35 | 36 | defer func() { 37 | newconn.Close() 38 | if stream != nil { 39 | stream.Close() 40 | } 41 | }() 42 | 43 | if session.Session == nil { 44 | log.Println("[ERROR] session.Session not connected!") 45 | return 46 | } 47 | 48 | buf := make([]byte, 1024*1024) 49 | stream, err = session.Session.OpenStream() 50 | 51 | if err != nil { 52 | log.Println("session.OpenStream error:", err) 53 | return 54 | } 55 | log.Println("session.OpenStream ok, id is :", stream.ID()) 56 | 57 | n, err := stream.Write([]byte(fmt.Sprintf("CONNECT /%s HTTP/1.0\r\n\r\n", id))) 58 | if err != nil { 59 | log.Println("stream.Write CONNECT error:", err) 60 | return 61 | } 62 | 63 | // 10秒读取超时 64 | stream.SetReadDeadline(time.Now().Add(time.Duration(10) * time.Second)) 65 | n, err = stream.Read(buf) 66 | if err != nil { 67 | log.Println("stream.Read error:", err) 68 | return 69 | } 70 | 71 | log.Println("stream.Read size : ", n) 72 | if strings.Contains(string(buf[:n]), "200 OK") { 73 | log.Printf("CONNECT to server OK\n") 74 | 75 | stream.SetReadDeadline(time.Time{}) 76 | IoBind(newconn, stream, func(err interface{}) { 77 | if err != io.EOF && err != nil { 78 | log.Printf("IoBind failed: %v\n", err) 79 | } 80 | }) 81 | } else { 82 | log.Printf("CONNECT to server failed: %s\n", string(buf[:n])) 83 | return 84 | } 85 | } 86 | 87 | func clientConnect() { 88 | session := ControlSession{} 89 | var err error 90 | 91 | go func(ctlsession *ControlSession) { 92 | // 获取控制session 93 | for { 94 | if ctlsession.Session != nil && !ctlsession.Session.IsClosed() { 95 | time.Sleep(time.Second * 5) 96 | } else { 97 | ctlsession.Session, err = getControlSession(configOptions.PublicServerAddr) 98 | if err != nil { 99 | log.Printf("connect to server failed: %v, retry after 5 seconds\n", err) 100 | time.Sleep(time.Second * 5) 101 | continue 102 | } 103 | } 104 | } 105 | }(&session) 106 | 107 | wg := sync.WaitGroup{} 108 | 109 | // 监听 110 | for _, v := range configOptions.ClientConnect { 111 | 112 | go func(config ClientConnectConfig) { 113 | log.Println("bind ", config.LocalBindAddr, " to ", config.ID) 114 | listenTCPServer(&wg, config.LocalBindAddr, func(newconn net.Conn) { 115 | bindConnToServer(config.ID, newconn, &session) 116 | }) 117 | }(v) 118 | wg.Add(1) 119 | } 120 | 121 | wg.Wait() 122 | } 123 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | // _ "net/http/pprof" 7 | "os" 8 | "runtime/pprof" 9 | 10 | "github.com/BurntSushi/toml" 11 | //"go/types" 12 | ) 13 | 14 | type TcpProxyConfig struct { 15 | LocalBindAddr string 16 | RemoteServerAddr string 17 | Type string 18 | } 19 | 20 | type PublicServerConfig struct { 21 | LocalBindAddr string 22 | } 23 | 24 | type ClientConnectConfig struct { 25 | LocalBindAddr string 26 | ID string 27 | } 28 | 29 | type NatServerConfig struct { 30 | RemoteServerAddr string 31 | ID string 32 | Type string 33 | } 34 | 35 | type TomlConfig struct { 36 | Mode string 37 | PublicServerAddr string 38 | TcpProxies map[string]TcpProxyConfig 39 | PublicServer PublicServerConfig 40 | NatServer map[string]NatServerConfig 41 | ClientConnect map[string]ClientConnectConfig 42 | } 43 | 44 | var configOptions TomlConfig 45 | 46 | func init() { 47 | cpuProfile := flag.String("p", "", "write cpu profile to file") 48 | configFile := flag.String("c", "config.toml", "config file") 49 | mode := flag.String("m", "", "mode, can overwrite config_file's setting") 50 | flag.Parse() 51 | 52 | if *configFile == "" { 53 | panic("config file is not specified") 54 | } 55 | 56 | if _, err := toml.DecodeFile(*configFile, &configOptions); err != nil { 57 | // handle error 58 | panic(err) 59 | } 60 | 61 | if *mode != "" { 62 | configOptions.Mode = *mode 63 | } 64 | 65 | if *cpuProfile != "" { 66 | // go func() { 67 | // log.Println(http.ListenAndServe("localhost:6060", nil)) 68 | // }() 69 | 70 | f, err := os.Create(*cpuProfile) 71 | if err != nil { 72 | fmt.Println(err) 73 | } 74 | pprof.StartCPUProfile(f) 75 | defer pprof.StopCPUProfile() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # publicserver 2 | # tcpproxy 3 | # natserver 4 | # client 5 | Mode = "natserver" 6 | PublicServerAddr = "127.0.0.1:10011" 7 | 8 | [TcpProxies] 9 | [TcpProxies.proxy80] 10 | LocalBindAddr = "127.0.0.1:1234" 11 | RemoteServerAddr = "192.168.0.1:80" 12 | Type = "http" 13 | 14 | [TcpProxies.proxy22] 15 | LocalBindAddr = "127.0.0.1:1235" 16 | RemoteServerAddr = "192.168.0.1:22" 17 | 18 | [PublicServer] 19 | LocalBindAddr = "0.0.0.0:10011" 20 | 21 | 22 | [NatServer] 23 | [NatServer.test] 24 | RemoteServerAddr = "192.168.0.1:80" 25 | ID = "test" 26 | Type = "http" 27 | 28 | [NatServer.test1] 29 | RemoteServerAddr = "192.168.0.1:22" 30 | ID = "test1" 31 | 32 | [ClientConnect] 33 | [ClientConnect.test] 34 | LocalBindAddr = "127.0.0.1:1234" 35 | ID = "test" 36 | 37 | [ClientConnect.test1] 38 | LocalBindAddr = "127.0.0.1:1235" 39 | ID = "test1" 40 | -------------------------------------------------------------------------------- /iobind.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "sync" 12 | 13 | "github.com/xtaci/smux" 14 | ) 15 | 16 | type onConnection func(net.Conn) 17 | type onStream func(*smux.Stream) 18 | 19 | func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) { 20 | buf := make([]byte, 32*1024) 21 | n := 0 22 | for { 23 | n, err = src.Read(buf) 24 | if n > 0 { 25 | if _, e := dst.Write(buf[0:n]); e != nil { 26 | return e 27 | } 28 | } 29 | if err != nil { 30 | return 31 | } 32 | } 33 | } 34 | 35 | func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interface{})) { 36 | defer func() { 37 | if err := recover(); err != nil { 38 | log.Printf("bind crashed %s", err) 39 | } 40 | }() 41 | e1 := make(chan interface{}, 1) 42 | e2 := make(chan interface{}, 1) 43 | go func() { 44 | defer func() { 45 | if err := recover(); err != nil { 46 | log.Printf("bind crashed %s", err) 47 | } 48 | }() 49 | //_, err := io.Copy(dst, src) 50 | err := ioCopy(dst, src) 51 | e1 <- err 52 | }() 53 | go func() { 54 | defer func() { 55 | if err := recover(); err != nil { 56 | log.Printf("bind crashed %s", err) 57 | } 58 | }() 59 | //_, err := io.Copy(src, dst) 60 | err := ioCopy(src, dst) 61 | e2 <- err 62 | }() 63 | var err interface{} 64 | select { 65 | case err = <-e1: 66 | //log.Printf("e1") 67 | case err = <-e2: 68 | //log.Printf("e2") 69 | } 70 | src.Close() 71 | dst.Close() 72 | 73 | fn(err) 74 | } 75 | 76 | func random(min int, max int) string { 77 | return fmt.Sprintf("%v", rand.Intn(max-min)+min) 78 | } 79 | 80 | // 通用TCP监听流程 81 | func listenTCPServer(wg *sync.WaitGroup, addr string, fn onConnection) (err error) { 82 | defer func() { 83 | wg.Done() 84 | }() 85 | 86 | var listener net.Listener 87 | listener, err = net.Listen("tcp", addr) 88 | if err != nil { 89 | return err 90 | } 91 | log.Printf("bind server to %s ok, listenning...\n", addr) 92 | for { 93 | var conn net.Conn 94 | conn, err = listener.Accept() 95 | if err != nil { 96 | log.Printf("Accept error, %v", err) 97 | } 98 | 99 | go func(newconn net.Conn) { 100 | fn(newconn) 101 | }(conn) 102 | } 103 | } 104 | 105 | func handleRequest(conn net.Conn, outConn net.Conn, inReq *http.Request, remoteAddr string) (resp *http.Response, err error) { 106 | if inReq == nil { 107 | return 108 | } 109 | 110 | outReq := new(http.Request) 111 | *outReq = *inReq // includes shallow copies of maps, but we handle this in Director 112 | outReq.Host = remoteAddr 113 | err = outReq.Write(outConn) 114 | if err != nil { 115 | log.Printf("[http] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) 116 | return 117 | } 118 | 119 | resp, err = http.ReadResponse(bufio.NewReader(outConn), outReq) 120 | if err != nil { 121 | log.Printf("[http] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) 122 | return 123 | } 124 | return 125 | } 126 | 127 | func HTTPBind(conn net.Conn, outConn net.Conn, remoteAddr string, id string) { 128 | defer func() { 129 | conn.Close() 130 | outConn.Close() 131 | }() 132 | for { 133 | req, err := http.ReadRequest(bufio.NewReader(conn)) 134 | if err != nil { 135 | log.Printf("%s [http ReadRequest from conn] %s - %s : %s", id, conn.RemoteAddr(), conn.LocalAddr(), err) 136 | return 137 | } 138 | 139 | // log.Printf("%s [http] %s - %s : %v -> ", id, conn.RemoteAddr(), conn.LocalAddr(), req.URL) 140 | 141 | resp, err := handleRequest(conn, outConn, req, remoteAddr) 142 | if err != nil { 143 | log.Printf("%s [http handleRequest] %s - %s : %s", id, conn.RemoteAddr(), conn.LocalAddr(), err) 144 | return 145 | } 146 | 147 | // log.Printf("%s [http] %s - %s : %v -> %s <-", id, conn.RemoteAddr(), conn.LocalAddr(), req.URL, resp.Status) 148 | 149 | err = resp.Write(conn) 150 | if err != nil { 151 | log.Printf("%s [http Write to conn] %s - %s : %s", id, conn.RemoteAddr(), conn.LocalAddr(), err) 152 | return 153 | } 154 | 155 | // log.Printf("%s [http finished] %s - %s : %v -> %s <-", id, conn.RemoteAddr(), conn.LocalAddr(), req.URL, resp.Status) 156 | 157 | return 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | PrintVersion() 10 | log.Println("Mode is :", configOptions.Mode) 11 | switch configOptions.Mode { 12 | case "tcpproxy": 13 | tcpProxy() 14 | case "natserver": 15 | natServer() 16 | case "publicserver": 17 | publicServer() 18 | case "client": 19 | clientConnect() 20 | default: 21 | fmt.Println("unknow mode of:", configOptions.Mode) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /natserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "sync" 11 | "time" 12 | 13 | "net/http" 14 | 15 | "github.com/xtaci/smux" 16 | ) 17 | 18 | func doNewDataStream(id string, stream *smux.Stream, v NatServerConfig) { 19 | defer func() { 20 | log.Printf("close stream id: %v\n", id) 21 | stream.Close() 22 | }() 23 | 24 | buf := make([]byte, 65536) 25 | 26 | _, err := stream.Write([]byte(fmt.Sprintf("DATASTREAM %s HTTP/1.0\r\n\r\n", id))) 27 | if err != nil { 28 | log.Println(fmt.Sprintf("stream.Write failed: %v", err)) 29 | return 30 | } 31 | 32 | _, err = stream.Read(buf) 33 | if err != nil { 34 | log.Printf("stream.Read failed: %v\n", err) 35 | return 36 | } 37 | //log.Printf("%s stream.Read : %s\n", id, string(buf[:n])) 38 | // log.Printf("%s try to net.Dial to %s\n", id, v.RemoteServerAddr) 39 | 40 | outConn, err := net.Dial("tcp", v.RemoteServerAddr) 41 | if err != nil { 42 | log.Printf("%s net.Dial failed: %v", id, err) 43 | return 44 | } 45 | // log.Printf("%s net.Dial to %s ok\n", id, v.RemoteServerAddr) 46 | 47 | if v.Type == "http" { 48 | HTTPBind(stream, outConn, v.RemoteServerAddr, id) 49 | } else { 50 | IoBind(stream, outConn, func(err interface{}) { 51 | if err != io.EOF && err != nil { 52 | log.Printf("IoBind failed: %v\n", err) 53 | } 54 | 55 | // inAddr := stream.RemoteAddr().String() 56 | // outAddr := outConn.RemoteAddr().String() 57 | // log.Printf("conn %s - %s released", inAddr, outAddr) 58 | }) 59 | } 60 | 61 | } 62 | 63 | func connectOneServer(publicServerAddr string, v NatServerConfig) (err error) { 64 | cli, err := net.DialTimeout("tcp", publicServerAddr, time.Second*10) 65 | if err != nil { 66 | return errors.New(fmt.Sprintf("Dial to server %s failed: %v!", publicServerAddr, err)) 67 | } 68 | 69 | log.Println("connected to public server : ", publicServerAddr) 70 | 71 | defer func() { 72 | cli.Close() 73 | }() 74 | 75 | session, _ := smux.Client(cli, nil) 76 | stream, _ := session.OpenStream() 77 | 78 | defer func() { 79 | stream.Close() 80 | session.Close() 81 | }() 82 | 83 | buf := make([]byte, 65536) 84 | 85 | stream.Write([]byte(fmt.Sprintf("REGISTER /%s HTTP/1.0\r\n\r\n", v.ID))) 86 | n, err := stream.Read(buf) 87 | if err != nil { 88 | return errors.New(fmt.Sprintf("stream.Read failed: %v", err)) 89 | } 90 | log.Println(string(buf[:n])) 91 | 92 | var req *http.Request 93 | for { 94 | req, err = http.ReadRequest(bufio.NewReader(stream)) 95 | if err != nil { 96 | return errors.New(fmt.Sprintf("recvReq failed: %v", err)) 97 | } 98 | 99 | switch req.Method { 100 | case "NEWDATASTREAM": 101 | // stream.Write([]byte("200 OK\r\n\r\n")) 102 | log.Println("NEWDATASTREAM", req.RequestURI) 103 | newstream, _ := session.OpenStream() 104 | go doNewDataStream(req.RequestURI, newstream, v) 105 | default: 106 | log.Println("Unknown msg type") 107 | } 108 | } 109 | 110 | return 111 | } 112 | 113 | // 控制stream通道 114 | func connectServer(wg *sync.WaitGroup, publicServerAddr string, v NatServerConfig) { 115 | defer func() { 116 | wg.Done() 117 | }() 118 | 119 | var err error 120 | for { 121 | err = connectOneServer(publicServerAddr, v) 122 | if err != nil { 123 | log.Println(err) 124 | time.Sleep(time.Second * 5) 125 | continue 126 | } 127 | break 128 | } 129 | } 130 | 131 | func natServer() { 132 | wg := sync.WaitGroup{} 133 | for _, v := range configOptions.NatServer { 134 | fmt.Printf("ID %s -> %s \n", v.ID, v.RemoteServerAddr) 135 | go connectServer(&wg, configOptions.PublicServerAddr, v) 136 | wg.Add(1) 137 | } 138 | wg.Wait() 139 | } 140 | -------------------------------------------------------------------------------- /publicserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | "sync" 12 | "time" 13 | 14 | "github.com/xtaci/smux" 15 | ) 16 | 17 | type ControlStream struct { 18 | sync.Mutex 19 | Stream *smux.Stream 20 | } 21 | 22 | var natCtlMap sync.Map // 控制流 23 | var dataStreamMap sync.Map // 数据通道流 24 | 25 | // 公网监听服务器 26 | func listenPublicServer(wg *sync.WaitGroup, addr string, fn onStream) (err error) { 27 | err = listenTCPServer(wg, addr, func(newconn net.Conn) { 28 | 29 | sess, err := smux.Server(newconn, nil) 30 | if err != nil { 31 | log.Printf("smux should not fail with default config: %v", err) 32 | return 33 | } 34 | 35 | // 断开连接后取消所有ID的注册 https://github.com/LubyRuffy/tcptunnel/issues/3 36 | session_streams := make([]*smux.Stream, 0) 37 | defer func() { 38 | for _, s := range session_streams { 39 | natCtlMap.Range(func(k, v interface{}) bool { 40 | if s == v.(*ControlStream).Stream { 41 | log.Printf("unregister ID: %v", k) 42 | natCtlMap.Delete(k) 43 | } 44 | return true 45 | }) 46 | } 47 | }() 48 | 49 | for { 50 | stream, err := sess.AcceptStream() 51 | if err != nil { 52 | log.Printf("AcceptStream failed: %v", err) 53 | return 54 | } 55 | // log.Printf("AcceptStream ok, id: %v", stream.ID) 56 | 57 | go func(newstream *smux.Stream) { 58 | fn(newstream) 59 | }(stream) 60 | session_streams = append(session_streams, stream) 61 | } 62 | }) 63 | return 64 | } 65 | 66 | func doConnect(req *http.Request, stream *smux.Stream) { 67 | var err error 68 | // buf := make([]byte, 1024*1024) 69 | 70 | if ctlStream, ok := natCtlMap.Load(req.RequestURI); ok { 71 | dataStreamID := "/" + random(1000, 10000000) 72 | // log.Println(" ctlStream: ", ctlStream, " dataStreamID: ", dataStreamID) 73 | 74 | // 新建等待事件 75 | dataStreamMap.Store(dataStreamID, make(chan *smux.Stream, 1)) 76 | // log.Println("wait DATASTREAM event:", dataStreamID) 77 | defer func() { 78 | dataStreamMap.Delete(dataStreamID) 79 | }() 80 | 81 | // 通知建立新的数据通道 82 | log.Println("send NEWDATASTREAM", dataStreamID) 83 | ctlStream.(*ControlStream).Lock() 84 | _, err = ctlStream.(*ControlStream).Stream.Write([]byte(fmt.Sprintf("NEWDATASTREAM %s HTTP/1.0\r\n\r\n", dataStreamID))) 85 | time.Sleep(time.Millisecond * 20) // 这里必须延时一下,否则在浏览器并发连接的时候,natserver在readRequest的过程中会接受不到数据,这个需要再想办法,目前临时加一个延时 86 | ctlStream.(*ControlStream).Unlock() 87 | if err != nil { 88 | log.Println(" stream write data error: ", err) 89 | } 90 | 91 | // 等待通知 92 | waitChan, ok := dataStreamMap.Load(dataStreamID) 93 | if !ok { 94 | panic(fmt.Sprintf(" dataStreamMap not found of : %s", dataStreamID)) 95 | } 96 | 97 | // dataStream := <-waitChan.(chan *smux.Stream) 98 | // log.Println("DATASTREAM event ok: ", dataStream) 99 | var dataStream *smux.Stream 100 | select { 101 | case dataStream = <-waitChan.(chan *smux.Stream): 102 | // log.Println("DATASTREAM event ok: ", dataStream) 103 | dataStreamMap.Delete(dataStreamID) 104 | case <-time.After(time.Second * 10): 105 | log.Println("waitChan DATASTREAM timeout: ", dataStreamID) 106 | dataStreamMap.Delete(dataStreamID) 107 | return 108 | } 109 | 110 | // log.Println("DUMP 222222") 111 | // dataStreamMap.Range(func(key, value interface{}) bool { 112 | // log.Println(key, value) 113 | // return true 114 | // }) 115 | // log.Println("222222 DUMP") 116 | 117 | stream.Write([]byte("200 OK\r\n\r\n")) 118 | 119 | IoBind(dataStream, stream, func(err interface{}) { 120 | if err != io.EOF && err != nil { 121 | log.Printf("IoBind failed: %v\n", err) 122 | } 123 | }) 124 | } else { 125 | log.Println("Could not found stream of id : ", req.RequestURI) 126 | 127 | // https://github.com/LubyRuffy/tcptunnel/issues/2 128 | stream.Write([]byte("404 ID Not Found\r\n\r\n")) 129 | stream.Close() 130 | } 131 | } 132 | 133 | func doDataStream(req *http.Request, stream *smux.Stream) { 134 | log.Println("DATASTREAM msg to ", req.RequestURI) 135 | stream.Write([]byte("200 OK\r\n\r\n")) 136 | waitChan, ok := dataStreamMap.Load(req.RequestURI) 137 | if ok { 138 | waitChan.(chan *smux.Stream) <- stream 139 | } else { 140 | log.Println(" dataStreamMap not found of : ", req.RequestURI) 141 | } 142 | } 143 | 144 | func publicServer() { 145 | rand.Seed(time.Now().UnixNano()) 146 | wg := sync.WaitGroup{} 147 | 148 | listenPublicServer(&wg, configOptions.PublicServer.LocalBindAddr, func(stream *smux.Stream) { 149 | 150 | req, err := http.ReadRequest(bufio.NewReader(stream)) 151 | if err != nil { 152 | log.Printf("recv request failed: %v", err) 153 | return 154 | } 155 | 156 | // log.Println("recv req: ", req) 157 | switch req.Method { 158 | case "CONNECT": 159 | // log.Println("CONNECT msg to ", req.RequestURI) 160 | go doConnect(req, stream) 161 | case "REGISTER": 162 | log.Println("REGISTER msg to ", req.RequestURI) 163 | 164 | // 排他唯一性,https://github.com/LubyRuffy/tcptunnel/issues/3 165 | if _, ok := natCtlMap.Load(req.RequestURI); ok { 166 | log.Println("ID Already Registered", req.RequestURI, ", reject it!") 167 | stream.Write([]byte("500 ID Already Registered\r\n\r\n")) 168 | stream.Close() 169 | return 170 | } else { 171 | ctlStream := ControlStream{Stream: stream} 172 | natCtlMap.Store(req.RequestURI, &ctlStream) 173 | stream.Write([]byte("200 OK\r\n\r\n")) 174 | } 175 | 176 | // log.Println("DUMP====") 177 | // natCtlMap.Range(func(key, value interface{}) bool { 178 | // log.Println(key, value) 179 | // return true 180 | // }) 181 | // log.Println("====DUMP") 182 | case "DATASTREAM": 183 | go doDataStream(req, stream) 184 | 185 | default: 186 | log.Println("Unknown msg type") 187 | return 188 | } 189 | }) 190 | } 191 | -------------------------------------------------------------------------------- /tcpproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | func tcpProxyPair(newconn net.Conn, v TcpProxyConfig) { 12 | defer func() { 13 | newconn.Close() 14 | }() 15 | 16 | outConn, err := net.Dial("tcp", v.RemoteServerAddr) 17 | if err != nil { 18 | panic(err) 19 | return 20 | } 21 | 22 | defer func() { 23 | outConn.Close() 24 | }() 25 | 26 | if v.Type == "http" { 27 | HTTPBind(newconn, outConn, v.RemoteServerAddr, "-") 28 | } else { 29 | IoBind(newconn, outConn, func(err interface{}) { 30 | if err != io.EOF && err != nil { 31 | log.Printf("IoBind failed: %v\n", err) 32 | } 33 | 34 | inAddr := newconn.RemoteAddr().String() 35 | outAddr := outConn.RemoteAddr().String() 36 | log.Printf("newconn %s - %s released", inAddr, outAddr) 37 | }) 38 | } 39 | } 40 | 41 | // 端口转发 42 | func createOneTcpProxy(wg *sync.WaitGroup, v TcpProxyConfig) { 43 | defer func() { 44 | if err := recover(); err != nil { 45 | log.Printf("bind crashed : %s", err) 46 | } 47 | wg.Done() 48 | }() 49 | 50 | l, err := net.Listen("tcp", v.LocalBindAddr) 51 | if err == nil { 52 | log.Printf("bind server to %s ok, listenning...\n", v.LocalBindAddr) 53 | for { 54 | var conn net.Conn 55 | conn, err = l.Accept() 56 | if err == nil { 57 | go tcpProxyPair(conn, v) 58 | } else { 59 | panic(err) 60 | return 61 | } 62 | } 63 | } else { 64 | panic("bind error") 65 | } 66 | } 67 | 68 | func tcpProxy() { 69 | wg := sync.WaitGroup{} 70 | for _, v := range configOptions.TcpProxies { 71 | fmt.Printf("proxy of %s -> %s \n", v.LocalBindAddr, v.RemoteServerAddr) 72 | go createOneTcpProxy(&wg, v) 73 | wg.Add(1) 74 | } 75 | wg.Wait() 76 | } 77 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | const Version = "1.0" 8 | 9 | func PrintVersion() { 10 | log.Println("=====================================") 11 | log.Println("tcptunnel, version ", Version) 12 | log.Println("https://github.com/LubyRuffy/tcptunnel") 13 | log.Println("=====================================") 14 | } 15 | -------------------------------------------------------------------------------- /zip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LubyRuffy/tcptunnel/a566941587e424fd394c7c90074aa9990c40395e/zip.exe --------------------------------------------------------------------------------