mirror of https://github.com/qwqdanchun/nps.git
Multiple HTTPS certificate support
This commit is contained in:
parent
5fd335f330
commit
2b841adb1b
|
@ -162,7 +162,7 @@ func (s *Bridge) verifySuccess(c *conn.Conn) {
|
|||
func (s *Bridge) cliProcess(c *conn.Conn) {
|
||||
//read test flag
|
||||
if _, err := c.GetShortContent(3); err != nil {
|
||||
logs.Info("The client %s connect error", c.Conn.RemoteAddr())
|
||||
logs.Info("The client %s connect error", c.Conn.RemoteAddr(), err.Error())
|
||||
return
|
||||
}
|
||||
//version check
|
||||
|
@ -173,7 +173,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
|
|||
}
|
||||
//write server version to client
|
||||
c.Write([]byte(crypt.Md5(version.GetVersion())))
|
||||
c.SetReadDeadline(5, s.tunnelType)
|
||||
c.SetReadDeadlineByType(5, s.tunnelType)
|
||||
var buf []byte
|
||||
var err error
|
||||
//get vKey from client
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/cnlh/nps/lib/common"
|
||||
|
@ -14,6 +15,8 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -180,11 +183,16 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
|
|||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "socks5":
|
||||
n, er := proxy.FromURL(u, nil)
|
||||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
connection, err = n.Dial("tcp", server)
|
||||
case "http":
|
||||
connection, err = NewHttpProxyConn(u, server)
|
||||
}
|
||||
} else {
|
||||
connection, err = net.Dial("tcp", server)
|
||||
}
|
||||
|
@ -230,3 +238,36 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
|
|||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
|
||||
req := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: url,
|
||||
Host: remoteAddr,
|
||||
Header: http.Header{},
|
||||
Proto: "HTTP/1.1",
|
||||
}
|
||||
password, _ := url.User.Password()
|
||||
req.Header.Set("Proxy-Authorization", "Basic "+basicAuth(url.User.Username(), password))
|
||||
b, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxyConn, err := net.Dial("tcp", url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := proxyConn.Write(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
if _, err := proxyConn.Read(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proxyConn, nil
|
||||
}
|
||||
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
|
|
@ -7,10 +7,6 @@ http_proxy_port=80
|
|||
https_proxy_port=443
|
||||
https_just_proxy=true
|
||||
http_proxy_ip=0.0.0.0
|
||||
#certFile absolute path
|
||||
#pem_path=conf/server.pem
|
||||
#KeyFile absolute path
|
||||
#key_path=conf/server.key
|
||||
|
||||
##bridge
|
||||
bridge_type=tcp
|
||||
|
|
|
@ -130,7 +130,7 @@ func dealCommon(s string) *CommonConfig {
|
|||
c.Cnf.Compress = common.GetBoolByStr(item[1])
|
||||
case "crypt":
|
||||
c.Cnf.Crypt = common.GetBoolByStr(item[1])
|
||||
case "proxy_socks5_url":
|
||||
case "proxy_url":
|
||||
c.ProxyUrl = item[1]
|
||||
case "rate_limit":
|
||||
c.Client.RateLimit = common.GetIntNoErrByStr(item[1])
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
type Conn struct {
|
||||
Conn net.Conn
|
||||
Rb []byte
|
||||
}
|
||||
|
||||
//new conn
|
||||
|
@ -83,6 +84,26 @@ func (s *Conn) GetShortContent(l int) (b []byte, err error) {
|
|||
return buf, binary.Read(s, binary.LittleEndian, &buf)
|
||||
}
|
||||
|
||||
func (s *Conn) LocalAddr() net.Addr {
|
||||
return s.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *Conn) RemoteAddr() net.Addr {
|
||||
return s.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (s *Conn) SetDeadline(t time.Time) error {
|
||||
return s.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return s.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Conn) SetReadDeadline(t time.Time) error {
|
||||
return s.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
//读取指定长度内容
|
||||
func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
|
||||
if cLen > len(buf) {
|
||||
|
@ -130,7 +151,7 @@ func (s *Conn) SetAlive(tp string) {
|
|||
}
|
||||
|
||||
//set read deadline
|
||||
func (s *Conn) SetReadDeadline(t time.Duration, tp string) {
|
||||
func (s *Conn) SetReadDeadlineByType(t time.Duration, tp string) {
|
||||
switch s.Conn.(type) {
|
||||
case *kcp.UDPSession:
|
||||
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
|
||||
|
@ -340,7 +361,15 @@ func (s *Conn) Write(b []byte) (int, error) {
|
|||
}
|
||||
|
||||
//read
|
||||
func (s *Conn) Read(b []byte) (int, error) {
|
||||
func (s *Conn) Read(b []byte) (n int, err error) {
|
||||
if s.Rb != nil {
|
||||
if len(s.Rb) > 0 {
|
||||
n = copy(b, s.Rb)
|
||||
s.Rb = s.Rb[n:]
|
||||
return
|
||||
}
|
||||
s.Rb = nil
|
||||
}
|
||||
return s.Conn.Read(b)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CurveID uint16
|
||||
type SignatureScheme uint16
|
||||
|
||||
const (
|
||||
statusTypeOCSP uint8 = 1
|
||||
extensionServerName uint16 = 0
|
||||
extensionStatusRequest uint16 = 5
|
||||
extensionSupportedCurves uint16 = 10
|
||||
extensionSupportedPoints uint16 = 11
|
||||
extensionSignatureAlgorithms uint16 = 13
|
||||
extensionALPN uint16 = 16
|
||||
extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
|
||||
extensionSessionTicket uint16 = 35
|
||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
||||
extensionRenegotiationInfo uint16 = 0xff01
|
||||
scsvRenegotiation uint16 = 0x00ff
|
||||
)
|
||||
|
||||
type ClientHelloMsg struct {
|
||||
raw []byte
|
||||
vers uint16
|
||||
random []byte
|
||||
sessionId []byte
|
||||
cipherSuites []uint16
|
||||
compressionMethods []uint8
|
||||
nextProtoNeg bool
|
||||
serverName string
|
||||
ocspStapling bool
|
||||
scts bool
|
||||
supportedCurves []CurveID
|
||||
supportedPoints []uint8
|
||||
ticketSupported bool
|
||||
sessionTicket []uint8
|
||||
supportedSignatureAlgorithms []SignatureScheme
|
||||
secureRenegotiation []byte
|
||||
secureRenegotiationSupported bool
|
||||
alpnProtocols []string
|
||||
}
|
||||
|
||||
func (m *ClientHelloMsg) GetServerName() string {
|
||||
return m.serverName
|
||||
}
|
||||
|
||||
func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
|
||||
if len(data) < 42 {
|
||||
return false
|
||||
}
|
||||
m.raw = data
|
||||
m.vers = uint16(data[4])<<8 | uint16(data[5])
|
||||
m.random = data[6:38]
|
||||
sessionIdLen := int(data[38])
|
||||
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
|
||||
return false
|
||||
}
|
||||
m.sessionId = data[39 : 39+sessionIdLen]
|
||||
data = data[39+sessionIdLen:]
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
|
||||
// they are uint16s, the number must be even.
|
||||
cipherSuiteLen := int(data[0])<<8 | int(data[1])
|
||||
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
|
||||
return false
|
||||
}
|
||||
numCipherSuites := cipherSuiteLen / 2
|
||||
m.cipherSuites = make([]uint16, numCipherSuites)
|
||||
for i := 0; i < numCipherSuites; i++ {
|
||||
m.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
|
||||
if m.cipherSuites[i] == scsvRenegotiation {
|
||||
m.secureRenegotiationSupported = true
|
||||
}
|
||||
}
|
||||
data = data[2+cipherSuiteLen:]
|
||||
if len(data) < 1 {
|
||||
return false
|
||||
}
|
||||
compressionMethodsLen := int(data[0])
|
||||
if len(data) < 1+compressionMethodsLen {
|
||||
return false
|
||||
}
|
||||
m.compressionMethods = data[1 : 1+compressionMethodsLen]
|
||||
data = data[1+compressionMethodsLen:]
|
||||
|
||||
m.nextProtoNeg = false
|
||||
m.serverName = ""
|
||||
m.ocspStapling = false
|
||||
m.ticketSupported = false
|
||||
m.sessionTicket = nil
|
||||
m.supportedSignatureAlgorithms = nil
|
||||
m.alpnProtocols = nil
|
||||
m.scts = false
|
||||
|
||||
if len(data) == 0 {
|
||||
// ClientHello is optionally followed by extension data
|
||||
return true
|
||||
}
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
extensionsLength := int(data[0])<<8 | int(data[1])
|
||||
data = data[2:]
|
||||
if extensionsLength != len(data) {
|
||||
return false
|
||||
}
|
||||
|
||||
for len(data) != 0 {
|
||||
if len(data) < 4 {
|
||||
return false
|
||||
}
|
||||
extension := uint16(data[0])<<8 | uint16(data[1])
|
||||
length := int(data[2])<<8 | int(data[3])
|
||||
data = data[4:]
|
||||
if len(data) < length {
|
||||
return false
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case extensionServerName:
|
||||
d := data[:length]
|
||||
if len(d) < 2 {
|
||||
return false
|
||||
}
|
||||
namesLen := int(d[0])<<8 | int(d[1])
|
||||
d = d[2:]
|
||||
if len(d) != namesLen {
|
||||
return false
|
||||
}
|
||||
for len(d) > 0 {
|
||||
if len(d) < 3 {
|
||||
return false
|
||||
}
|
||||
nameType := d[0]
|
||||
nameLen := int(d[1])<<8 | int(d[2])
|
||||
d = d[3:]
|
||||
if len(d) < nameLen {
|
||||
return false
|
||||
}
|
||||
if nameType == 0 {
|
||||
m.serverName = string(d[:nameLen])
|
||||
// An SNI value may not include a
|
||||
// trailing dot. See
|
||||
// https://tools.ietf.org/html/rfc6066#section-3.
|
||||
if strings.HasSuffix(m.serverName, ".") {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
d = d[nameLen:]
|
||||
}
|
||||
case extensionNextProtoNeg:
|
||||
if length > 0 {
|
||||
return false
|
||||
}
|
||||
m.nextProtoNeg = true
|
||||
case extensionStatusRequest:
|
||||
m.ocspStapling = length > 0 && data[0] == statusTypeOCSP
|
||||
case extensionSupportedCurves:
|
||||
// https://tools.ietf.org/html/rfc4492#section-5.5.1
|
||||
if length < 2 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l%2 == 1 || length != l+2 {
|
||||
return false
|
||||
}
|
||||
numCurves := l / 2
|
||||
m.supportedCurves = make([]CurveID, numCurves)
|
||||
d := data[2:]
|
||||
for i := 0; i < numCurves; i++ {
|
||||
m.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])
|
||||
d = d[2:]
|
||||
}
|
||||
case extensionSupportedPoints:
|
||||
// https://tools.ietf.org/html/rfc4492#section-5.5.2
|
||||
if length < 1 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])
|
||||
if length != l+1 {
|
||||
return false
|
||||
}
|
||||
m.supportedPoints = make([]uint8, l)
|
||||
copy(m.supportedPoints, data[1:])
|
||||
case extensionSessionTicket:
|
||||
// https://tools.ietf.org/html/rfc5077#section-3.2
|
||||
m.ticketSupported = true
|
||||
m.sessionTicket = data[:length]
|
||||
case extensionSignatureAlgorithms:
|
||||
// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
|
||||
if length < 2 || length&1 != 0 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l != length-2 {
|
||||
return false
|
||||
}
|
||||
n := l / 2
|
||||
d := data[2:]
|
||||
m.supportedSignatureAlgorithms = make([]SignatureScheme, n)
|
||||
for i := range m.supportedSignatureAlgorithms {
|
||||
m.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1])
|
||||
d = d[2:]
|
||||
}
|
||||
case extensionRenegotiationInfo:
|
||||
if length == 0 {
|
||||
return false
|
||||
}
|
||||
d := data[:length]
|
||||
l := int(d[0])
|
||||
d = d[1:]
|
||||
if l != len(d) {
|
||||
return false
|
||||
}
|
||||
|
||||
m.secureRenegotiation = d
|
||||
m.secureRenegotiationSupported = true
|
||||
case extensionALPN:
|
||||
if length < 2 {
|
||||
return false
|
||||
}
|
||||
l := int(data[0])<<8 | int(data[1])
|
||||
if l != length-2 {
|
||||
return false
|
||||
}
|
||||
d := data[2:length]
|
||||
for len(d) != 0 {
|
||||
stringLen := int(d[0])
|
||||
d = d[1:]
|
||||
if stringLen == 0 || stringLen > len(d) {
|
||||
return false
|
||||
}
|
||||
m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))
|
||||
d = d[stringLen:]
|
||||
}
|
||||
case extensionSCT:
|
||||
m.scts = true
|
||||
if length != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -152,6 +152,8 @@ type Host struct {
|
|||
Location string //url router
|
||||
Remark string //remark
|
||||
Scheme string //http https all
|
||||
CertFilePath string
|
||||
KeyFilePath string
|
||||
NoStore bool
|
||||
IsClose bool
|
||||
Flow *Flow
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package version
|
||||
|
||||
const VERSION = "0.20.2"
|
||||
const VERSION = "0.21.0"
|
||||
|
||||
// Compulsory minimum version, Minimum downward compatibility to this version
|
||||
func GetVersion() string {
|
||||
return "0.19.0"
|
||||
return "0.21.0"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package proxy
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"github.com/cnlh/nps/bridge"
|
||||
"github.com/cnlh/nps/lib/common"
|
||||
|
@ -15,7 +14,6 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
@ -24,10 +22,8 @@ import (
|
|||
|
||||
type httpServer struct {
|
||||
BaseServer
|
||||
httpPort int //http端口
|
||||
httpsPort int //https监听端口
|
||||
pemPath string
|
||||
keyPath string
|
||||
httpPort int
|
||||
httpsPort int
|
||||
httpServer *http.Server
|
||||
httpsServer *http.Server
|
||||
httpsListener net.Listener
|
||||
|
@ -36,8 +32,6 @@ type httpServer struct {
|
|||
func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer {
|
||||
httpPort, _ := beego.AppConfig.Int("http_proxy_port")
|
||||
httpsPort, _ := beego.AppConfig.Int("https_proxy_port")
|
||||
pemPath := beego.AppConfig.String("pem_path")
|
||||
keyPath := beego.AppConfig.String("key_path")
|
||||
return &httpServer{
|
||||
BaseServer: BaseServer{
|
||||
task: c,
|
||||
|
@ -46,57 +40,9 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer {
|
|||
},
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
pemPath: pemPath,
|
||||
keyPath: keyPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *httpServer) processHttps(c net.Conn) {
|
||||
buf := make([]byte, 2048)
|
||||
n, err := c.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var host *file.Host
|
||||
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
|
||||
v := value.(*file.Host)
|
||||
if v.Scheme != "https" && v.Scheme != "all" {
|
||||
return true
|
||||
}
|
||||
if bytes.Index(buf[:n], []byte(v.Host)) >= 0 && (host == nil || len(host.Host) < len(v.Host)) {
|
||||
host = v
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if host == nil {
|
||||
logs.Error("new https connection can't be parsed!", c.RemoteAddr().String())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
var targetAddr string
|
||||
r := new(http.Request)
|
||||
r.RequestURI = "/"
|
||||
r.URL = new(url.URL)
|
||||
r.URL.Scheme = "https"
|
||||
r.Host = host.Host
|
||||
if err := s.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 = s.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())
|
||||
s.DealClient(conn.NewConn(c), host.Client, targetAddr, buf[:n], common.CONN_TCP, nil, host.Flow)
|
||||
}
|
||||
|
||||
func (s *httpServer) Start() error {
|
||||
var err error
|
||||
if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
|
||||
|
@ -125,30 +71,7 @@ func (s *httpServer) Start() error {
|
|||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
|
||||
for {
|
||||
c, err := s.httpsListener.Accept()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
break
|
||||
}
|
||||
go s.processHttps(c)
|
||||
}
|
||||
} else {
|
||||
if !common.FileExists(s.pemPath) {
|
||||
logs.Error("ssl certFile %s exist", s.keyPath)
|
||||
os.Exit(0)
|
||||
}
|
||||
if !common.FileExists(s.keyPath) {
|
||||
logs.Error("ssl keyFile %s exist", s.keyPath)
|
||||
os.Exit(0)
|
||||
}
|
||||
err = s.httpsServer.ServeTLS(s.httpsListener, s.pemPath, s.keyPath)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
logs.Error(NewHttpsServer(s.httpsListener, s.bridge).Start())
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
|
@ -255,7 +178,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
|
|||
goto start
|
||||
}
|
||||
}
|
||||
//根据设定,修改header和host
|
||||
//change the host and header and set proxy setting
|
||||
common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
|
||||
b, err := httputil.DumpRequest(r, false)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/cnlh/nps/bridge"
|
||||
"github.com/cnlh/nps/lib/common"
|
||||
"github.com/cnlh/nps/lib/conn"
|
||||
"github.com/cnlh/nps/lib/crypt"
|
||||
"github.com/cnlh/nps/lib/file"
|
||||
"github.com/cnlh/nps/vender/github.com/astaxie/beego"
|
||||
"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type HttpsServer struct {
|
||||
httpServer
|
||||
listener net.Listener
|
||||
httpsListenerMap sync.Map
|
||||
}
|
||||
|
||||
func NewHttpsServer(l net.Listener, bridge *bridge.Bridge) *HttpsServer {
|
||||
https := &HttpsServer{listener: l}
|
||||
https.bridge = bridge
|
||||
return https
|
||||
}
|
||||
|
||||
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 {
|
||||
conn.Accept(https.listener, func(c net.Conn) {
|
||||
serverName, rb := GetServerNameFromClientHello(c)
|
||||
var l *HttpsListener
|
||||
if v, ok := https.httpsListenerMap.Load(serverName); ok {
|
||||
l = v.(*HttpsListener)
|
||||
} else {
|
||||
r := new(http.Request)
|
||||
r.RequestURI = "/"
|
||||
r.URL = new(url.URL)
|
||||
r.URL.Scheme = "https"
|
||||
if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
|
||||
c.Close()
|
||||
logs.Notice("the url %s can't be parsed!", serverName)
|
||||
return
|
||||
} else {
|
||||
if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
|
||||
c.Close()
|
||||
logs.Error("the key %s cert %s file is not exist", host.KeyFilePath, host.CertFilePath)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (https *HttpsServer) Close() error {
|
||||
return https.listener.Close()
|
||||
}
|
||||
|
||||
func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
|
||||
go func() {
|
||||
logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
|
||||
}()
|
||||
}
|
||||
|
||||
func (https *HttpsServer) handleHttps(c net.Conn) {
|
||||
hostName, rb := GetServerNameFromClientHello(c)
|
||||
var targetAddr string
|
||||
r := new(http.Request)
|
||||
r.RequestURI = "/"
|
||||
r.URL = new(url.URL)
|
||||
r.URL.Scheme = "https"
|
||||
r.Host = 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)
|
||||
}
|
||||
|
||||
type HttpsListener struct {
|
||||
acceptConn chan *conn.Conn
|
||||
parentListener net.Listener
|
||||
}
|
||||
|
||||
func NewHttpsListener(l net.Listener) *HttpsListener {
|
||||
return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
|
||||
}
|
||||
|
||||
func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
|
||||
httpsConn := <-httpsListener.acceptConn
|
||||
if httpsConn == nil {
|
||||
return nil, errors.New("get connection error")
|
||||
}
|
||||
return httpsConn, nil
|
||||
}
|
||||
|
||||
func (httpsListener *HttpsListener) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (httpsListener *HttpsListener) Addr() net.Addr {
|
||||
return httpsListener.parentListener.Addr()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
copy(data, buf[:n])
|
||||
clientHello := new(crypt.ClientHelloMsg)
|
||||
clientHello.Unmarshal(data[5:n])
|
||||
return clientHello.GetServerName(), buf[:n]
|
||||
}
|
|
@ -86,7 +86,7 @@ func NewWebServer(bridge *bridge.Bridge) *WebServer {
|
|||
|
||||
type process func(c *conn.Conn, s *TunnelModeServer) error
|
||||
|
||||
//tcp隧道模式
|
||||
//tcp proxy
|
||||
func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
|
||||
targetAddr, err := s.task.Target.GetRandomTarget()
|
||||
if err != nil {
|
||||
|
@ -97,7 +97,7 @@ func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
|
|||
return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow)
|
||||
}
|
||||
|
||||
//http代理模式
|
||||
//http proxy
|
||||
func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {
|
||||
_, addr, rb, err, r := c.GetHost()
|
||||
if err != nil {
|
||||
|
|
|
@ -43,6 +43,7 @@ func (s *BaseController) Prepare() {
|
|||
} else {
|
||||
s.Data["isAdmin"] = true
|
||||
}
|
||||
s.Data["https_just_proxy"], _ = beego.AppConfig.Bool("https_just_proxy")
|
||||
}
|
||||
|
||||
//加载模板
|
||||
|
|
|
@ -251,10 +251,12 @@ func (s *IndexController) AddHost() {
|
|||
Location: s.GetString("location"),
|
||||
Flow: &file.Flow{},
|
||||
Scheme: s.GetString("scheme"),
|
||||
KeyFilePath: s.GetString("key_file_path"),
|
||||
CertFilePath: s.GetString("cert_file_path"),
|
||||
}
|
||||
var err error
|
||||
if h.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
|
||||
s.AjaxErr("add error")
|
||||
s.AjaxErr("add error the client can not be found")
|
||||
}
|
||||
if err := file.GetDb().NewHost(h); err != nil {
|
||||
s.AjaxErr("add fail" + err.Error())
|
||||
|
@ -300,6 +302,8 @@ func (s *IndexController) EditHost() {
|
|||
h.Remark = s.GetString("remark")
|
||||
h.Location = s.GetString("location")
|
||||
h.Scheme = s.GetString("scheme")
|
||||
h.KeyFilePath = s.GetString("key_file_path")
|
||||
h.CertFilePath = s.GetString("cert_file_path")
|
||||
file.GetDb().JsonDb.StoreHostToJsonFile()
|
||||
}
|
||||
s.AjaxOk("modified success")
|
||||
|
|
|
@ -19,13 +19,29 @@
|
|||
<div class="form-group" id="scheme">
|
||||
<label class="control-label col-sm-2" langtag="info-scheme">协议类型</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="scheme">
|
||||
<select id="scheme_select" class="form-control" name="scheme">
|
||||
<option value="all">all</option>
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{if eq false .https_just_proxy}}
|
||||
<div class="form-group" id="cert_file">
|
||||
<label class="col-sm-2 control-label" langtag="info-https-cert">https cert file路径</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" type="text" name="cert_file_path"
|
||||
placeholder="empty means to be unrestricted">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="key_file">
|
||||
<label class="col-sm-2 control-label" langtag="info-https-key">https key file路径</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" type="text" name="key_file_path"
|
||||
placeholder="empty means to be unrestricted">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" langtag="info-url-router">url路由</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -60,7 +76,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-group" id="hostchange" >
|
||||
<div class="form-group" id="hostchange">
|
||||
<label class="col-sm-2 control-label" langtag="info-host-change">request host修改</label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" value="" type="text" name="hostchange"
|
||||
|
@ -95,5 +111,14 @@
|
|||
}
|
||||
})
|
||||
})
|
||||
$("#scheme_select").on("change", function () {
|
||||
if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
|
||||
$("#cert_file").css("display", "block")
|
||||
$("#key_file").css("display", "block")
|
||||
} else {
|
||||
$("#cert_file").css("display", "none")
|
||||
$("#key_file").css("display", "none")
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -22,13 +22,29 @@
|
|||
<div class="form-group" id="scheme">
|
||||
<label class="control-label col-sm-2" langtag="info-scheme">协议类型</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="scheme">
|
||||
<select id="scheme_select" class="form-control" name="scheme">
|
||||
<option {{if eq "all" .h.Scheme}}selected{{end}} value="all">all</option>
|
||||
<option {{if eq "http" .h.Scheme}}selected{{end}} value="http">http</option>
|
||||
<option {{if eq "https" .h.Scheme}}selected{{end}} value="https">https</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{if eq false .https_just_proxy}}
|
||||
<div class="form-group" id="cert_file">
|
||||
<label class="col-sm-2 control-label" langtag="info-https-cert">https cert file路径</label>
|
||||
<div class="col-sm-10">
|
||||
<input value="{{.h.CertFilePath}}" class="form-control" type="text" name="cert_file_path"
|
||||
placeholder="empty means to be unrestricted">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="key_file">
|
||||
<label class="col-sm-2 control-label" langtag="info-https-key">https key file路径</label>
|
||||
<div class="col-sm-10">
|
||||
<input value="{{.h.KeyFilePath}}" class="form-control" type="text" name="key_file_path"
|
||||
placeholder="empty means to be unrestricted">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" langtag="info-url-router">url路由</label>
|
||||
<div class="col-sm-10">
|
||||
|
@ -99,5 +115,15 @@
|
|||
}
|
||||
})
|
||||
})
|
||||
$("#scheme_select").on("change", function () {
|
||||
if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
|
||||
$("#cert_file").css("display", "block")
|
||||
$("#key_file").css("display", "block")
|
||||
} else {
|
||||
$("#cert_file").css("display", "none")
|
||||
$("#key_file").css("display", "none")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
|
@ -68,6 +68,8 @@
|
|||
+ '<b langtag="info-compress">压缩</b>:' + row.Client.Cnf.Compress + `       ` + "<br/><br>"
|
||||
+ '<b langtag="info-web-auth-username">basic权限认证用户名</b>:' + row.Client.Cnf.U + `       `
|
||||
+ '<b langtag="info-web-auth-password">basic权限认证密码</b>:' + row.Client.Cnf.P + `       ` + "<br/><br>"
|
||||
+ '<b langtag="info-https-cert">cert file路径</b>:' + row.CertFilePath + `       `
|
||||
+ '<b langtag="info-https-key">key file路径</b>:' + row.KeyFilePath + `       ` + "<br/><br>"
|
||||
+ '<b langtag="info-header-change">request header修改</b>:' + row.HeaderChange + `       ` + "<br/><br>"
|
||||
+ '<b langtag="info-host-change">request host 修改</b>:' + row.HostChange + `       `
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue