├── LICENSE ├── README.md ├── bridge └── iobridge.go ├── config.json ├── core └── core.go └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Switcher 2 | 一个用GO语言编写的端口复用工具,能让HTTP/(HTTPS/SSL/TLS)/SSH/RDP/SOCKS5/HTTPProxy/Other跑在同一个端口上。支持复用本地端口,也支持复用其他IP的端口! 3 | ## 使用方法 4 | 配置好目录下的config.json后,直接运行就行 5 | ## 配置 6 | 打开程序目录下的config.json,你会看到类似下面的内容 7 | 8 | { 9 | "ListenAddr": "0.0.0.0:5294", 10 | "HTTPAddr": "127.0.0.1:80", 11 | "HTTPHostReplace":"www.hello.com", 12 | "SSLAddr": "", 13 | "SSHAddr": "123.123.123.123:22", 14 | "RDPAddr": "", 15 | "SOCKS5Addr": "", 16 | "HTTPProxyAddr":"", 17 | "DefaultAddr": "127.0.0.1:1234" 18 | } 19 | 20 | 上面的配置,如果别人用浏览器连接你电脑的5294端口,就相当于以www.hello.com为host去访问你电脑的80端口 21 | 22 | 如果别人用SSH客户端去连接你电脑的5294端口,就相当于连接123.123.123.123:22 23 | 24 | 如果别人用上面列出的协议以外的客户端去连接你电脑的5294端口,就相当于连接你电脑的1234端口 25 | 26 | 27 | 28 | - ListenAddr是本地监听地址,也就是需要复用的端口 29 | - HTTPAddr是目标的HTTP服务器地址 30 | - HTTPHostReplace是HTTP请求的HOST字段替换值,留空为不替换 31 | - SSLAddr是目标的SSL服务器地址(HTTPS/TLS都可以填在这里) 32 | - SSHAddr是目标的SSH服务器地址 33 | - RDPAddr是目标的微软远程桌面服务器地址 34 | - SOCKS5Addr是目标的Socks5代理服务器地址 35 | - HTTPProxyAddr是目标的HTTP代理服务器地址 36 | - DefaultAddr是当上面所有协议都不满足的时候的目标地址 37 | 38 | 所有地址都要带端口! 39 | 40 | 用不到的协议可以直接留空。 41 | 42 | 支持复用本地端口,也支持复用其他IP的端口!所以上面的地址也可以是127.0.0.1,也可以是外网地址。 43 | 44 | ## 注意事项 45 | 本工具的原理是根据客户端建立好连接后第一个数据包的特征进行判断是什么协议,然后再中转到目标地址。 46 | 这种方式已知有两个缺陷: 47 | 48 | 1. 不支持连接建立之后服务器主动握手的协议,例如VNC,FTP,MYSQL…所以DefaultAddr里面不要填写这些协议的地址,没法复用的。 49 | 2. SSH无法连接请更换最新版putty或MobaXterm,因为SSH本来属于服务器主动握手的协议,但有些软件遵守有些软件不遵守,所以请选择客户端主动握手的软件。 50 | 51 | ## 想增加新协议?遇到了问题? 52 | 欢迎提issue和Pull Request 53 | 54 | ## 开源协议 55 | BSD 3-Clause License -------------------------------------------------------------------------------- /bridge/iobridge.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "net" 5 | "log" 6 | "github.com/crabkun/switcher/core" 7 | ) 8 | 9 | func IOBridge(A net.Conn,B net.Conn){ 10 | buf:=make([]byte,1024) 11 | for{ 12 | n,err:=A.Read(buf) 13 | if err!=nil{ 14 | A.Close() 15 | B.Close() 16 | return 17 | } 18 | B.Write(buf[:n]) 19 | } 20 | } 21 | func NewBridge(client net.Conn,remoteAddr string,connType string,firstPacket []byte){ 22 | r,err:=net.Dial("tcp",remoteAddr) 23 | if err!=nil{ 24 | log.Printf("客户端%v无法中转到[%s]%s,原因:%s\n",client.RemoteAddr(),connType,remoteAddr,err.Error()) 25 | return 26 | } 27 | r.Write(firstPacket) 28 | go IOBridge(client,r) 29 | go IOBridge(r,client) 30 | log.Printf("客户端%v被中转到[%s]%s\n",client.RemoteAddr(),connType,remoteAddr) 31 | } 32 | 33 | func NewClientComming(client net.Conn){ 34 | buf:=make([]byte,20480) 35 | n,err:=client.Read(buf) 36 | if err!=nil{ 37 | log.Printf("客户端%v处理错误,原因:%s\n",client.RemoteAddr(),err) 38 | client.Close() 39 | } 40 | testbuf:=buf[:n] 41 | connType,addr:=core.GetAddrByRegExp(testbuf,&testbuf) 42 | NewBridge(client,addr,connType,testbuf) 43 | } 44 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ListenAddr": "0.0.0.0:5294", 3 | "HTTPAddr": "", 4 | "HTTPHostReplace":"", 5 | "SSLAddr": "", 6 | "SSHAddr": "", 7 | "RDPAddr": "", 8 | "SOCKS5Addr": "", 9 | "HTTPProxyAddr":"", 10 | "DefaultAddr": "" 11 | } -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "regexp" 5 | "bytes" 6 | ) 7 | 8 | var Config struct{ 9 | ListenAddr string 10 | HTTPAddr string 11 | HTTPHostReplace string 12 | SSLAddr string 13 | SSHAddr string 14 | RDPAddr string 15 | VNCAddr string 16 | SOCKS5Addr string 17 | HTTPProxyAddr string 18 | DefaultAddr string 19 | } 20 | 21 | var RegExpMap map[string]*regexp.Regexp 22 | var RegExp_HostRpl *regexp.Regexp 23 | func InitRegExpMap(){ 24 | RegExpMap=make(map[string]*regexp.Regexp) 25 | RegExpMap["http"]=regexp.MustCompile(`^(GET|POST|HEAD|DELETE|PUT|CONNECT|OPTIONS|TRACE)`) 26 | RegExpMap["ssh"]=regexp.MustCompile(`^SSH`) 27 | RegExpMap["ssl"]=regexp.MustCompile(`^\x16\x03`) 28 | RegExpMap["rdp"]=regexp.MustCompile(`^\x03\x00\x00\x13`) 29 | RegExpMap["socks5"]=regexp.MustCompile(`^\x05`) 30 | RegExpMap["httpProxy"]=regexp.MustCompile(`(^CONNECT)|(Proxy-Connection:)`) 31 | RegExp_HostRpl=regexp.MustCompile("Host: (.*)") 32 | } 33 | 34 | func GetAddrByRegExp(testbuf []byte,ptr *[]byte)(string,string){ 35 | switch { 36 | case RegExpMap["http"].Match(testbuf) && 37 | bytes.Index(testbuf,[]byte("Proxy-Connection:"))==-1 : 38 | if Config.HTTPHostReplace!=""{ 39 | *ptr=RegExp_HostRpl.ReplaceAll(testbuf,[]byte("Host: "+Config.HTTPHostReplace+"\r")) 40 | } 41 | return "http",Config.HTTPAddr 42 | case RegExpMap["ssh"].Match(testbuf): 43 | return "ssh",Config.SSHAddr 44 | case RegExpMap["ssl"].Match(testbuf): 45 | return "ssl",Config.SSLAddr 46 | case RegExpMap["rdp"].Match(testbuf): 47 | return "rdp",Config.RDPAddr 48 | case RegExpMap["socks5"].Match(testbuf): 49 | return "socks5",Config.SOCKS5Addr 50 | case RegExpMap["httpProxy"].Match(testbuf): 51 | return "httpProxy",Config.HTTPProxyAddr 52 | default: 53 | return "default",Config.DefaultAddr 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "encoding/json" 6 | "net" 7 | "log" 8 | "github.com/crabkun/switcher/core" 9 | "github.com/crabkun/switcher/bridge" 10 | ) 11 | 12 | func main(){ 13 | var err error 14 | log.Println("Switcher V1.0 by Crabkun") 15 | buf,err:=ioutil.ReadFile("config.json") 16 | if err!=nil{ 17 | panic("配置文件config.json载入失败:"+err.Error()) 18 | } 19 | if err=json.Unmarshal(buf,&core.Config);err!=nil{ 20 | panic("配置文件config.json解析失败:"+err.Error()) 21 | } 22 | core.InitRegExpMap() 23 | l,err:=net.Listen("tcp",core.Config.ListenAddr) 24 | if err!=nil{ 25 | panic("监听失败:"+err.Error()) 26 | } 27 | log.Println("万能端口成功监听于",l.Addr()) 28 | for{ 29 | client,err:=l.Accept() 30 | if err!=nil{ 31 | panic("客户端接受失败:"+err.Error()) 32 | } 33 | go bridge.NewClientComming(client) 34 | } 35 | } --------------------------------------------------------------------------------