nps/server/proxy/https.go

186 lines
5.1 KiB
Go
Raw Normal View History

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
}