package websocket
import (
"context"
"crypto/tls"
"net"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/transport"
"github.com/libp2p/go-libp2p/p2p/transport/tcpreuse"
ma "github.com/multiformats/go-multiaddr"
mafmt "github.com/multiformats/go-multiaddr-fmt"
manet "github.com/multiformats/go-multiaddr/net"
ws "github.com/gorilla/websocket"
)
var WsFmt = mafmt .And (mafmt .TCP , mafmt .Base (ma .P_WS ))
var dialMatcher = mafmt .And (
mafmt .Or (mafmt .IP , mafmt .DNS ),
mafmt .Base (ma .P_TCP ),
mafmt .Or (
mafmt .Base (ma .P_WS ),
mafmt .And (
mafmt .Or (
mafmt .And (
mafmt .Base (ma .P_TLS ),
mafmt .Base (ma .P_SNI )),
mafmt .Base (ma .P_TLS ),
),
mafmt .Base (ma .P_WS )),
mafmt .Base (ma .P_WSS )))
var (
wssComponent, _ = ma .NewComponent ("wss" , "" )
tlsComponent, _ = ma .NewComponent ("tls" , "" )
wsComponent, _ = ma .NewComponent ("ws" , "" )
tlsWsAddr = ma .Multiaddr {*tlsComponent , *wsComponent }
)
func init() {
manet .RegisterFromNetAddr (ParseWebsocketNetAddr , "websocket" )
manet .RegisterToNetAddr (ConvertWebsocketMultiaddrToNetAddr , "ws" )
manet .RegisterToNetAddr (ConvertWebsocketMultiaddrToNetAddr , "wss" )
}
type Option func (*WebsocketTransport ) error
func WithTLSClientConfig (c *tls .Config ) Option {
return func (t *WebsocketTransport ) error {
t .tlsClientConf = c
return nil
}
}
func WithTLSConfig (conf *tls .Config ) Option {
return func (t *WebsocketTransport ) error {
t .tlsConf = conf
return nil
}
}
var defaultHandshakeTimeout = 15 * time .Second
func WithHandshakeTimeout (timeout time .Duration ) Option {
return func (t *WebsocketTransport ) error {
t .handshakeTimeout = timeout
return nil
}
}
type WebsocketTransport struct {
upgrader transport .Upgrader
rcmgr network .ResourceManager
tlsClientConf *tls .Config
tlsConf *tls .Config
sharedTcp *tcpreuse .ConnMgr
handshakeTimeout time .Duration
}
var _ transport .Transport = (*WebsocketTransport )(nil )
func New (u transport .Upgrader , rcmgr network .ResourceManager , sharedTCP *tcpreuse .ConnMgr , opts ...Option ) (*WebsocketTransport , error ) {
if rcmgr == nil {
rcmgr = &network .NullResourceManager {}
}
t := &WebsocketTransport {
upgrader : u ,
rcmgr : rcmgr ,
tlsClientConf : &tls .Config {},
sharedTcp : sharedTCP ,
handshakeTimeout : defaultHandshakeTimeout ,
}
for _ , opt := range opts {
if err := opt (t ); err != nil {
return nil , err
}
}
return t , nil
}
func (t *WebsocketTransport ) CanDial (a ma .Multiaddr ) bool {
return dialMatcher .Matches (a )
}
func (t *WebsocketTransport ) Protocols () []int {
return []int {ma .P_WS , ma .P_WSS }
}
func (t *WebsocketTransport ) Proxy () bool {
return false
}
func (t *WebsocketTransport ) Resolve (_ context .Context , maddr ma .Multiaddr ) ([]ma .Multiaddr , error ) {
parsed , err := parseWebsocketMultiaddr (maddr )
if err != nil {
return nil , err
}
if !parsed .isWSS {
return []ma .Multiaddr {maddr }, nil
}
if parsed .sni == nil {
var err error
loop :
for _ , c := range parsed .restMultiaddr {
switch c .Protocol ().Code {
case ma .P_DNS , ma .P_DNS4 , ma .P_DNS6 :
parsed .sni , err = ma .NewComponent ("sni" , c .Value ())
break loop
}
}
if err != nil {
return nil , err
}
}
if parsed .sni == nil {
return []ma .Multiaddr {maddr }, nil
}
return []ma .Multiaddr {parsed .toMultiaddr ()}, nil
}
func (t *WebsocketTransport ) Dial (ctx context .Context , raddr ma .Multiaddr , p peer .ID ) (transport .CapableConn , error ) {
connScope , err := t .rcmgr .OpenConnection (network .DirOutbound , true , raddr )
if err != nil {
return nil , err
}
c , err := t .dialWithScope (ctx , raddr , p , connScope )
if err != nil {
connScope .Done ()
return nil , err
}
return c , nil
}
func (t *WebsocketTransport ) dialWithScope (ctx context .Context , raddr ma .Multiaddr , p peer .ID , connScope network .ConnManagementScope ) (transport .CapableConn , error ) {
macon , err := t .maDial (ctx , raddr , connScope )
if err != nil {
return nil , err
}
conn , err := t .upgrader .Upgrade (ctx , t , macon , network .DirOutbound , p , connScope )
if err != nil {
return nil , err
}
return &capableConn {CapableConn : conn }, nil
}
func (t *WebsocketTransport ) maDial (ctx context .Context , raddr ma .Multiaddr , scope network .ConnManagementScope ) (manet .Conn , error ) {
wsurl , err := parseMultiaddr (raddr )
if err != nil {
return nil , err
}
isWss := wsurl .Scheme == "wss"
dialer := ws .Dialer {
HandshakeTimeout : t .handshakeTimeout ,
Proxy : ws .DefaultDialer .Proxy ,
}
if isWss {
sni := ""
sni , err = raddr .ValueForProtocol (ma .P_SNI )
if err != nil {
sni = ""
}
if sni != "" {
copytlsClientConf := t .tlsClientConf .Clone ()
copytlsClientConf .ServerName = sni
dialer .TLSClientConfig = copytlsClientConf
ipPortAddr := wsurl .Host
wsurl .Host = sni + ":" + wsurl .Port ()
dialer .NetDial = func (network , address string ) (net .Conn , error ) {
var tcpAddr *net .TCPAddr
var err error
if address == wsurl .Host {
tcpAddr , err = net .ResolveTCPAddr (network , ipPortAddr )
} else {
tcpAddr , err = net .ResolveTCPAddr (network , address )
}
if err != nil {
return nil , err
}
return net .DialTCP ("tcp" , nil , tcpAddr )
}
} else {
dialer .TLSClientConfig = t .tlsClientConf
}
}
wscon , _ , err := dialer .DialContext (ctx , wsurl .String (), nil )
if err != nil {
return nil , err
}
mnc , err := manet .WrapNetConn (newConn (wscon , isWss , scope ))
if err != nil {
wscon .Close ()
return nil , err
}
return mnc , nil
}
func (t *WebsocketTransport ) gatedMaListen (a ma .Multiaddr ) (transport .GatedMaListener , error ) {
var tlsConf *tls .Config
if t .tlsConf != nil {
tlsConf = t .tlsConf .Clone ()
}
l , err := newListener (a , tlsConf , t .sharedTcp , t .upgrader , t .handshakeTimeout )
if err != nil {
return nil , err
}
go l .serve ()
return l , nil
}
func (t *WebsocketTransport ) Listen (a ma .Multiaddr ) (transport .Listener , error ) {
gmal , err := t .gatedMaListen (a )
if err != nil {
return nil , err
}
return &transportListener {Listener : t .upgrader .UpgradeGatedMaListener (t , gmal )}, nil
}
type transportListener struct {
transport .Listener
}
type capableConn struct {
transport .CapableConn
}
func (c *capableConn ) ConnState () network .ConnectionState {
cs := c .CapableConn .ConnState ()
cs .Transport = "websocket"
return cs
}
func (l *transportListener ) Accept () (transport .CapableConn , error ) {
conn , err := l .Listener .Accept ()
if err != nil {
return nil , err
}
return &capableConn {CapableConn : conn }, nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .