mirror of
https://gitee.com/IrisVega/frp.git
synced 2024-11-01 22:31:29 +08:00
commit
e945c1667a
@ -187,9 +187,9 @@ func parseServerCommonCfgFromCmd() (err error) {
|
|||||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||||
|
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbClientCfg.LogWay = "console"
|
g.GlbServerCfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbClientCfg.LogWay = "file"
|
g.GlbServerCfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,12 @@ custom_domains = test6.frp.com
|
|||||||
host_header_rewrite = test6.frp.com
|
host_header_rewrite = test6.frp.com
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
|
|
||||||
|
[wildcard_http]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10704
|
||||||
|
custom_domains = *.frp1.com
|
||||||
|
|
||||||
[subhost01]
|
[subhost01]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
|
@ -182,6 +182,21 @@ func TestHttp(t *testing.T) {
|
|||||||
assert.Equal("true", header.Get("X-Header-Set"))
|
assert.Equal("true", header.Get("X-Header-Set"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wildcard_http
|
||||||
|
// test.frp1.com match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// new.test.frp1.com also match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
// subhost01
|
// subhost01
|
||||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
|
@ -88,8 +88,10 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
|
||||||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
|
strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
|
||||||
|
strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
|
||||||
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
|
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
|
||||||
} else if strings.Contains(r.Host, "test3.frp.com") {
|
} else if strings.Contains(r.Host, "test3.frp.com") {
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.25.0"
|
var version string = "0.25.1"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package vhost
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HttpMuxer struct {
|
|
||||||
*VhostMuxer
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
|
||||||
reqInfoMap := make(map[string]string, 0)
|
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
|
||||||
|
|
||||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
|
||||||
if err != nil {
|
|
||||||
return nil, reqInfoMap, err
|
|
||||||
}
|
|
||||||
// hostName
|
|
||||||
tmpArr := strings.Split(request.Host, ":")
|
|
||||||
reqInfoMap["Host"] = tmpArr[0]
|
|
||||||
reqInfoMap["Path"] = request.URL.Path
|
|
||||||
reqInfoMap["Scheme"] = request.URL.Scheme
|
|
||||||
|
|
||||||
// Authorization
|
|
||||||
authStr := request.Header.Get("Authorization")
|
|
||||||
if authStr != "" {
|
|
||||||
reqInfoMap["Authorization"] = authStr
|
|
||||||
}
|
|
||||||
request.Body.Close()
|
|
||||||
return frpNet.WrapConn(sc), reqInfoMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
|
||||||
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
|
|
||||||
return &HttpMuxer{mux}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
|
||||||
var buff []byte
|
|
||||||
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
|
|
||||||
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = sc.ResetBuf(buff)
|
|
||||||
return frpNet.WrapConn(sc), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
defer pool.PutBuf(buf)
|
|
||||||
|
|
||||||
var n int
|
|
||||||
n, err = request.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
|
|
||||||
return retBuffer, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRequest(org []byte, rewriteHost string, remoteIP 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 == "" {
|
|
||||||
var changedBuf []byte
|
|
||||||
if rewriteHost != "" {
|
|
||||||
changedBuf, err = changeHostName(tp, rewriteHost)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.Write(b)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
if len(changedBuf) == 0 {
|
|
||||||
tp.WriteTo(buf)
|
|
||||||
} else {
|
|
||||||
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.
|
|
||||||
if rewriteHost != "" {
|
|
||||||
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)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
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, rewriteHost 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") {
|
|
||||||
var hostHeader string
|
|
||||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
|
||||||
if portPos == -1 {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
|
|
||||||
} else {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
|
|
||||||
s := strings.SplitN(authorization, " ", 2)
|
|
||||||
if len(s) != 2 {
|
|
||||||
res := noAuthResponse()
|
|
||||||
res.Write(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pair := strings.SplitN(string(b), ":", 2)
|
|
||||||
if len(pair) != 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pair[0] != userName || pair[1] != passWord {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func noAuthResponse() *http.Response {
|
|
||||||
header := make(map[string][]string)
|
|
||||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
|
||||||
res := &http.Response{
|
|
||||||
Status: "401 Not authorized",
|
|
||||||
StatusCode: 401,
|
|
||||||
Proto: "HTTP/1.1",
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Header: header,
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
@ -18,6 +18,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -145,7 +146,7 @@ func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (ne
|
|||||||
return fn()
|
return fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ErrNoDomain
|
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||||
@ -173,11 +174,22 @@ func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostR
|
|||||||
|
|
||||||
domainSplit := strings.Split(domain, ".")
|
domainSplit := strings.Split(domain, ".")
|
||||||
if len(domainSplit) < 3 {
|
if len(domainSplit) < 3 {
|
||||||
return vr, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
domainSplit[0] = "*"
|
domainSplit[0] = "*"
|
||||||
domain = strings.Join(domainSplit, ".")
|
domain = strings.Join(domainSplit, ".")
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return vr, true
|
||||||
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,3 +61,17 @@ func notFoundResponse() *http.Response {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func noAuthResponse() *http.Response {
|
||||||
|
header := make(map[string][]string)
|
||||||
|
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "401 Not authorized",
|
||||||
|
StatusCode: 401,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
@ -102,18 +102,25 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
|||||||
|
|
||||||
domainSplit := strings.Split(name, ".")
|
domainSplit := strings.Split(name, ".")
|
||||||
if len(domainSplit) < 3 {
|
if len(domainSplit) < 3 {
|
||||||
return l, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
domainSplit[0] = "*"
|
domainSplit[0] = "*"
|
||||||
name = strings.Join(domainSplit, ".")
|
name = strings.Join(domainSplit, ".")
|
||||||
|
|
||||||
vr, found = v.registryRouter.Get(name, path)
|
vr, found = v.registryRouter.Get(name, path)
|
||||||
if !found {
|
if found {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return vr.payload.(*Listener), true
|
return vr.payload.(*Listener), true
|
||||||
}
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) run() {
|
func (v *VhostMuxer) run() {
|
||||||
for {
|
for {
|
||||||
|
Loading…
Reference in New Issue
Block a user