2019-03-30 04:03:17 +00:00
|
|
|
package proxy
|
|
|
|
|
|
|
|
import (
|
2019-08-10 03:15:25 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"sync"
|
|
|
|
|
2020-01-08 13:57:14 +00:00
|
|
|
"ehang.io/nps/lib/cache"
|
|
|
|
"ehang.io/nps/lib/common"
|
|
|
|
"ehang.io/nps/lib/conn"
|
|
|
|
"ehang.io/nps/lib/crypt"
|
|
|
|
"ehang.io/nps/lib/file"
|
2019-08-10 03:15:25 +00:00
|
|
|
"github.com/astaxie/beego"
|
|
|
|
"github.com/astaxie/beego/logs"
|
2019-03-30 04:03:17 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type HttpsServer struct {
|
|
|
|
httpServer
|
|
|
|
listener net.Listener
|
|
|
|
httpsListenerMap sync.Map
|
|
|
|
}
|
|
|
|
|
2019-04-21 15:03:58 +00:00
|
|
|
func NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer {
|
2019-03-30 04:03:17 +00:00
|
|
|
https := &HttpsServer{listener: l}
|
|
|
|
https.bridge = bridge
|
2019-04-08 09:01:08 +00:00
|
|
|
https.useCache = useCache
|
|
|
|
if useCache {
|
|
|
|
https.cache = cache.New(cacheLen)
|
|
|
|
}
|
2019-03-30 04:03:17 +00:00
|
|
|
return https
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
//start https server
|
2019-03-30 04:03:17 +00:00
|
|
|
func (https *HttpsServer) Start() error {
|
|
|
|
if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
|
|
|
|
conn.Accept(https.listener, func(c net.Conn) {
|
|
|
|
https.handleHttps(c)
|
|
|
|
})
|
|
|
|
} else {
|
2019-04-01 15:54:03 +00:00
|
|
|
//start the default listener
|
|
|
|
certFile := beego.AppConfig.String("https_default_cert_file")
|
|
|
|
keyFile := beego.AppConfig.String("https_default_key_file")
|
|
|
|
if common.FileExists(certFile) && common.FileExists(keyFile) {
|
|
|
|
l := NewHttpsListener(https.listener)
|
|
|
|
https.NewHttps(l, certFile, keyFile)
|
|
|
|
https.httpsListenerMap.Store("default", l)
|
|
|
|
}
|
2019-03-30 04:03:17 +00:00
|
|
|
conn.Accept(https.listener, func(c net.Conn) {
|
|
|
|
serverName, rb := GetServerNameFromClientHello(c)
|
2019-04-01 15:54:03 +00:00
|
|
|
//if the clientHello does not contains sni ,use the default ssl certificate
|
|
|
|
if serverName == "" {
|
|
|
|
serverName = "default"
|
|
|
|
}
|
2019-03-30 04:03:17 +00:00
|
|
|
var l *HttpsListener
|
|
|
|
if v, ok := https.httpsListenerMap.Load(serverName); ok {
|
|
|
|
l = v.(*HttpsListener)
|
|
|
|
} else {
|
2019-03-30 08:35:43 +00:00
|
|
|
r := buildHttpsRequest(serverName)
|
2019-03-30 04:03:17 +00:00
|
|
|
if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
|
|
|
|
c.Close()
|
2019-04-01 15:54:03 +00:00
|
|
|
logs.Notice("the url %s can't be parsed!,remote addr %s", serverName, c.RemoteAddr().String())
|
2019-03-30 04:03:17 +00:00
|
|
|
return
|
|
|
|
} else {
|
|
|
|
if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
|
2019-04-01 15:54:03 +00:00
|
|
|
//if the host cert file or key file is not set ,use the default file
|
|
|
|
if v, ok := https.httpsListenerMap.Load("default"); ok {
|
|
|
|
l = v.(*HttpsListener)
|
|
|
|
} else {
|
|
|
|
c.Close()
|
|
|
|
logs.Error("the key %s cert %s file is not exist", host.KeyFilePath, host.CertFilePath)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
l = NewHttpsListener(https.listener)
|
|
|
|
https.NewHttps(l, host.CertFilePath, host.KeyFilePath)
|
|
|
|
https.httpsListenerMap.Store(serverName, l)
|
2019-03-30 04:03:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
acceptConn := conn.NewConn(c)
|
|
|
|
acceptConn.Rb = rb
|
|
|
|
l.acceptConn <- acceptConn
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// close
|
2019-03-30 04:03:17 +00:00
|
|
|
func (https *HttpsServer) Close() error {
|
|
|
|
return https.listener.Close()
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// new https server by cert and key file
|
2019-03-30 04:03:17 +00:00
|
|
|
func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
|
|
|
|
go func() {
|
|
|
|
logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
//handle the https which is just proxy to other client
|
2019-03-30 04:03:17 +00:00
|
|
|
func (https *HttpsServer) handleHttps(c net.Conn) {
|
|
|
|
hostName, rb := GetServerNameFromClientHello(c)
|
|
|
|
var targetAddr string
|
2019-03-30 08:35:43 +00:00
|
|
|
r := buildHttpsRequest(hostName)
|
2019-03-30 04:03:17 +00:00
|
|
|
var host *file.Host
|
|
|
|
var err error
|
|
|
|
if host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil {
|
|
|
|
c.Close()
|
|
|
|
logs.Notice("the url %s can't be parsed!", hostName)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := https.CheckFlowAndConnNum(host.Client); err != nil {
|
|
|
|
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
|
|
|
|
c.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer host.Client.AddConn()
|
|
|
|
if err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
|
|
|
|
logs.Warn("auth error", err, r.RemoteAddr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
|
|
|
|
logs.Warn(err.Error())
|
|
|
|
}
|
|
|
|
logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
|
2019-04-08 09:01:08 +00:00
|
|
|
https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy)
|
2019-03-30 04:03:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type HttpsListener struct {
|
|
|
|
acceptConn chan *conn.Conn
|
|
|
|
parentListener net.Listener
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// https listener
|
2019-03-30 04:03:17 +00:00
|
|
|
func NewHttpsListener(l net.Listener) *HttpsListener {
|
|
|
|
return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// accept
|
2019-03-30 04:03:17 +00:00
|
|
|
func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
|
|
|
|
httpsConn := <-httpsListener.acceptConn
|
|
|
|
if httpsConn == nil {
|
|
|
|
return nil, errors.New("get connection error")
|
|
|
|
}
|
|
|
|
return httpsConn, nil
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// close
|
2019-03-30 04:03:17 +00:00
|
|
|
func (httpsListener *HttpsListener) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// addr
|
2019-03-30 04:03:17 +00:00
|
|
|
func (httpsListener *HttpsListener) Addr() net.Addr {
|
|
|
|
return httpsListener.parentListener.Addr()
|
|
|
|
}
|
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// get server name from connection by read client hello bytes
|
2019-03-30 04:03:17 +00:00
|
|
|
func GetServerNameFromClientHello(c net.Conn) (string, []byte) {
|
|
|
|
buf := make([]byte, 4096)
|
|
|
|
data := make([]byte, 4096)
|
|
|
|
n, err := c.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
2019-04-17 06:17:59 +00:00
|
|
|
if n < 42 {
|
|
|
|
return "", nil
|
|
|
|
}
|
2019-03-30 04:03:17 +00:00
|
|
|
copy(data, buf[:n])
|
|
|
|
clientHello := new(crypt.ClientHelloMsg)
|
|
|
|
clientHello.Unmarshal(data[5:n])
|
|
|
|
return clientHello.GetServerName(), buf[:n]
|
|
|
|
}
|
2019-03-30 08:35:43 +00:00
|
|
|
|
2019-04-08 10:02:54 +00:00
|
|
|
// build https request
|
2019-03-30 08:35:43 +00:00
|
|
|
func buildHttpsRequest(hostName string) *http.Request {
|
|
|
|
r := new(http.Request)
|
|
|
|
r.RequestURI = "/"
|
|
|
|
r.URL = new(url.URL)
|
|
|
|
r.URL.Scheme = "https"
|
|
|
|
r.Host = hostName
|
|
|
|
return r
|
|
|
|
}
|