diff --git a/conf/app.conf b/conf/app.conf index 27f1aba..8005a66 100755 --- a/conf/app.conf +++ b/conf/app.conf @@ -1,6 +1,12 @@ appname = httpMonitor +#web管理端口 httpport = 8080 +#启动模式dev|pro runmode = dev +#web管理密码 password=123 +#http监听端口 hostPort=8028 - +#basic auth认证用户名和密码 +auth.user=test +auth.password=1234 \ No newline at end of file diff --git a/lib/conn.go b/lib/conn.go index a7f4b3e..6eb2900 100755 --- a/lib/conn.go +++ b/lib/conn.go @@ -178,14 +178,14 @@ func (s *Conn) SetAlive() { } //从tcp报文中解析出host -func (s *Conn) GetHost() (method, address string, rb []byte, err error) { +func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) { var b [32 * 1024]byte var n int if n, err = s.Read(b[:]); err != nil { return } rb = b[:n] - r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(rb))) + r, err = http.ReadRequest(bufio.NewReader(bytes.NewReader(rb))) if err != nil { log.Println("解析host出错:", err) return diff --git a/lib/init.go b/lib/init.go index 9dbd857..b3fcc7a 100644 --- a/lib/init.go +++ b/lib/init.go @@ -3,6 +3,7 @@ package lib import ( "errors" "flag" + "github.com/astaxie/beego" "log" "reflect" "strings" @@ -42,7 +43,7 @@ func InitMode() { stop := make(chan int) for _, v := range strings.Split(*verifyKey, ",") { log.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v) - go NewRPClient(*serverAddr, 2, v).Start() + go NewRPClient(*serverAddr, 3, v).Start() } <-stop } else { @@ -71,15 +72,19 @@ func InitFromCsv() { } func newMode(mode string, bridge *Tunnel, httpPort int, tunnelTarget string, u string, p string, enCompress int, deCompress int, vkey string) interface{} { + if u == "" || p == "" { //如果web管理中设置了用户名和密码,则覆盖配置文件 + u = beego.AppConfig.String("auth.user") + p = beego.AppConfig.String("auth.password") + } switch mode { case "httpServer": return NewHttpModeServer(httpPort, bridge, enCompress, deCompress, vkey) case "tunnelServer": - return NewTunnelModeServer(httpPort, tunnelTarget, ProcessTunnel, bridge, enCompress, deCompress, vkey) + return NewTunnelModeServer(httpPort, tunnelTarget, ProcessTunnel, bridge, enCompress, deCompress, vkey, u, p) case "sock5Server": return NewSock5ModeServer(httpPort, u, p, bridge, enCompress, deCompress, vkey) case "httpProxyServer": - return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHttp, bridge, enCompress, deCompress, vkey) + return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHttp, bridge, enCompress, deCompress, vkey, u, p) case "udpServer": return NewUdpModeServer(httpPort, tunnelTarget, bridge, enCompress, deCompress, vkey) case "webServer": @@ -88,7 +93,7 @@ func newMode(mode string, bridge *Tunnel, httpPort int, tunnelTarget string, u s case "hostServer": return NewHostServer() case "httpHostServer": - return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHost, bridge, enCompress, deCompress, vkey) + return NewTunnelModeServer(httpPort, tunnelTarget, ProcessHost, bridge, enCompress, deCompress, vkey, u, p) } return nil } @@ -97,17 +102,9 @@ func StopServer(cFlag string) error { if v, ok := RunList[cFlag]; ok { reflect.ValueOf(v).MethodByName("Close").Call(nil) delete(RunList, cFlag) - if t := bridge.signalList[getverifyval(cFlag)]; t != nil { - if *verifyKey == "" { //多客户端模式重启相关隧道 - for { - if t.Len() <= 0 { - break - } - t.Pop().Close() - } - delete(bridge.signalList, getverifyval(cFlag)) - delete(bridge.tunnelList, getverifyval(cFlag)) - } + if *verifyKey == "" { //多客户端模式关闭相关隧道 + bridge.DelClientSignal(cFlag) + bridge.DelClientTunnel(cFlag) } if t, err := CsvDb.GetTask(cFlag); err != nil { return err diff --git a/lib/server.go b/lib/server.go index 793d174..a5fc785 100755 --- a/lib/server.go +++ b/lib/server.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/astaxie/beego" + "github.com/astaxie/beego/session" "io/ioutil" "log" "net" @@ -11,15 +12,22 @@ import ( "strings" ) +var GlobalHostSessions *session.Manager + const ( - VERIFY_EER = "vkey" - WORK_MAIN = "main" - WORK_CHAN = "chan" - RES_SIGN = "sign" - RES_MSG = "msg0" - TEST_FLAG = "tst" - CONN_TCP = "tcp" - CONN_UDP = "udp" + VERIFY_EER = "vkey" + WORK_MAIN = "main" + WORK_CHAN = "chan" + RES_SIGN = "sign" + RES_MSG = "msg0" + TEST_FLAG = "tst" + CONN_TCP = "tcp" + CONN_UDP = "udp" + Unauthorized_BYTES = `HTTP/1.1 401 Unauthorized +Content-Type: text/plain; charset=utf-8 +WWW-Authenticate: Basic realm="easyProxy" + +401 Unauthorized` ) type HttpModeServer struct { @@ -44,19 +52,28 @@ func NewHttpModeServer(httpPort int, bridge *Tunnel, enCompress int, deCompress func (s *HttpModeServer) Start() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { retry: + u := beego.AppConfig.String("basic.user") + p := beego.AppConfig.String("basic.password") + if u != "" && p != "" && !checkAuth(r, u, p) { + w.Header().Set("WWW-Authenticate", `Basic realm="easyProxy""`) + w.WriteHeader(401) + w.Write([]byte("401 Unauthorized\n")) + return + } err, conn := s.bridge.GetSignal(getverifyval(s.vKey)) if err != nil { BadRequest(w) + return } if err := s.writeRequest(r, conn); err != nil { - log.Println(err) + log.Println("write request to client error:", err) conn.Close() goto retry return } err = s.writeResponse(w, conn) if err != nil { - log.Println(err) + log.Println("write response error:", err) conn.Close() goto retry return @@ -92,7 +109,7 @@ func (s *HttpModeServer) writeResponse(w http.ResponseWriter, c *Conn) error { } switch flags { case RES_SIGN: - buf := make([]byte, 1024*32) + buf := make([]byte, 1024*1024*32) n, err := c.ReadFromCompress(buf, s.deCompress) if err != nil { return err @@ -125,17 +142,19 @@ func (s *HttpModeServer) writeResponse(w http.ResponseWriter, c *Conn) error { type process func(c *Conn, s *TunnelModeServer) error type TunnelModeServer struct { - httpPort int - tunnelTarget string - process process - bridge *Tunnel - listener *net.TCPListener - enCompress int - deCompress int - vKey string + httpPort int + tunnelTarget string + process process + bridge *Tunnel + listener *net.TCPListener + enCompress int + deCompress int + basicUser string + basicPassword string + vKey string } -func NewTunnelModeServer(httpPort int, tunnelTarget string, process process, bridge *Tunnel, enCompress int, deCompress int, vKey string) *TunnelModeServer { +func NewTunnelModeServer(httpPort int, tunnelTarget string, process process, bridge *Tunnel, enCompress, deCompress int, vKey, basicUser, basicPasswd string) *TunnelModeServer { s := new(TunnelModeServer) s.httpPort = httpPort s.bridge = bridge @@ -144,6 +163,8 @@ func NewTunnelModeServer(httpPort int, tunnelTarget string, process process, bri s.enCompress = enCompress s.deCompress = deCompress s.vKey = vKey + s.basicUser = basicUser + s.basicPassword = basicPasswd return s } @@ -166,6 +187,14 @@ func (s *TunnelModeServer) Start() error { } return nil } +func (s *TunnelModeServer) auth(r *http.Request, c *Conn) error { + if s.basicUser != "" && s.basicPassword != "" && !checkAuth(r, s.basicUser, s.basicPassword) { + c.Write([]byte(Unauthorized_BYTES)) + c.Close() + return errors.New("401 Unauthorized") + } + return nil +} func (s *TunnelModeServer) Close() error { return s.listener.Close() @@ -173,7 +202,12 @@ func (s *TunnelModeServer) Close() error { //tcp隧道模式 func ProcessTunnel(c *Conn, s *TunnelModeServer) error { - link := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + link, err := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + if err != nil { + log.Println(err) + c.Close() + return err + } if _, err := link.WriteHost(CONN_TCP, s.tunnelTarget); err != nil { link.Close() c.Close() @@ -187,12 +221,20 @@ func ProcessTunnel(c *Conn, s *TunnelModeServer) error { //http代理模式 func ProcessHttp(c *Conn, s *TunnelModeServer) error { - method, addr, rb, err := c.GetHost() + method, addr, rb, err, r := c.GetHost() if err != nil { c.Close() return err } - link := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + if err := s.auth(r, c); err != nil { + return err + } + link, err := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + if err != nil { + log.Println(err) + c.Close() + return err + } if _, err := link.WriteHost(CONN_TCP, addr); err != nil { c.Close() link.Close() @@ -211,18 +253,26 @@ func ProcessHttp(c *Conn, s *TunnelModeServer) error { //多客户端域名代理 func ProcessHost(c *Conn, s *TunnelModeServer) error { - method, addr, rb, err := c.GetHost() + method, addr, rb, err, r := c.GetHost() if err != nil { c.Close() return err } + if err := s.auth(r, c); err != nil { + return err + } host, task, err := getKeyByHost(addr) if err != nil { c.Close() return err } de, en := getCompressType(task.Compress) - link := s.bridge.GetTunnel(getverifyval(host.Vkey), en, de) + link, err := s.bridge.GetTunnel(getverifyval(host.Vkey), en, de) + if err != nil { + log.Println(err) + c.Close() + return err + } if _, err := link.WriteHost(CONN_TCP, host.Target); err != nil { c.Close() link.Close() @@ -262,7 +312,7 @@ func (s *WebServer) Start() { } AddTask(t) beego.BConfig.WebConfig.Session.SessionOn = true - log.Println("web管理启动,访问端口为",beego.AppConfig.String("httpport")) + log.Println("web管理启动,访问端口为", beego.AppConfig.String("httpport")) beego.Run() } @@ -280,6 +330,8 @@ type HostServer struct { func (s *HostServer) Start() error { return nil } + +//TODO:host模式的客户端,无需指定和监听端口等,此处有待优化 func NewHostServer() *HostServer { s := new(HostServer) return s diff --git a/lib/sock5.go b/lib/sock5.go index 436a519..4427dce 100755 --- a/lib/sock5.go +++ b/lib/sock5.go @@ -136,7 +136,12 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *Conn, binary.Read(c, binary.BigEndian, &port) // connect to host addr := net.JoinHostPort(host, strconv.Itoa(int(port))) - client := s.bridge.GetTunnel(getverifyval(s.vKey),s.enCompress,s.deCompress) + client, err := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + if err != nil { + log.Println(err) + client.Close() + return + } s.sendReply(c, succeeded) var ltype string if command == associateMethod { diff --git a/lib/tunnel.go b/lib/tunnel.go index a050d4a..4107442 100755 --- a/lib/tunnel.go +++ b/lib/tunnel.go @@ -83,7 +83,6 @@ func (s *Tunnel) cliProcess(c *Conn) error { c.conn.Close() return err } - //TODO:暂时取消 if !verify(string(vval)) { log.Println("当前客户端连接校验错误,关闭此客户端:", c.conn.RemoteAddr()) s.verifyError(c) @@ -126,8 +125,10 @@ func (s *Tunnel) addList(m map[string]*list, c *Conn, cFlag string) { } //新建隧道 -func (s *Tunnel) newChan(cFlag string) { - s.wait(s.signalList, cFlag) +func (s *Tunnel) newChan(cFlag string) error { + if err := s.wait(s.signalList, cFlag); err != nil { + return err + } retry: connPass := s.signalList[cFlag].Pop() _, err := connPass.conn.Write([]byte("chan")) @@ -136,22 +137,25 @@ retry: goto retry } s.signalList[cFlag].Add(connPass) + return nil } //得到一个tcp隧道 -func (s *Tunnel) GetTunnel(cFlag string, en, de int) *Conn { +func (s *Tunnel) GetTunnel(cFlag string, en, de int) (c *Conn, err error) { if v, ok := s.tunnelList[cFlag]; !ok || v.Len() < 10 { //新建通道 go s.newChan(cFlag) } retry: - s.wait(s.tunnelList, cFlag) - c := s.tunnelList[cFlag].Pop() + if err = s.wait(s.tunnelList, cFlag); err != nil { + return + } + c = s.tunnelList[cFlag].Pop() if _, err := c.wTest(); err != nil { c.Close() goto retry } c.WriteCompressType(en, de) - return c + return } //得到一个通信通道 @@ -171,14 +175,43 @@ func (s *Tunnel) ReturnSignal(conn *Conn, cFlag string) { } } -//等待 -func (s *Tunnel) wait(m map[string]*list, cFlag string) { - ticker := time.NewTicker(time.Millisecond * 100) - for { - <-ticker.C - if _, ok := m[cFlag]; ok { - ticker.Stop() - break +//删除通信通道 +func (s *Tunnel) DelClientSignal(cFlag string) { + s.delClient(cFlag, s.signalList) +} + +//删除隧道 +func (s *Tunnel) DelClientTunnel(cFlag string) { + s.delClient(cFlag, s.tunnelList) +} + +func (s *Tunnel) delClient(cFlag string, l map[string]*list) { + if t := l[getverifyval(cFlag)]; t != nil { + for { + if t.Len() <= 0 { + break + } + t.Pop().Close() } + delete(l, getverifyval(cFlag)) } } + +//等待 +func (s *Tunnel) wait(m map[string]*list, cFlag string) error { + ticker := time.NewTicker(time.Millisecond * 100) + stop := time.After(time.Second * 10) +loop: + for { + select { + case <-ticker.C: + if _, ok := m[cFlag]; ok { + ticker.Stop() + break loop + } + case <-stop: + return errors.New("client key: " + cFlag + ",err: get client conn timeout") + } + } + return nil +} diff --git a/lib/udp.go b/lib/udp.go index 4936772..16156ce 100755 --- a/lib/udp.go +++ b/lib/udp.go @@ -57,7 +57,11 @@ func (s *UdpModeServer) Start() error { func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { fmt.Println(addr.String()) fmt.Println(string(data)) - conn := s.bridge.GetTunnel(getverifyval(s.vKey),s.enCompress,s.deCompress) + conn, err := s.bridge.GetTunnel(getverifyval(s.vKey), s.enCompress, s.deCompress) + if err != nil { + log.Println(err) + return + } if _, err := conn.WriteHost(CONN_UDP, s.tunnelTarget); err != nil { conn.Close() return diff --git a/lib/util.go b/lib/util.go index 595971b..2647ca4 100755 --- a/lib/util.go +++ b/lib/util.go @@ -5,6 +5,7 @@ import ( "bytes" "compress/gzip" "crypto/md5" + "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" @@ -219,12 +220,12 @@ func getverifyval(vkey string) string { } func verify(verifyKeyMd5 string) bool { - if getverifyval(*verifyKey) == verifyKeyMd5 { + if *verifyKey != "" && getverifyval(*verifyKey) == verifyKeyMd5 { return true } if *verifyKey == "" { - for _, v := range CsvDb.Tasks { - if _, ok := RunList[v.VerifyKey]; getverifyval(v.VerifyKey) == verifyKeyMd5 && ok { + for k := range RunList { + if getverifyval(k) == verifyKeyMd5 { return true } } @@ -237,9 +238,6 @@ func getKeyByHost(host string) (h *HostList, t *TaskList, err error) { if strings.Contains(host, v.Host) { h = v t, err = CsvDb.GetTask(v.Vkey) - if err != nil { - return - } return } } @@ -266,6 +264,7 @@ func GetRandomString(l int) string { return string(result) } +//通过host获取对应的ip地址 func Gethostbyname(hostname string) string { if !DomainCheck(hostname) { return hostname @@ -281,6 +280,7 @@ func Gethostbyname(hostname string) string { return "" } +//检查是否是域名 func DomainCheck(domain string) bool { var match bool IsLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/)" @@ -291,3 +291,22 @@ func DomainCheck(domain string) bool { } return match } + +//检查basic认证 +func checkAuth(r *http.Request, user, passwd string) bool { + s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + if len(s) != 2 { + return false + } + + b, err := base64.StdEncoding.DecodeString(s[1]) + if err != nil { + return false + } + + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + return false + } + return pair[0] == user && pair[1] == passwd +} diff --git a/views/index/add.html b/views/index/add.html index 43928e6..fc70b30 100755 --- a/views/index/add.html +++ b/views/index/add.html @@ -39,11 +39,11 @@
- +
- +
@@ -63,37 +63,37 @@ arr["tunnelServer"] = ["type", "port", "target", "compress", "tcp隧道模式,提供一条tcp隧道,适用于ssh、远程桌面等,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的目标端口"] arr["udpServer"] = ["type", "port", "target", "compress", "udp隧道模式,提供一条udp隧道,适用于dns、内网dns访问等,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的udp目标端口"] arr["sock5Server"] = ["type", "port", "compress", "u", "p", "socks5代理模式,内网socks5代理,配合proxifer,可如同使用vpn一样访问内网设备或资源,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,在外网环境下本机配置socks5代理,即访问内网设备或者资源 "] - arr["httpProxyServer"] = ["type", "port", "compress", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,在外网环境下本机配置http代理,即访问内网站点"] + arr["httpProxyServer"] = ["type", "port", "compress", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,在外网环境下本机配置http代理,即访问内网站点"] arr["hostServer"] = ["type", "compress", "域名分发模式,使用域名代理内网服务,适用于小程序开发、公众号开发、站点演示等,添加后会自动生成一个客户端验证key
在内网机器执行./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口
建立成功后,使用nginx将请求反向代理到本程序,再进行域名配置,即可解析"] - function resetForm() { - for (var i = 0; i < arr["all"].length; i++) { - $("#" + arr["all"][i]).css("display", "none") - } - o = $("#type option:selected").val() - for (var i = 0; i < arr[o].length - 1; i++) { - $("#" + arr[o][i]).css("display", "block") - } - $("#info").html(arr[o][arr[o].length - 1]) + function resetForm() { + for (var i = 0; i < arr["all"].length; i++) { + $("#" + arr["all"][i]).css("display", "none") } + o = $("#type option:selected").val() + for (var i = 0; i < arr[o].length - 1; i++) { + $("#" + arr[o][i]).css("display", "block") + } + $("#info").html(arr[o][arr[o].length - 1]) + } - $(function () { + $(function () { + resetForm() + $("#type").on("change", function () { resetForm() - $("#type").on("change", function () { - resetForm() - }) - $("#add").on("click", function () { - $.ajax({ - type: "POST", - url: "/index/add", - data: $("form").serializeArray(), - success: function (res) { - alert(res.msg) - if (res.status) { - history.back(-1) - } + }) + $("#add").on("click", function () { + $.ajax({ + type: "POST", + url: "/index/add", + data: $("form").serializeArray(), + success: function (res) { + alert(res.msg) + if (res.status) { + history.back(-1) } - }) + } }) }) + }) \ No newline at end of file diff --git a/views/index/edit.html b/views/index/edit.html index f979dc8..22b1423 100755 --- a/views/index/edit.html +++ b/views/index/edit.html @@ -34,14 +34,14 @@
- + + placeholder="不填则无需验证">
- + + placeholder="不填则无需验证">
@@ -60,7 +60,7 @@ arr["tunnelServer"] = ["type", "port", "target", "compress"] arr["udpServer"] = ["type", "port", "target", "compress"] arr["sock5Server"] = ["type", "port", "compress", "u", "p"] - arr["httpProxyServer"] = ["type", "port", "compress"] + arr["httpProxyServer"] = ["type", "port", "compress", "u", "p"] arr["hostServer"] = ["type", "compress"] function resetForm() {