// Package websocket implements a websocket based transport for go-libp2p.
package websocket import ( ma mafmt manet ws ) // WsFmt is multiaddr formatter for WsProtocol 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 // WithTLSClientConfig sets a TLS client configuration on the WebSocket Dialer. Only // relevant for non-browser usages. // // Some useful use cases include setting InsecureSkipVerify to `true`, or // setting user-defined trusted CA certificates. func ( *tls.Config) Option { return func( *WebsocketTransport) error { .tlsClientConf = return nil } } // WithTLSConfig sets a TLS configuration for the WebSocket listener. func ( *tls.Config) Option { return func( *WebsocketTransport) error { .tlsConf = return nil } } var defaultHandshakeTimeout = 15 * time.Second // WithHandshakeTimeout sets a timeout for the websocket upgrade. func ( time.Duration) Option { return func( *WebsocketTransport) error { .handshakeTimeout = return nil } } // WebsocketTransport is the actual go-libp2p transport 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 ( transport.Upgrader, network.ResourceManager, *tcpreuse.ConnMgr, ...Option) (*WebsocketTransport, error) { if == nil { = &network.NullResourceManager{} } := &WebsocketTransport{ upgrader: , rcmgr: , tlsClientConf: &tls.Config{}, sharedTcp: , handshakeTimeout: defaultHandshakeTimeout, } for , := range { if := (); != nil { return nil, } } return , nil } func ( *WebsocketTransport) ( ma.Multiaddr) bool { return dialMatcher.Matches() } func ( *WebsocketTransport) () []int { return []int{ma.P_WS, ma.P_WSS} } func ( *WebsocketTransport) () bool { return false } func ( *WebsocketTransport) ( context.Context, ma.Multiaddr) ([]ma.Multiaddr, error) { , := parseWebsocketMultiaddr() if != nil { return nil, } if !.isWSS { // No /tls/ws component, this isn't a secure websocket multiaddr. We can just return it here return []ma.Multiaddr{}, nil } if .sni == nil { var error // We don't have an sni component, we'll use dns : for , := range .restMultiaddr { switch .Protocol().Code { case ma.P_DNS, ma.P_DNS4, ma.P_DNS6: // err shouldn't happen since this means we couldn't parse a dns hostname for an sni value. .sni, = ma.NewComponent("sni", .Value()) break } } if != nil { return nil, } } if .sni == nil { // we didn't find anything to set the sni with. So we just return the given multiaddr return []ma.Multiaddr{}, nil } return []ma.Multiaddr{.toMultiaddr()}, nil } // Dial will dial the given multiaddr and expect the given peer. If an // HTTPS_PROXY env is set, it will use that for the dial out. func ( *WebsocketTransport) ( context.Context, ma.Multiaddr, peer.ID) (transport.CapableConn, error) { , := .rcmgr.OpenConnection(network.DirOutbound, true, ) if != nil { return nil, } , := .dialWithScope(, , , ) if != nil { .Done() return nil, } return , nil } func ( *WebsocketTransport) ( context.Context, ma.Multiaddr, peer.ID, network.ConnManagementScope) (transport.CapableConn, error) { , := .maDial(, , ) if != nil { return nil, } , := .upgrader.Upgrade(, , , network.DirOutbound, , ) if != nil { return nil, } return &capableConn{CapableConn: }, nil } func ( *WebsocketTransport) ( context.Context, ma.Multiaddr, network.ConnManagementScope) (manet.Conn, error) { , := parseMultiaddr() if != nil { return nil, } := .Scheme == "wss" := ws.Dialer{ HandshakeTimeout: .handshakeTimeout, // Inherit the default proxy behavior Proxy: ws.DefaultDialer.Proxy, } if { := "" , = .ValueForProtocol(ma.P_SNI) if != nil { = "" } if != "" { := .tlsClientConf.Clone() .ServerName = .TLSClientConfig = := .Host // We set the `.Host` to the sni field so that the host header gets properly set. .Host = + ":" + .Port() // Setting the NetDial because we already have the resolved IP address, so we can avoid another resolution. .NetDial = func(, string) (net.Conn, error) { var *net.TCPAddr var error if == .Host { , = net.ResolveTCPAddr(, ) // Use our already resolved IP address } else { , = net.ResolveTCPAddr(, ) } if != nil { return nil, } return net.DialTCP("tcp", nil, ) } } else { .TLSClientConfig = .tlsClientConf } } , , := .DialContext(, .String(), nil) if != nil { return nil, } , := manet.WrapNetConn(newConn(, , )) if != nil { .Close() return nil, } return , nil } func ( *WebsocketTransport) ( ma.Multiaddr) (transport.GatedMaListener, error) { var *tls.Config if .tlsConf != nil { = .tlsConf.Clone() } , := newListener(, , .sharedTcp, .upgrader, .handshakeTimeout) if != nil { return nil, } go .serve() return , nil } func ( *WebsocketTransport) ( ma.Multiaddr) (transport.Listener, error) { , := .gatedMaListen() if != nil { return nil, } return &transportListener{Listener: .upgrader.UpgradeGatedMaListener(, )}, nil } // transportListener wraps a transport.Listener to provide connections with a `ConnState() network.ConnectionState` method. type transportListener struct { transport.Listener } type capableConn struct { transport.CapableConn } func ( *capableConn) () network.ConnectionState { := .CapableConn.ConnState() .Transport = "websocket" return } func ( *transportListener) () (transport.CapableConn, error) { , := .Listener.Accept() if != nil { return nil, } return &capableConn{CapableConn: }, nil }