From 4f49458af0cd69c203c2185bb10d9b3183bd93b7 Mon Sep 17 00:00:00 2001 From: fatedier Date: Wed, 20 Jul 2016 16:00:35 +0800 Subject: [PATCH 1/5] frp/models/msg: fix a bug if local service write to socket immediately every time accept one user connection, fix #20 --- src/frp/models/msg/process.go | 22 +++++++++------------- src/frp/models/server/server.go | 5 ++--- src/frp/utils/conn/conn.go | 11 ++++++++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/frp/models/msg/process.go b/src/frp/models/msg/process.go index 4c7783bd..43e34ab6 100644 --- a/src/frp/models/msg/process.go +++ b/src/frp/models/msg/process.go @@ -15,12 +15,10 @@ package msg import ( - "bufio" "bytes" "encoding/binary" "fmt" "io" - "net" "sync" "frp/models/config" @@ -61,7 +59,7 @@ func JoinMore(c1 *conn.Conn, c2 *conn.Conn, conf config.BaseConf, needRecord boo defer wait.Done() // we don't care about errors here - pipeEncrypt(from.TcpConn, to.TcpConn, conf, needRecord) + pipeEncrypt(from, to, conf, needRecord) } decryptPipe := func(to *conn.Conn, from *conn.Conn) { @@ -70,7 +68,7 @@ func JoinMore(c1 *conn.Conn, c2 *conn.Conn, conf config.BaseConf, needRecord boo defer wait.Done() // we don't care about errors here - pipeDecrypt(to.TcpConn, from.TcpConn, conf, needRecord) + pipeDecrypt(to, from, conf, needRecord) } wait.Add(2) @@ -106,7 +104,7 @@ func unpkgMsg(data []byte) (int, []byte, []byte) { } // decrypt msg from reader, then write into writer -func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) (err error) { +func pipeDecrypt(r *conn.Conn, w *conn.Conn, conf config.BaseConf, needRecord bool) (err error) { laes := new(pcrypto.Pcrypto) key := conf.AuthToken if conf.PrivilegeMode { @@ -119,7 +117,7 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) buf := make([]byte, 5*1024+4) var left, res []byte - var cnt int + var cnt int = -1 // record var flowBytes int64 = 0 @@ -129,13 +127,12 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) }() } - nreader := bufio.NewReader(r) for { // there may be more than 1 package in variable // and we read more bytes if unpkgMsg returns an error var newBuf []byte if cnt < 0 { - n, err := nreader.Read(buf) + n, err := r.Read(buf) if err != nil { return err } @@ -165,7 +162,7 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) } } - _, err = w.Write(res) + _, err = w.WriteBytes(res) if err != nil { return err } @@ -182,7 +179,7 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) } // recvive msg from reader, then encrypt msg into writer -func pipeEncrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) (err error) { +func pipeEncrypt(r *conn.Conn, w *conn.Conn, conf config.BaseConf, needRecord bool) (err error) { laes := new(pcrypto.Pcrypto) key := conf.AuthToken if conf.PrivilegeMode { @@ -201,10 +198,9 @@ func pipeEncrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) }() } - nreader := bufio.NewReader(r) buf := make([]byte, 5*1024) for { - n, err := nreader.Read(buf) + n, err := r.Read(buf) if err != nil { return err } @@ -235,7 +231,7 @@ func pipeEncrypt(r net.Conn, w net.Conn, conf config.BaseConf, needRecord bool) } res = pkgMsg(res) - _, err = w.Write(res) + _, err = w.WriteBytes(res) if err != nil { return err } diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index 139c9899..e69a2793 100644 --- a/src/frp/models/server/server.go +++ b/src/frp/models/server/server.go @@ -154,13 +154,12 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { } // start another goroutine for join two conns from frpc and user - go func() { + go func(userConn *conn.Conn) { workConn, err := p.getWorkConn() if err != nil { return } - userConn := c // msg will transfer to another without modifying // l means local, r means remote log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(), @@ -169,7 +168,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { needRecord := true go msg.JoinMore(userConn, workConn, p.BaseConf, needRecord) metric.OpenConnection(p.Name) - }() + }(c) } }(listener) } diff --git a/src/frp/utils/conn/conn.go b/src/frp/utils/conn/conn.go index ed330f68..a3981104 100644 --- a/src/frp/utils/conn/conn.go +++ b/src/frp/utils/conn/conn.go @@ -125,6 +125,11 @@ func (c *Conn) GetLocalAddr() (addr string) { return c.TcpConn.LocalAddr().String() } +func (c *Conn) Read(p []byte) (n int, err error) { + n, err = c.Reader.Read(p) + return +} + func (c *Conn) ReadLine() (buff string, err error) { buff, err = c.Reader.ReadString('\n') if err != nil { @@ -138,10 +143,14 @@ func (c *Conn) ReadLine() (buff string, err error) { return buff, err } +func (c *Conn) WriteBytes(content []byte) (n int, err error) { + n, err = c.TcpConn.Write(content) + return +} + func (c *Conn) Write(content string) (err error) { _, err = c.TcpConn.Write([]byte(content)) return err - } func (c *Conn) SetDeadline(t time.Time) error { From 926d0b74a9a99bed17bf0deaa7e7eb2cc1059acf Mon Sep 17 00:00:00 2001 From: fatedier Date: Wed, 20 Jul 2016 16:33:42 +0800 Subject: [PATCH 2/5] utils/vhost: update TcpConn with bufio.Reader --- src/frp/utils/conn/conn.go | 5 +++++ src/frp/utils/vhost/vhost.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frp/utils/conn/conn.go b/src/frp/utils/conn/conn.go index a3981104..a5a7a335 100644 --- a/src/frp/utils/conn/conn.go +++ b/src/frp/utils/conn/conn.go @@ -117,6 +117,11 @@ func ConnectServer(host string, port int64) (c *Conn, err error) { return c, nil } +func (c *Conn) SetTcpConn(tcpConn net.Conn) { + c.TcpConn = tcpConn + c.Reader = bufio.NewReader(c.TcpConn) +} + func (c *Conn) GetRemoteAddr() (addr string) { return c.TcpConn.RemoteAddr().String() } diff --git a/src/frp/utils/vhost/vhost.go b/src/frp/utils/vhost/vhost.go index ecf080d1..ae672097 100644 --- a/src/frp/utils/vhost/vhost.go +++ b/src/frp/utils/vhost/vhost.go @@ -105,7 +105,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) { if err = sConn.SetDeadline(time.Time{}); err != nil { return } - c.TcpConn = sConn + c.SetTcpConn(sConn) l.accept <- c } From e0f2993b707966be84b4972c6ee4e80477860dbc Mon Sep 17 00:00:00 2001 From: Maodanping <673698750@qq.com> Date: Sun, 24 Jul 2016 15:00:18 +0800 Subject: [PATCH 3/5] change the Host value in http request header --- src/frp/cmd/frpc/control.go | 2 + src/frp/cmd/frps/control.go | 2 + src/frp/models/config/config.go | 3 + src/frp/models/msg/msg.go | 2 + src/frp/models/server/server.go | 5 +- src/frp/utils/vhost/http.go | 114 ++++++++++++++++++++++++++++++++ src/frp/utils/vhost/vhost.go | 46 +++++++++++-- 7 files changed, 165 insertions(+), 9 deletions(-) diff --git a/src/frp/cmd/frpc/control.go b/src/frp/cmd/frpc/control.go index 9e8c3e62..ffdfe10d 100644 --- a/src/frp/cmd/frpc/control.go +++ b/src/frp/cmd/frpc/control.go @@ -144,6 +144,8 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { UseGzip: cli.UseGzip, PrivilegeMode: cli.PrivilegeMode, ProxyType: cli.Type, + LocalIp: cli.LocalIp, + LocalPort: cli.LocalPort, Timestamp: nowTime, } if cli.PrivilegeMode { diff --git a/src/frp/cmd/frps/control.go b/src/frp/cmd/frps/control.go index 61660bfa..700a1466 100644 --- a/src/frp/cmd/frps/control.go +++ b/src/frp/cmd/frps/control.go @@ -276,6 +276,8 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { // set infomations from frpc s.UseEncryption = req.UseEncryption s.UseGzip = req.UseGzip + s.ClientIp = req.LocalIp + s.ClientPort = req.LocalPort // start proxy and listen for user connections, no block err := s.Start(c) diff --git a/src/frp/models/config/config.go b/src/frp/models/config/config.go index 14200eb4..b18e6282 100644 --- a/src/frp/models/config/config.go +++ b/src/frp/models/config/config.go @@ -22,4 +22,7 @@ type BaseConf struct { UseGzip bool PrivilegeMode bool PrivilegeToken string + ClientIp string + ClientPort int64 + ServerPort int64 } diff --git a/src/frp/models/msg/msg.go b/src/frp/models/msg/msg.go index e89bce1c..4d06b7c1 100644 --- a/src/frp/models/msg/msg.go +++ b/src/frp/models/msg/msg.go @@ -26,6 +26,8 @@ type ControlReq struct { AuthKey string `json:"auth_key"` UseEncryption bool `json:"use_encryption"` UseGzip bool `json:"use_gzip"` + LocalIp string `json:"local_ip"` + LocalPort int64 `json:"local_port"` // configures used if privilege_mode is enabled PrivilegeMode bool `json:"privilege_mode"` diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index e69a2793..87087b49 100644 --- a/src/frp/models/server/server.go +++ b/src/frp/models/server/server.go @@ -64,6 +64,7 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) { p.BindAddr = BindAddr p.ListenPort = req.RemotePort p.CustomDomains = req.CustomDomains + p.ServerPort = VhostHttpPort return } @@ -113,7 +114,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { p.listeners = append(p.listeners, l) } else if p.Type == "http" { for _, domain := range p.CustomDomains { - l, err := VhostHttpMuxer.Listen(domain) + l, err := VhostHttpMuxer.Listen(domain, p.Type, p.ClientIp, p.ClientPort, p.ServerPort) if err != nil { return err } @@ -121,7 +122,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { } } else if p.Type == "https" { for _, domain := range p.CustomDomains { - l, err := VhostHttpsMuxer.Listen(domain) + l, err := VhostHttpsMuxer.Listen(domain, p.Type, p.ClientIp, p.ClientPort, p.ServerPort) if err != nil { return err } diff --git a/src/frp/utils/vhost/http.go b/src/frp/utils/vhost/http.go index 0f6aab5b..5e78de68 100644 --- a/src/frp/utils/vhost/http.go +++ b/src/frp/utils/vhost/http.go @@ -16,12 +16,17 @@ package vhost import ( "bufio" + "bytes" + "fmt" + "io" "net" "net/http" + "net/url" "strings" "time" "frp/utils/conn" + "frp/utils/log" ) type HttpMuxer struct { @@ -45,3 +50,112 @@ func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, e mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout) return &HttpMuxer{mux}, err } + +func HostNameRewrite(c *conn.Conn, clientHost string) (_ net.Conn, err error) { + log.Info("HostNameRewrite, clientHost: %s", clientHost) + sc, rd := newShareConn(c.TcpConn) + var buff []byte + if buff, err = hostNameRewrite(rd, clientHost); err != nil { + return sc, err + } + err = sc.WriteBuff(buff) + return sc, err +} + +func hostNameRewrite(request io.Reader, clientHost string) (_ []byte, err error) { + buffer := make([]byte, 1024) + request.Read(buffer) + log.Debug("before hostNameRewrite:\n %s", string(buffer)) + retBuffer, err := parseRequest(buffer, clientHost) + log.Debug("after hostNameRewrite:\n %s", string(retBuffer)) + return retBuffer, err +} + +func parseRequest(org []byte, clientHost string) (ret []byte, err error) { + tp := bytes.NewBuffer(org) + // First line: GET /index.html HTTP/1.0 + var b []byte + if b, err = tp.ReadBytes('\n'); err != nil { + return nil, err + } + req := new(http.Request) + //we invoked ReadRequest in GetHttpHostname before, so we ignore error + req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b)) + rawurl := req.RequestURI + //CONNECT www.google.com:443 HTTP/1.1 + justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/") + if justAuthority { + rawurl = "http://" + rawurl + } + req.URL, _ = url.ParseRequestURI(rawurl) + if justAuthority { + // Strip the bogus "http://" back off. + req.URL.Scheme = "" + } + + // RFC2616: first case + // GET /index.html HTTP/1.1 + // Host: www.google.com + if req.URL.Host == "" { + changedBuf, err := changeHostName(tp, clientHost) + buf := new(bytes.Buffer) + buf.Write(b) + buf.Write(changedBuf) + return buf.Bytes(), err + } + + // RFC2616: second case + // GET http://www.google.com/index.html HTTP/1.1 + // Host: doesntmatter + // In this case, any Host line is ignored. + req.URL.Host = clientHost + firstLine := req.Method + " " + req.URL.String() + " " + req.Proto + buf := new(bytes.Buffer) + buf.WriteString(firstLine) + tp.WriteTo(buf) + return buf.Bytes(), err + +} + +// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts. +func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { + s1 := strings.Index(line, " ") + s2 := strings.Index(line[s1+1:], " ") + if s1 < 0 || s2 < 0 { + return + } + s2 += s1 + 1 + return line[:s1], line[s1+1 : s2], line[s2+1:], true +} + +func changeHostName(buff *bytes.Buffer, clientHost string) (_ []byte, err error) { + retBuf := new(bytes.Buffer) + + peek := buff.Bytes() + for len(peek) > 0 { + i := bytes.IndexByte(peek, '\n') + if i < 3 { + // Not present (-1) or found within the next few bytes, + // implying we're at the end ("\r\n\r\n" or "\n\n") + return nil, err + } + kv := peek[:i] + j := bytes.IndexByte(kv, ':') + if j < 0 { + return nil, fmt.Errorf("malformed MIME header line: " + string(kv)) + } + if strings.Contains(strings.ToLower(string(kv[:j])), "host") { + hostHeader := fmt.Sprintf("Host: %s\n", clientHost) + retBuf.WriteString(hostHeader) + peek = peek[i+1:] + break + } else { + retBuf.Write(peek[:i]) + retBuf.WriteByte('\n') + } + + peek = peek[i+1:] + } + retBuf.Write(peek) + return retBuf.Bytes(), err +} diff --git a/src/frp/utils/vhost/vhost.go b/src/frp/utils/vhost/vhost.go index ae672097..11038296 100644 --- a/src/frp/utils/vhost/vhost.go +++ b/src/frp/utils/vhost/vhost.go @@ -34,6 +34,10 @@ type VhostMuxer struct { vhostFunc muxFunc registryMap map[string]*Listener mutex sync.RWMutex + + //build map between custom_domains and client_domain + domainMap map[string]string + domainMutex sync.RWMutex } func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) { @@ -47,7 +51,7 @@ func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Dura return mux, nil } -func (v *VhostMuxer) Listen(name string) (l *Listener, err error) { +func (v *VhostMuxer) Listen(name, proxytype, clientIp string, clientPort, serverPort int64) (l *Listener, err error) { v.mutex.Lock() defer v.mutex.Unlock() if _, exist := v.registryMap[name]; exist { @@ -55,9 +59,13 @@ func (v *VhostMuxer) Listen(name string) (l *Listener, err error) { } l = &Listener{ - name: name, - mux: v, - accept: make(chan *conn.Conn), + name: name, + mux: v, + accept: make(chan *conn.Conn), + proxyType: proxytype, + clientIp: clientIp, + clientPort: clientPort, + serverPort: serverPort, } v.registryMap[name] = l return l, nil @@ -111,9 +119,13 @@ func (v *VhostMuxer) handle(c *conn.Conn) { } type Listener struct { - name string - mux *VhostMuxer // for closing VhostMuxer - accept chan *conn.Conn + name string + mux *VhostMuxer // for closing VhostMuxer + accept chan *conn.Conn + proxyType string //suppor http host rewrite + clientIp string + clientPort int64 + serverPort int64 } func (l *Listener) Accept() (*conn.Conn, error) { @@ -121,6 +133,20 @@ func (l *Listener) Accept() (*conn.Conn, error) { if !ok { return nil, fmt.Errorf("Listener closed") } + if net.ParseIP(l.clientIp) == nil && l.proxyType == "http" { + if (l.name != l.clientIp) || (l.serverPort != l.clientPort) { + clientHost := l.clientIp + if l.clientPort != 80 { + strPort := fmt.Sprintf(":%d", l.clientPort) + clientHost += strPort + } + retConn, err := HostNameRewrite(conn, clientHost) + if err != nil { + return nil, fmt.Errorf("http host rewrite failed") + } + conn.SetTcpConn(retConn) + } + } return conn, nil } @@ -166,3 +192,9 @@ func (sc *sharedConn) Read(p []byte) (n int, err error) { sc.Unlock() return } + +func (sc *sharedConn) WriteBuff(buffer []byte) (err error) { + sc.buff.Reset() + _, err = sc.buff.Write(buffer) + return err +} From 6dd51e095137aff36ba66dc0417402fa68bbf823 Mon Sep 17 00:00:00 2001 From: Maodanping <673698750@qq.com> Date: Sun, 24 Jul 2016 15:08:01 +0800 Subject: [PATCH 4/5] remove the extra filed --- src/frp/utils/vhost/vhost.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/frp/utils/vhost/vhost.go b/src/frp/utils/vhost/vhost.go index 11038296..d9a6b238 100644 --- a/src/frp/utils/vhost/vhost.go +++ b/src/frp/utils/vhost/vhost.go @@ -34,10 +34,6 @@ type VhostMuxer struct { vhostFunc muxFunc registryMap map[string]*Listener mutex sync.RWMutex - - //build map between custom_domains and client_domain - domainMap map[string]string - domainMutex sync.RWMutex } func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) { From 452e02adabf4e81409cc23b7d2c60b7abd304f9e Mon Sep 17 00:00:00 2001 From: fatedier Date: Tue, 26 Jul 2016 00:18:19 +0800 Subject: [PATCH 5/5] add host_header_rewrite in frpc.ini to rewrite your requests with a modified Host header --- conf/frpc.ini | 1 + src/frp/cmd/frpc/control.go | 17 +++++------ src/frp/cmd/frps/control.go | 3 +- src/frp/models/client/config.go | 10 ++++++ src/frp/models/config/config.go | 18 +++++------ src/frp/models/msg/msg.go | 15 +++++---- src/frp/models/server/server.go | 8 ++--- src/frp/utils/conn/conn.go | 5 +++ src/frp/utils/vhost/http.go | 39 ++++++++++++++---------- src/frp/utils/vhost/https.go | 2 +- src/frp/utils/vhost/vhost.go | 54 +++++++++++++++------------------ 11 files changed, 93 insertions(+), 79 deletions(-) diff --git a/conf/frpc.ini b/conf/frpc.ini index bbab6d09..c0f0e5e8 100644 --- a/conf/frpc.ini +++ b/conf/frpc.ini @@ -52,3 +52,4 @@ local_ip = 127.0.0.1 local_port = 80 use_gzip = true custom_domains = web03.yourdomain.com +host_header_rewrite = example.com diff --git a/src/frp/cmd/frpc/control.go b/src/frp/cmd/frpc/control.go index ffdfe10d..7aaff6ba 100644 --- a/src/frp/cmd/frpc/control.go +++ b/src/frp/cmd/frpc/control.go @@ -138,15 +138,14 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { nowTime := time.Now().Unix() req := &msg.ControlReq{ - Type: consts.NewCtlConn, - ProxyName: cli.Name, - UseEncryption: cli.UseEncryption, - UseGzip: cli.UseGzip, - PrivilegeMode: cli.PrivilegeMode, - ProxyType: cli.Type, - LocalIp: cli.LocalIp, - LocalPort: cli.LocalPort, - Timestamp: nowTime, + Type: consts.NewCtlConn, + ProxyName: cli.Name, + UseEncryption: cli.UseEncryption, + UseGzip: cli.UseGzip, + PrivilegeMode: cli.PrivilegeMode, + ProxyType: cli.Type, + HostHeaderRewrite: cli.HostHeaderRewrite, + Timestamp: nowTime, } if cli.PrivilegeMode { privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime)) diff --git a/src/frp/cmd/frps/control.go b/src/frp/cmd/frps/control.go index 700a1466..083ab3c2 100644 --- a/src/frp/cmd/frps/control.go +++ b/src/frp/cmd/frps/control.go @@ -276,8 +276,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { // set infomations from frpc s.UseEncryption = req.UseEncryption s.UseGzip = req.UseGzip - s.ClientIp = req.LocalIp - s.ClientPort = req.LocalPort + s.HostHeaderRewrite = req.HostHeaderRewrite // start proxy and listen for user connections, no block err := s.Start(c) diff --git a/src/frp/models/client/config.go b/src/frp/models/client/config.go index 0d942e95..9bf1f002 100644 --- a/src/frp/models/client/config.go +++ b/src/frp/models/client/config.go @@ -140,6 +140,14 @@ func LoadConf(confFile string) (err error) { proxyClient.UseGzip = true } + if proxyClient.Type == "http" { + // host_header_rewrite + tmpStr, ok = section["host_header_rewrite"] + if ok { + proxyClient.HostHeaderRewrite = tmpStr + } + } + // privilege_mode proxyClient.PrivilegeMode = false tmpStr, ok = section["privilege_mode"] @@ -167,6 +175,7 @@ func LoadConf(confFile string) (err error) { return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name) } } else if proxyClient.Type == "http" { + // custom_domains domainStr, ok := section["custom_domains"] if ok { proxyClient.CustomDomains = strings.Split(domainStr, ",") @@ -180,6 +189,7 @@ func LoadConf(confFile string) (err error) { return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name) } } else if proxyClient.Type == "https" { + // custom_domains domainStr, ok := section["custom_domains"] if ok { proxyClient.CustomDomains = strings.Split(domainStr, ",") diff --git a/src/frp/models/config/config.go b/src/frp/models/config/config.go index b18e6282..2e862d4e 100644 --- a/src/frp/models/config/config.go +++ b/src/frp/models/config/config.go @@ -15,14 +15,12 @@ package config type BaseConf struct { - Name string - AuthToken string - Type string - UseEncryption bool - UseGzip bool - PrivilegeMode bool - PrivilegeToken string - ClientIp string - ClientPort int64 - ServerPort int64 + Name string + AuthToken string + Type string + UseEncryption bool + UseGzip bool + PrivilegeMode bool + PrivilegeToken string + HostHeaderRewrite string } diff --git a/src/frp/models/msg/msg.go b/src/frp/models/msg/msg.go index 4d06b7c1..d4efcb2b 100644 --- a/src/frp/models/msg/msg.go +++ b/src/frp/models/msg/msg.go @@ -26,16 +26,15 @@ type ControlReq struct { AuthKey string `json:"auth_key"` UseEncryption bool `json:"use_encryption"` UseGzip bool `json:"use_gzip"` - LocalIp string `json:"local_ip"` - LocalPort int64 `json:"local_port"` // configures used if privilege_mode is enabled - PrivilegeMode bool `json:"privilege_mode"` - PrivilegeKey string `json:"privilege_key"` - ProxyType string `json:"proxy_type"` - RemotePort int64 `json:"remote_port"` - CustomDomains []string `json:"custom_domains, omitempty"` - Timestamp int64 `json:"timestamp"` + PrivilegeMode bool `json:"privilege_mode"` + PrivilegeKey string `json:"privilege_key"` + ProxyType string `json:"proxy_type"` + RemotePort int64 `json:"remote_port"` + CustomDomains []string `json:"custom_domains, omitempty"` + HostHeaderRewrite string `json:"host_header_rewrite"` + Timestamp int64 `json:"timestamp"` } type ControlRes struct { diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index 87087b49..28570a91 100644 --- a/src/frp/models/server/server.go +++ b/src/frp/models/server/server.go @@ -64,7 +64,7 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) { p.BindAddr = BindAddr p.ListenPort = req.RemotePort p.CustomDomains = req.CustomDomains - p.ServerPort = VhostHttpPort + p.HostHeaderRewrite = req.HostHeaderRewrite return } @@ -80,7 +80,7 @@ func (p *ProxyServer) Init() { func (p *ProxyServer) Compare(p2 *ProxyServer) bool { if p.Name != p2.Name || p.AuthToken != p2.AuthToken || p.Type != p2.Type || - p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort { + p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite { return false } if len(p.CustomDomains) != len(p2.CustomDomains) { @@ -114,7 +114,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { p.listeners = append(p.listeners, l) } else if p.Type == "http" { for _, domain := range p.CustomDomains { - l, err := VhostHttpMuxer.Listen(domain, p.Type, p.ClientIp, p.ClientPort, p.ServerPort) + l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite) if err != nil { return err } @@ -122,7 +122,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { } } else if p.Type == "https" { for _, domain := range p.CustomDomains { - l, err := VhostHttpsMuxer.Listen(domain, p.Type, p.ClientIp, p.ClientPort, p.ServerPort) + l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite) if err != nil { return err } diff --git a/src/frp/utils/conn/conn.go b/src/frp/utils/conn/conn.go index a5a7a335..2ce69e29 100644 --- a/src/frp/utils/conn/conn.go +++ b/src/frp/utils/conn/conn.go @@ -117,8 +117,13 @@ func ConnectServer(host string, port int64) (c *Conn, err error) { return c, nil } +// if the tcpConn is different with c.TcpConn +// you should call c.Close() first func (c *Conn) SetTcpConn(tcpConn net.Conn) { + c.mutex.Lock() + defer c.mutex.Unlock() c.TcpConn = tcpConn + c.closeFlag = false c.Reader = bufio.NewReader(c.TcpConn) } diff --git a/src/frp/utils/vhost/http.go b/src/frp/utils/vhost/http.go index 5e78de68..4bc720cc 100644 --- a/src/frp/utils/vhost/http.go +++ b/src/frp/utils/vhost/http.go @@ -26,7 +26,6 @@ import ( "time" "frp/utils/conn" - "frp/utils/log" ) type HttpMuxer struct { @@ -47,31 +46,28 @@ func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) { } func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) { - mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout) + mux, err := NewVhostMuxer(listener, GetHttpHostname, HttpHostNameRewrite, timeout) return &HttpMuxer{mux}, err } -func HostNameRewrite(c *conn.Conn, clientHost string) (_ net.Conn, err error) { - log.Info("HostNameRewrite, clientHost: %s", clientHost) +func HttpHostNameRewrite(c *conn.Conn, rewriteHost string) (_ net.Conn, err error) { sc, rd := newShareConn(c.TcpConn) var buff []byte - if buff, err = hostNameRewrite(rd, clientHost); err != nil { + if buff, err = hostNameRewrite(rd, rewriteHost); err != nil { return sc, err } err = sc.WriteBuff(buff) return sc, err } -func hostNameRewrite(request io.Reader, clientHost string) (_ []byte, err error) { +func hostNameRewrite(request io.Reader, rewriteHost string) (_ []byte, err error) { buffer := make([]byte, 1024) request.Read(buffer) - log.Debug("before hostNameRewrite:\n %s", string(buffer)) - retBuffer, err := parseRequest(buffer, clientHost) - log.Debug("after hostNameRewrite:\n %s", string(retBuffer)) + retBuffer, err := parseRequest(buffer, rewriteHost) return retBuffer, err } -func parseRequest(org []byte, clientHost string) (ret []byte, err error) { +func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) { tp := bytes.NewBuffer(org) // First line: GET /index.html HTTP/1.0 var b []byte @@ -79,10 +75,10 @@ func parseRequest(org []byte, clientHost string) (ret []byte, err error) { return nil, err } req := new(http.Request) - //we invoked ReadRequest in GetHttpHostname before, so we ignore error + // we invoked ReadRequest in GetHttpHostname before, so we ignore error req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b)) rawurl := req.RequestURI - //CONNECT www.google.com:443 HTTP/1.1 + // CONNECT www.google.com:443 HTTP/1.1 justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/") if justAuthority { rawurl = "http://" + rawurl @@ -97,7 +93,7 @@ func parseRequest(org []byte, clientHost string) (ret []byte, err error) { // GET /index.html HTTP/1.1 // Host: www.google.com if req.URL.Host == "" { - changedBuf, err := changeHostName(tp, clientHost) + changedBuf, err := changeHostName(tp, rewriteHost) buf := new(bytes.Buffer) buf.Write(b) buf.Write(changedBuf) @@ -108,7 +104,12 @@ func parseRequest(org []byte, clientHost string) (ret []byte, err error) { // GET http://www.google.com/index.html HTTP/1.1 // Host: doesntmatter // In this case, any Host line is ignored. - req.URL.Host = clientHost + hostPort := strings.Split(req.URL.Host, ":") + if len(hostPort) == 1 { + req.URL.Host = rewriteHost + } else if len(hostPort) == 2 { + req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1]) + } firstLine := req.Method + " " + req.URL.String() + " " + req.Proto buf := new(bytes.Buffer) buf.WriteString(firstLine) @@ -128,7 +129,7 @@ func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { return line[:s1], line[s1+1 : s2], line[s2+1:], true } -func changeHostName(buff *bytes.Buffer, clientHost string) (_ []byte, err error) { +func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) { retBuf := new(bytes.Buffer) peek := buff.Bytes() @@ -145,7 +146,13 @@ func changeHostName(buff *bytes.Buffer, clientHost string) (_ []byte, err error) return nil, fmt.Errorf("malformed MIME header line: " + string(kv)) } if strings.Contains(strings.ToLower(string(kv[:j])), "host") { - hostHeader := fmt.Sprintf("Host: %s\n", clientHost) + var hostHeader string + portPos := bytes.IndexByte(kv[j+1:], ':') + if portPos == -1 { + hostHeader = fmt.Sprintf("Host: %s\n", rewriteHost) + } else { + hostHeader = fmt.Sprintf("Host: %s:%s\n", rewriteHost, kv[portPos+1:]) + } retBuf.WriteString(hostHeader) peek = peek[i+1:] break diff --git a/src/frp/utils/vhost/https.go b/src/frp/utils/vhost/https.go index 2fd61c62..eedfab37 100644 --- a/src/frp/utils/vhost/https.go +++ b/src/frp/utils/vhost/https.go @@ -47,7 +47,7 @@ type HttpsMuxer struct { } func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) { - mux, err := NewVhostMuxer(listener, GetHttpsHostname, timeout) + mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, timeout) return &HttpsMuxer{mux}, err } diff --git a/src/frp/utils/vhost/vhost.go b/src/frp/utils/vhost/vhost.go index d9a6b238..18c6d5dd 100644 --- a/src/frp/utils/vhost/vhost.go +++ b/src/frp/utils/vhost/vhost.go @@ -27,41 +27,42 @@ import ( ) type muxFunc func(*conn.Conn) (net.Conn, string, error) +type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error) type VhostMuxer struct { listener *conn.Listener timeout time.Duration vhostFunc muxFunc + rewriteFunc hostRewriteFunc registryMap map[string]*Listener mutex sync.RWMutex } -func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) { +func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) { mux = &VhostMuxer{ listener: listener, timeout: timeout, vhostFunc: vhostFunc, + rewriteFunc: rewriteFunc, registryMap: make(map[string]*Listener), } go mux.run() return mux, nil } -func (v *VhostMuxer) Listen(name, proxytype, clientIp string, clientPort, serverPort int64) (l *Listener, err error) { +// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil, then rewrite the host header to rewriteHost +func (v *VhostMuxer) Listen(name string, rewriteHost string) (l *Listener, err error) { v.mutex.Lock() defer v.mutex.Unlock() if _, exist := v.registryMap[name]; exist { - return nil, fmt.Errorf("name %s is already bound", name) + return nil, fmt.Errorf("domain name %s is already bound", name) } l = &Listener{ - name: name, - mux: v, - accept: make(chan *conn.Conn), - proxyType: proxytype, - clientIp: clientIp, - clientPort: clientPort, - serverPort: serverPort, + name: name, + rewriteHost: rewriteHost, + mux: v, + accept: make(chan *conn.Conn), } v.registryMap[name] = l return l, nil @@ -115,13 +116,10 @@ func (v *VhostMuxer) handle(c *conn.Conn) { } type Listener struct { - name string - mux *VhostMuxer // for closing VhostMuxer - accept chan *conn.Conn - proxyType string //suppor http host rewrite - clientIp string - clientPort int64 - serverPort int64 + name string + rewriteHost string + mux *VhostMuxer // for closing VhostMuxer + accept chan *conn.Conn } func (l *Listener) Accept() (*conn.Conn, error) { @@ -129,19 +127,16 @@ func (l *Listener) Accept() (*conn.Conn, error) { if !ok { return nil, fmt.Errorf("Listener closed") } - if net.ParseIP(l.clientIp) == nil && l.proxyType == "http" { - if (l.name != l.clientIp) || (l.serverPort != l.clientPort) { - clientHost := l.clientIp - if l.clientPort != 80 { - strPort := fmt.Sprintf(":%d", l.clientPort) - clientHost += strPort - } - retConn, err := HostNameRewrite(conn, clientHost) - if err != nil { - return nil, fmt.Errorf("http host rewrite failed") - } - conn.SetTcpConn(retConn) + + // if rewriteFunc is exist and rewriteHost is set + // rewrite http requests with a modified host header + if l.mux.rewriteFunc != nil && l.rewriteHost != "" { + fmt.Printf("host rewrite: %s\n", l.rewriteHost) + sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost) + if err != nil { + return nil, fmt.Errorf("http host header rewrite failed") } + conn.SetTcpConn(sConn) } return conn, nil } @@ -162,6 +157,7 @@ type sharedConn struct { buff *bytes.Buffer } +// the bytes you read in io.Reader, will be reserved in sharedConn func newShareConn(conn net.Conn) (*sharedConn, io.Reader) { sc := &sharedConn{ Conn: conn,