add obfuscation support for SNI
This commit is contained in:
parent
6a65b32b71
commit
65c0ce36bc
@ -193,8 +193,10 @@ func initChain() (*gost.Chain, error) {
|
|||||||
connector = gost.SSHDirectForwardConnector()
|
connector = gost.SSHDirectForwardConnector()
|
||||||
case "remote":
|
case "remote":
|
||||||
connector = gost.SSHRemoteForwardConnector()
|
connector = gost.SSHRemoteForwardConnector()
|
||||||
case "forward", "sni": // sni is an alias of forward
|
case "forward":
|
||||||
connector = gost.ForwardConnector()
|
connector = gost.ForwardConnector()
|
||||||
|
case "sni":
|
||||||
|
connector = gost.SNIConnector(node.Values.Get("host"))
|
||||||
case "http":
|
case "http":
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
|
@ -65,6 +65,9 @@ type tcpDirectForwardHandler struct {
|
|||||||
// TCPDirectForwardHandler creates a server Handler for TCP port forwarding server.
|
// TCPDirectForwardHandler creates a server Handler for TCP port forwarding server.
|
||||||
// The raddr is the remote address that the server will forward to.
|
// The raddr is the remote address that the server will forward to.
|
||||||
func TCPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
func TCPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
||||||
|
if raddr == "" {
|
||||||
|
raddr = "0.0.0.0:0"
|
||||||
|
}
|
||||||
h := &tcpDirectForwardHandler{
|
h := &tcpDirectForwardHandler{
|
||||||
raddr: raddr,
|
raddr: raddr,
|
||||||
options: &HandlerOptions{},
|
options: &HandlerOptions{},
|
||||||
|
28
http.go
28
http.go
@ -134,6 +134,16 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try to get the actual host.
|
||||||
|
if req.Host != "" {
|
||||||
|
if index := strings.IndexByte(req.Host, '.'); index > 0 {
|
||||||
|
// try to decode the prefix
|
||||||
|
if name, err := decodeServerName(req.Host[:index]); err == nil {
|
||||||
|
req.Host = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// forward http request
|
// forward http request
|
||||||
lastNode := h.options.Chain.LastNode()
|
lastNode := h.options.Chain.LastNode()
|
||||||
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
||||||
@ -142,17 +152,18 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
host := req.Host
|
host := req.Host
|
||||||
if !strings.Contains(req.Host, ":") {
|
if !strings.Contains(host, ":") {
|
||||||
host += ":80"
|
host += ":80"
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := h.options.Chain.Dial(host)
|
cc, err := h.options.Chain.Dial(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), host, err)
|
||||||
|
|
||||||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
||||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), host, string(b))
|
||||||
}
|
}
|
||||||
conn.Write(b)
|
conn.Write(b)
|
||||||
return
|
return
|
||||||
@ -163,21 +174,21 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
|||||||
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
||||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), host, string(b))
|
||||||
}
|
}
|
||||||
conn.Write(b)
|
conn.Write(b)
|
||||||
} else {
|
} else {
|
||||||
req.Header.Del("Proxy-Connection")
|
req.Header.Del("Proxy-Connection")
|
||||||
|
|
||||||
if err = req.Write(cc); err != nil {
|
if err = req.Write(cc); err != nil {
|
||||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), host, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Logf("[http] %s <-> %s", cc.LocalAddr(), req.Host)
|
log.Logf("[http] %s <-> %s", cc.LocalAddr(), host)
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[http] %s >-< %s", cc.LocalAddr(), req.Host)
|
log.Logf("[http] %s >-< %s", cc.LocalAddr(), host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) {
|
func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) {
|
||||||
@ -210,6 +221,9 @@ func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
|
cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
|
||||||
|
if !req.URL.IsAbs() {
|
||||||
|
req.URL.Scheme = "http" // make sure that the URL is absolute
|
||||||
|
}
|
||||||
if err = req.WriteProxy(cc); err != nil {
|
if err = req.WriteProxy(cc); err != nil {
|
||||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||||
return
|
return
|
||||||
|
175
obfs.go
175
obfs.go
@ -12,6 +12,7 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
|
|
||||||
@ -65,7 +66,8 @@ func (l *obfsHTTPListener) Accept() (net.Conn, error) {
|
|||||||
|
|
||||||
type obfsHTTPConn struct {
|
type obfsHTTPConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
r *http.Request
|
request *http.Request
|
||||||
|
response *http.Response
|
||||||
rbuf []byte
|
rbuf []byte
|
||||||
wbuf []byte
|
wbuf []byte
|
||||||
isServer bool
|
isServer bool
|
||||||
@ -82,88 +84,103 @@ func (c *obfsHTTPConn) Handshake() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.isServer {
|
if c.isServer {
|
||||||
br := bufio.NewReader(c.Conn)
|
err = c.serverHandshake()
|
||||||
c.r, err = http.ReadRequest(br)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
dump, _ := httputil.DumpRequest(c.r, false)
|
|
||||||
log.Logf("[ohttp] %s -> %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if br.Buffered() > 0 {
|
|
||||||
c.rbuf, err = br.Peek(br.Buffered())
|
|
||||||
} else {
|
|
||||||
c.rbuf, err = ioutil.ReadAll(c.r.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Logf("[ohttp] %s -> %s : %v", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if c.r.Header.Get("Connection") == "Upgrade" &&
|
|
||||||
c.r.Header.Get("Upgrade") == "websocket" {
|
|
||||||
b.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
|
||||||
b.WriteString("Server: nginx/1.10.0\r\n")
|
|
||||||
b.WriteString("Connection: Upgrade\r\n")
|
|
||||||
b.WriteString("Upgrade: websocket\r\n")
|
|
||||||
b.WriteString(fmt.Sprintf("Sec-WebSocket-Accept: %s\r\n", computeAcceptKey(c.r.Header.Get("Sec-WebSocket-Key"))))
|
|
||||||
b.WriteString("\r\n")
|
|
||||||
} else {
|
|
||||||
b.WriteString("HTTP/1.1 200 OK\r\n")
|
|
||||||
b.WriteString("Server: nginx/1.10.0\r\n")
|
|
||||||
b.WriteString("Content-Type: application/octet-stream\r\n")
|
|
||||||
b.WriteString("Connection: keep-alive\r\n")
|
|
||||||
b.WriteString("Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform\r\n")
|
|
||||||
b.WriteString("Pragma: no-cache\r\n")
|
|
||||||
b.WriteString("\r\n")
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
log.Logf("[ohttp] %s <- %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), b.String())
|
|
||||||
}
|
|
||||||
if _, err = b.WriteTo(c.Conn); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
r := c.r
|
err = c.clientHandshake()
|
||||||
if r == nil {
|
}
|
||||||
r = &http.Request{
|
if err != nil {
|
||||||
Method: http.MethodPost,
|
return
|
||||||
ProtoMajor: 1,
|
}
|
||||||
ProtoMinor: 1,
|
|
||||||
URL: &url.URL{Scheme: "http", Host: "www.baidu.com"},
|
|
||||||
Header: make(http.Header),
|
|
||||||
}
|
|
||||||
r.Header.Set("Connection", "keep-alive")
|
|
||||||
r.Header.Set("User-Agent", DefaultUserAgent)
|
|
||||||
if len(c.wbuf) > 0 {
|
|
||||||
r.Body = ioutil.NopCloser(bytes.NewReader(c.wbuf))
|
|
||||||
r.ContentLength = int64(len(c.wbuf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = r.Write(c.Conn); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
dump, _ := httputil.DumpRequest(r, false)
|
|
||||||
log.Logf("[ohttp] %s -> %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump))
|
|
||||||
}
|
|
||||||
var resp *http.Response
|
|
||||||
resp, err = http.ReadResponse(bufio.NewReader(c.Conn), r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if Debug {
|
c.handshaked = true
|
||||||
dump, _ := httputil.DumpResponse(resp, false)
|
return nil
|
||||||
log.Logf("[ohttp] %s <- %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump))
|
}
|
||||||
|
|
||||||
|
func (c *obfsHTTPConn) serverHandshake() (err error) {
|
||||||
|
br := bufio.NewReader(c.Conn)
|
||||||
|
c.request, err = http.ReadRequest(br)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpRequest(c.request, false)
|
||||||
|
log.Logf("[ohttp] %s -> %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
if br.Buffered() > 0 {
|
||||||
|
c.rbuf, err = br.Peek(br.Buffered())
|
||||||
|
} else {
|
||||||
|
c.rbuf, err = ioutil.ReadAll(c.request.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[ohttp] %s -> %s : %v", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if c.request.Header.Get("Upgrade") == "websocket" {
|
||||||
|
b.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||||
|
b.WriteString("Server: nginx/1.10.0\r\n")
|
||||||
|
b.WriteString("Date: " + time.Now().Format(time.RFC1123) + "\r\n")
|
||||||
|
b.WriteString("Connection: Upgrade\r\n")
|
||||||
|
b.WriteString("Upgrade: websocket\r\n")
|
||||||
|
b.WriteString(fmt.Sprintf("Sec-WebSocket-Accept: %s\r\n", computeAcceptKey(c.request.Header.Get("Sec-WebSocket-Key"))))
|
||||||
|
b.WriteString("\r\n")
|
||||||
|
} else {
|
||||||
|
b.WriteString("HTTP/1.1 200 OK\r\n")
|
||||||
|
b.WriteString("Server: nginx/1.10.0\r\n")
|
||||||
|
b.WriteString("Date: " + time.Now().Format(time.RFC1123) + "\r\n")
|
||||||
|
b.WriteString("Content-Type: application/octet-stream\r\n")
|
||||||
|
b.WriteString("Connection: keep-alive\r\n")
|
||||||
|
b.WriteString("Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform\r\n")
|
||||||
|
b.WriteString("Pragma: no-cache\r\n")
|
||||||
|
b.WriteString("\r\n")
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[ohttp] %s <- %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), b.String())
|
||||||
|
}
|
||||||
|
_, err = b.WriteTo(c.Conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsHTTPConn) clientHandshake() (err error) {
|
||||||
|
r := c.request
|
||||||
|
if r == nil {
|
||||||
|
r = &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
URL: &url.URL{Scheme: "http", Host: "www.baidu.com"},
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
r.Header.Set("Connection", "keep-alive")
|
||||||
|
r.Header.Set("Upgrade", "websocket")
|
||||||
|
r.Header.Set("User-Agent", DefaultUserAgent)
|
||||||
|
if len(c.wbuf) > 0 {
|
||||||
|
log.Log("write buf", len(c.wbuf))
|
||||||
|
r.Body = ioutil.NopCloser(bytes.NewReader(c.wbuf))
|
||||||
|
r.ContentLength = int64(len(c.wbuf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.handshaked = true
|
if err = r.Write(c.Conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpRequest(r, false)
|
||||||
|
log.Logf("[ohttp] %s -> %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump))
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
resp, err = http.ReadResponse(bufio.NewReader(c.Conn), r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Logf("[ohttp] %s <- %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
164
sni.go
164
sni.go
@ -6,12 +6,32 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
dissector "github.com/ginuerzh/tls-dissector"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type sniConnector struct {
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SNIConnector creates a Connector for SNI proxy client.
|
||||||
|
func SNIConnector(host string) Connector {
|
||||||
|
return &sniConnector{host: host}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sniConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
return &sniClientConn{addr: addr, host: c.host, Conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type sniHandler struct {
|
type sniHandler struct {
|
||||||
options []HandlerOption
|
options []HandlerOption
|
||||||
}
|
}
|
||||||
@ -26,23 +46,26 @@ func SNIHandler(opts ...HandlerOption) Handler {
|
|||||||
|
|
||||||
func (h *sniHandler) Handle(conn net.Conn) {
|
func (h *sniHandler) Handle(conn net.Conn) {
|
||||||
br := bufio.NewReader(conn)
|
br := bufio.NewReader(conn)
|
||||||
isTLS, sni, err := clientHelloServerName(br)
|
|
||||||
|
hdr, err := br.Peek(dissector.RecordHeaderLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log("[sni]", err)
|
log.Log("[sni]", err)
|
||||||
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = &bufferdConn{br: br, Conn: conn}
|
conn = &bufferdConn{br: br, Conn: conn}
|
||||||
// We assume that it is HTTP request
|
|
||||||
if !isTLS {
|
if hdr[0] != dissector.Handshake {
|
||||||
|
// We assume that it is HTTP request
|
||||||
HTTPHandler(h.options...).Handle(conn)
|
HTTPHandler(h.options...).Handle(conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
if sni == "" {
|
b, host, err := readClientHelloRecord(conn, "", false)
|
||||||
log.Log("[sni] The client does not support SNI")
|
if err != nil {
|
||||||
|
log.Log("[sni]", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,20 +74,25 @@ func (h *sniHandler) Handle(conn net.Conn) {
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Can("tcp", sni, options.Whitelist, options.Blacklist) {
|
if !Can("tcp", host, options.Whitelist, options.Blacklist) {
|
||||||
log.Logf("[sni] Unauthorized to tcp connect to %s", sni)
|
log.Logf("[sni] Unauthorized to tcp connect to %s", host)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := options.Chain.Dial(sni + ":443")
|
cc, err := options.Chain.Dial(host + ":443")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[sni] %s -> %s : %s", conn.RemoteAddr(), sni, err)
|
log.Logf("[sni] %s -> %s : %s", conn.RemoteAddr(), host, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
log.Logf("[sni] %s <-> %s", cc.LocalAddr(), sni)
|
|
||||||
|
if _, err := cc.Write(b); err != nil {
|
||||||
|
log.Logf("[sni] %s -> %s : %s", conn.RemoteAddr(), host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Logf("[sni] %s <-> %s", cc.LocalAddr(), host)
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[sni] %s >-< %s", cc.LocalAddr(), sni)
|
log.Logf("[sni] %s >-< %s", cc.LocalAddr(), host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clientHelloServerName returns the SNI server name inside the TLS ClientHello,
|
// clientHelloServerName returns the SNI server name inside the TLS ClientHello,
|
||||||
@ -104,3 +132,115 @@ type sniSniffConn struct {
|
|||||||
|
|
||||||
func (c sniSniffConn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
func (c sniSniffConn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
||||||
func (sniSniffConn) Write(p []byte) (int, error) { return 0, io.EOF }
|
func (sniSniffConn) Write(p []byte) (int, error) { return 0, io.EOF }
|
||||||
|
|
||||||
|
type sniClientConn struct {
|
||||||
|
addr string
|
||||||
|
host string
|
||||||
|
mutex sync.Mutex
|
||||||
|
obfuscated bool
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sniClientConn) Write(p []byte) (int, error) {
|
||||||
|
b, err := c.obfuscate(p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if _, err = c.Conn.Write(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sniClientConn) obfuscate(p []byte) ([]byte, error) {
|
||||||
|
if c.host == "" {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.obfuscated {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if p[0] == dissector.Handshake {
|
||||||
|
b, host, err := readClientHelloRecord(bytes.NewReader(p), c.host, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[sni] obfuscate: %s -> %s", c.addr, host)
|
||||||
|
}
|
||||||
|
c.obfuscated = true
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: HTTP obfuscate
|
||||||
|
c.obfuscated = true
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, string, error) {
|
||||||
|
record, err := dissector.ReadRecord(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
clientHello := &dissector.ClientHelloHandshake{}
|
||||||
|
if err := clientHello.Decode(record.Opaque); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
for _, ext := range clientHello.Extensions {
|
||||||
|
if ext.Type() == dissector.ExtServerName {
|
||||||
|
snExtension := ext.(*dissector.ServerNameExtension)
|
||||||
|
serverName := snExtension.Name
|
||||||
|
if isClient {
|
||||||
|
snExtension.Name = encodeServerName(serverName) + "." + host
|
||||||
|
} else {
|
||||||
|
if index := strings.IndexByte(serverName, '.'); index > 0 {
|
||||||
|
// try to decode the prefix
|
||||||
|
if name, err := decodeServerName(serverName[:index]); err == nil {
|
||||||
|
snExtension.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host = snExtension.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record.Opaque, err = clientHello.Encode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if _, err := record.WriteTo(buf); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeServerName(name string) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
binary.Write(buf, binary.BigEndian, crc32.ChecksumIEEE([]byte(name)))
|
||||||
|
buf.WriteString(base64.StdEncoding.EncodeToString([]byte(name)))
|
||||||
|
return base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeServerName(s string) (string, error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(b) < 4 {
|
||||||
|
return "", errors.New("invalid name")
|
||||||
|
}
|
||||||
|
v, err := base64.StdEncoding.DecodeString(string(b[4:]))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if crc32.ChecksumIEEE(v) != binary.BigEndian.Uint32(b[:4]) {
|
||||||
|
return "", errors.New("invalid name")
|
||||||
|
}
|
||||||
|
return string(v), nil
|
||||||
|
}
|
||||||
|
21
vendor/github.com/ginuerzh/tls-dissector/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ginuerzh/tls-dissector/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 ginuerzh
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
2
vendor/github.com/ginuerzh/tls-dissector/README.md
generated
vendored
Normal file
2
vendor/github.com/ginuerzh/tls-dissector/README.md
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# tls-dissector
|
||||||
|
Dissect TLS handshake messages
|
75
vendor/github.com/ginuerzh/tls-dissector/extension.go
generated
vendored
Normal file
75
vendor/github.com/ginuerzh/tls-dissector/extension.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package dissector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtServerName uint16 = 0x0000
|
||||||
|
)
|
||||||
|
|
||||||
|
type Extension interface {
|
||||||
|
Type() uint16
|
||||||
|
Bytes() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadExtension(r io.Reader) (ext Extension, err error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
if _, err = io.ReadFull(r, b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bb := make([]byte, int(binary.BigEndian.Uint16(b[2:4])))
|
||||||
|
if _, err = io.ReadFull(r, bb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := binary.BigEndian.Uint16(b[:2])
|
||||||
|
switch t {
|
||||||
|
case ExtServerName:
|
||||||
|
ext = &ServerNameExtension{
|
||||||
|
NameType: bb[2],
|
||||||
|
Name: string(bb[5:]),
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ext = &unknownExtension{
|
||||||
|
raw: append(b, bb...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownExtension struct {
|
||||||
|
raw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *unknownExtension) Type() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(ext.raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *unknownExtension) Bytes() []byte {
|
||||||
|
return ext.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerNameExtension struct {
|
||||||
|
NameType uint8
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *ServerNameExtension) Type() uint16 {
|
||||||
|
return ExtServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *ServerNameExtension) Bytes() []byte {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
binary.Write(buf, binary.BigEndian, ExtServerName)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(2+1+2+len(ext.Name)))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(1+2+len(ext.Name)))
|
||||||
|
buf.WriteByte(ext.NameType)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(ext.Name)))
|
||||||
|
buf.WriteString(ext.Name)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
152
vendor/github.com/ginuerzh/tls-dissector/handshake.go
generated
vendored
Normal file
152
vendor/github.com/ginuerzh/tls-dissector/handshake.go
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package dissector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
handshakeHeaderLen = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HelloRequest = 0
|
||||||
|
ClientHello = 1
|
||||||
|
ServerHello = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Random struct {
|
||||||
|
Time uint32
|
||||||
|
Opaque [28]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type CipherSuite uint16
|
||||||
|
|
||||||
|
type CompressionMethod uint8
|
||||||
|
|
||||||
|
type ClientHelloHandshake struct {
|
||||||
|
Version Version
|
||||||
|
Random Random
|
||||||
|
SessionID []byte
|
||||||
|
CipherSuites []CipherSuite
|
||||||
|
CompressionMethods []CompressionMethod
|
||||||
|
Extensions []Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ClientHelloHandshake) Encode() (data []byte, err error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err = h.WriteTo(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = buf.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ClientHelloHandshake) Decode(data []byte) (err error) {
|
||||||
|
_, err = h.ReadFrom(bytes.NewReader(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ClientHelloHandshake) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
b := make([]byte, handshakeHeaderLen)
|
||||||
|
nn, err := io.ReadFull(r, b)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != ClientHello {
|
||||||
|
err = ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
length := int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||||
|
b = make([]byte, length)
|
||||||
|
nn, err = io.ReadFull(r, b)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Version = Version(binary.BigEndian.Uint16(b[:2]))
|
||||||
|
|
||||||
|
pos := 2
|
||||||
|
h.Random.Time = binary.BigEndian.Uint32(b[pos : pos+4])
|
||||||
|
pos += 4
|
||||||
|
copy(h.Random.Opaque[:], b[pos:pos+28])
|
||||||
|
pos += 28
|
||||||
|
|
||||||
|
sessionLen := int(b[pos])
|
||||||
|
pos++
|
||||||
|
h.SessionID = make([]byte, sessionLen)
|
||||||
|
copy(h.SessionID, b[pos:pos+sessionLen])
|
||||||
|
pos += sessionLen
|
||||||
|
|
||||||
|
cipherLen := int(binary.BigEndian.Uint16(b[pos : pos+2]))
|
||||||
|
pos += 2
|
||||||
|
for i := 0; i < cipherLen/2; i++ {
|
||||||
|
h.CipherSuites = append(h.CipherSuites, CipherSuite(binary.BigEndian.Uint16(b[pos:pos+2])))
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
compLen := int(b[pos])
|
||||||
|
pos++
|
||||||
|
for i := 0; i < compLen; i++ {
|
||||||
|
h.CompressionMethods = append(h.CompressionMethods, CompressionMethod(b[pos]))
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
// extLen := int(binary.BigEndian.Uint16(b[pos : pos+2]))
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
br := bytes.NewReader(b[pos:])
|
||||||
|
for br.Len() > 0 {
|
||||||
|
var ext Extension
|
||||||
|
ext, err = ReadExtension(br)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Extensions = append(h.Extensions, ext)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ClientHelloHandshake) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
buf.WriteByte(ClientHello)
|
||||||
|
buf.Write([]byte{0, 0, 0}) // placeholder for payload length
|
||||||
|
binary.Write(buf, binary.BigEndian, h.Version)
|
||||||
|
pos := 6
|
||||||
|
binary.Write(buf, binary.BigEndian, h.Random.Time)
|
||||||
|
buf.Write(h.Random.Opaque[:])
|
||||||
|
pos += 32
|
||||||
|
buf.WriteByte(byte(len(h.SessionID)))
|
||||||
|
buf.Write(h.SessionID)
|
||||||
|
pos += (1 + len(h.SessionID))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(h.CipherSuites)*2))
|
||||||
|
for _, cs := range h.CipherSuites {
|
||||||
|
binary.Write(buf, binary.BigEndian, cs)
|
||||||
|
}
|
||||||
|
pos += (2 + len(h.CipherSuites)*2)
|
||||||
|
buf.WriteByte(byte(len(h.CompressionMethods)))
|
||||||
|
for _, cm := range h.CompressionMethods {
|
||||||
|
buf.WriteByte(byte(cm))
|
||||||
|
}
|
||||||
|
pos += (1 + len(h.CompressionMethods))
|
||||||
|
buf.Write([]byte{0, 0}) // placeholder for extensions length
|
||||||
|
|
||||||
|
extLen := 0
|
||||||
|
for _, ext := range h.Extensions {
|
||||||
|
nn, _ := buf.Write(ext.Bytes())
|
||||||
|
extLen += nn
|
||||||
|
}
|
||||||
|
|
||||||
|
b := buf.Bytes()
|
||||||
|
plen := len(b) - handshakeHeaderLen
|
||||||
|
b[1], b[2], b[3] = byte((plen>>16)&0xFF), byte((plen>>8)&0xFF), byte(plen&0xFF) // payload length
|
||||||
|
b[pos], b[pos+1] = byte((extLen>>8)&0xFF), byte(extLen&0xFF) // extensions length
|
||||||
|
|
||||||
|
nn, err := w.Write(b)
|
||||||
|
n = int64(nn)
|
||||||
|
return
|
||||||
|
}
|
61
vendor/github.com/ginuerzh/tls-dissector/record.go
generated
vendored
Normal file
61
vendor/github.com/ginuerzh/tls-dissector/record.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package dissector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RecordHeaderLen = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Handshake = 0x16
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadType = errors.New("bad type")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version uint16
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Type uint8
|
||||||
|
Version Version
|
||||||
|
Opaque []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRecord(r io.Reader) (*Record, error) {
|
||||||
|
record := &Record{}
|
||||||
|
if _, err := record.ReadFrom(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rec *Record) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
b := make([]byte, RecordHeaderLen)
|
||||||
|
nn, err := io.ReadFull(r, b)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec.Type = b[0]
|
||||||
|
rec.Version = Version(binary.BigEndian.Uint16(b[1:3]))
|
||||||
|
length := int(binary.BigEndian.Uint16(b[3:5]))
|
||||||
|
rec.Opaque = make([]byte, length)
|
||||||
|
nn, err = io.ReadFull(r, rec.Opaque)
|
||||||
|
n += int64(nn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rec *Record) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
buf.WriteByte(rec.Type)
|
||||||
|
binary.Write(buf, binary.BigEndian, rec.Version)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(rec.Opaque)))
|
||||||
|
buf.Write(rec.Opaque)
|
||||||
|
return buf.WriteTo(w)
|
||||||
|
}
|
5827
vendor/github.com/ginuerzh/tls-dissector/rfc5246.txt
generated
vendored
Normal file
5827
vendor/github.com/ginuerzh/tls-dissector/rfc5246.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -98,6 +98,12 @@
|
|||||||
"revision": "8acf34a91f436364fadb82a630ab3c7e938e1d0f",
|
"revision": "8acf34a91f436364fadb82a630ab3c7e938e1d0f",
|
||||||
"revisionTime": "2017-02-05T06:52:49Z"
|
"revisionTime": "2017-02-05T06:52:49Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "qBQox+0NNTwkyw+a4eMiuMjDxjY=",
|
||||||
|
"path": "github.com/ginuerzh/tls-dissector",
|
||||||
|
"revision": "7daf6e2af3aed2de50b3662683f6434297949d2a",
|
||||||
|
"revisionTime": "2017-10-25T09:37:20Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "fBx0fqiyrl26gkGo14J9pJ8zB2Y=",
|
"checksumSHA1": "fBx0fqiyrl26gkGo14J9pJ8zB2Y=",
|
||||||
"path": "github.com/go-log/log",
|
"path": "github.com/go-log/log",
|
||||||
|
Loading…
Reference in New Issue
Block a user