ceremonyclient/go-libp2p/p2p/transport/websocket/addrs.go
2024-10-12 11:55:17 -07:00

177 lines
3.8 KiB
Go

package websocket
import (
"fmt"
"net"
"net/url"
"strconv"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
// Addr is an implementation of net.Addr for WebSocket.
type Addr struct {
*url.URL
}
var _ net.Addr = (*Addr)(nil)
// Network returns the network type for a WebSocket, "websocket".
func (addr *Addr) Network() string {
return "websocket"
}
// NewAddr creates an Addr with `ws` scheme (insecure).
//
// Deprecated. Use NewAddrWithScheme.
func NewAddr(host string) *Addr {
// Older versions of the transport only supported insecure connections (i.e.
// WS instead of WSS). Assume that is the case here.
return NewAddrWithScheme(host, false)
}
// NewAddrWithScheme creates a new Addr using the given host string. isSecure
// should be true for WSS connections and false for WS.
func NewAddrWithScheme(host string, isSecure bool) *Addr {
scheme := "ws"
if isSecure {
scheme = "wss"
}
return &Addr{
URL: &url.URL{
Scheme: scheme,
Host: host,
},
}
}
func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) {
url, err := parseMultiaddr(maddr)
if err != nil {
return nil, err
}
return &Addr{URL: url}, nil
}
func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) {
wsa, ok := a.(*Addr)
if !ok {
return nil, fmt.Errorf("not a websocket address")
}
var (
tcpma ma.Multiaddr
err error
port int
host = wsa.Hostname()
)
// Get the port
if portStr := wsa.Port(); portStr != "" {
port, err = strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("failed to parse port '%q': %s", portStr, err)
}
} else {
return nil, fmt.Errorf("invalid port in url: '%q'", wsa.URL)
}
// NOTE: Ignoring IPv6 zones...
// Detect if host is IP address or DNS
if ip := net.ParseIP(host); ip != nil {
// Assume IP address
tcpma, err = manet.FromNetAddr(&net.TCPAddr{
IP: ip,
Port: port,
})
if err != nil {
return nil, err
}
} else {
// Assume DNS name
tcpma, err = ma.NewMultiaddr(fmt.Sprintf("/dns/%s/tcp/%d", host, port))
if err != nil {
return nil, err
}
}
wsma, err := ma.NewMultiaddr("/" + wsa.Scheme)
if err != nil {
return nil, err
}
return tcpma.Encapsulate(wsma), nil
}
func parseMultiaddr(maddr ma.Multiaddr) (*url.URL, error) {
parsed, err := parseWebsocketMultiaddr(maddr)
if err != nil {
return nil, err
}
scheme := "ws"
if parsed.isWSS {
scheme = "wss"
}
network, host, err := manet.DialArgs(parsed.restMultiaddr)
if err != nil {
return nil, err
}
switch network {
case "tcp", "tcp4", "tcp6":
default:
return nil, fmt.Errorf("unsupported websocket network %s", network)
}
return &url.URL{
Scheme: scheme,
Host: host,
}, nil
}
type parsedWebsocketMultiaddr struct {
isWSS bool
// sni is the SNI value for the TLS handshake, and for setting HTTP Host header
sni *ma.Component
// the rest of the multiaddr before the /tls/sni/example.com/ws or /ws or /wss
restMultiaddr ma.Multiaddr
}
func parseWebsocketMultiaddr(a ma.Multiaddr) (parsedWebsocketMultiaddr, error) {
out := parsedWebsocketMultiaddr{}
// First check if we have a WSS component. If so we'll canonicalize it into a /tls/ws
withoutWss := a.Decapsulate(wssComponent)
if !withoutWss.Equal(a) {
a = withoutWss.Encapsulate(tlsWsComponent)
}
// Remove the ws component
withoutWs := a.Decapsulate(wsComponent)
if withoutWs.Equal(a) {
return out, fmt.Errorf("not a websocket multiaddr")
}
rest := withoutWs
// If this is not a wss then withoutWs is the rest of the multiaddr
out.restMultiaddr = withoutWs
for {
var err error
var head *ma.Component
rest, head, err = ma.SplitLast(rest)
if head == nil || rest == nil || err != nil {
break
}
if head.Protocol().Code == ma.P_SNI {
out.sni = head
} else if head.Protocol().Code == ma.P_TLS {
out.isWSS = true
out.restMultiaddr = rest
break
}
}
return out, nil
}