123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- package proxy
- import (
- "net"
- "net/http"
- "net/url"
- "sync"
- "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"
- "github.com/astaxie/beego"
- "github.com/astaxie/beego/logs"
- "github.com/pkg/errors"
- )
- type HttpsServer struct {
- httpServer
- listener net.Listener
- httpsListenerMap sync.Map
- }
- func NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer {
- https := &HttpsServer{listener: l}
- https.bridge = bridge
- https.useCache = useCache
- if useCache {
- https.cache = cache.New(cacheLen)
- }
- return https
- }
- //start https server
- 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 {
- //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)
- }
- conn.Accept(https.listener, func(c net.Conn) {
- serverName, rb := GetServerNameFromClientHello(c)
- //if the clientHello does not contains sni ,use the default ssl certificate
- if serverName == "" {
- serverName = "default"
- }
- var l *HttpsListener
- if v, ok := https.httpsListenerMap.Load(serverName); ok {
- l = v.(*HttpsListener)
- } else {
- r := buildHttpsRequest(serverName)
- if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
- c.Close()
- logs.Notice("the url %s can't be parsed!,remote addr %s", serverName, c.RemoteAddr().String())
- return
- } else {
- if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
- //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)
- }
- }
- }
- acceptConn := conn.NewConn(c)
- acceptConn.Rb = rb
- l.acceptConn <- acceptConn
- })
- }
- return nil
- }
- // close
- func (https *HttpsServer) Close() error {
- return https.listener.Close()
- }
- // new https server by cert and key file
- func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
- go func() {
- logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
- }()
- }
- //handle the https which is just proxy to other client
- func (https *HttpsServer) handleHttps(c net.Conn) {
- hostName, rb := GetServerNameFromClientHello(c)
- var targetAddr string
- r := buildHttpsRequest(hostName)
- 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())
- https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy)
- }
- type HttpsListener struct {
- acceptConn chan *conn.Conn
- parentListener net.Listener
- }
- // https listener
- func NewHttpsListener(l net.Listener) *HttpsListener {
- return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
- }
- // accept
- func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
- httpsConn := <-httpsListener.acceptConn
- if httpsConn == nil {
- return nil, errors.New("get connection error")
- }
- return httpsConn, nil
- }
- // close
- func (httpsListener *HttpsListener) Close() error {
- return nil
- }
- // addr
- func (httpsListener *HttpsListener) Addr() net.Addr {
- return httpsListener.parentListener.Addr()
- }
- // get server name from connection by read client hello bytes
- 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
- }
- if n < 42 {
- return "", nil
- }
- copy(data, buf[:n])
- clientHello := new(crypt.ClientHelloMsg)
- clientHello.Unmarshal(data[5:n])
- return clientHello.GetServerName(), buf[:n]
- }
- // build https request
- 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
- }
|