想法来自于好多年前就看到的 sslh,其实原理很简单,就是先从 tcp 读头几个 bytes,就可以判断是什么连接了。
http 报文的头是 GET / HTTP/1.1(其他 method 同理),https 报文的第一个字节是 0x16,ssh 报文是 SSH。以下是代码示例,同时监听 http 和 https 在同一个端口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package mainimport ( "bufio" "crypto/tls" "net" "net/http" ) type bufferedConn struct { net.Conn r *bufio.Reader } func (b bufferedConn) Peek(n int) ([]byte, error) { return b.r.Peek(n) } func (b bufferedConn) Read(p []byte) (int, error) { return b.r.Read(p) } // MuxListener is a net.Listener that accepts both TLS and non-TLS connections type MuxListener struct { net.Listener tlsConfig *tls.Config } func NewMuxListener(inner net.Listener, tlsConfig *tls.Config) net.Listener { l := new(MuxListener) l.Listener = inner l.tlsConfig = tlsConfig return l } func (l *MuxListener) Accept() (net.Conn, error) { c, err := l.Listener.Accept() if err != nil { return nil, err } conn := bufferedConn{Conn: c, r: bufio.NewReaderSize(c, 1)} p, err := conn.Peek(1) if err != nil { return nil, err } if p[0] == 22 { return tls.Server(conn, l.tlsConfig), nil } return conn, nil } func main() { ln, err := net.Listen("tcp", ":443") if err != nil { panic(err) } defer ln.Close() muxListener := NewMuxListener(ln, &tls.Config{}) (&http.Server{Handler: http.HandlerFunc(nil)}).Serve(muxListener) }
|