Skip to content

Commit

Permalink
Implement websocket (ws:// and wss://) links (#1152)
Browse files Browse the repository at this point in the history
ws:// can be listened and dialed
wss:// is a convenience link for ws:// that supports dialing to ws://
peer.

---------

Signed-off-by: Vasyl Gello <[email protected]>
Co-authored-by: Neil Alexander <[email protected]>
  • Loading branch information
basilgello and neilalexander authored Jul 23, 2024
1 parent da7ebde commit 5ea16e6
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
golang.org/x/text v0.16.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/windows v0.5.3
nhooyr.io/websocket v1.8.11
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
12 changes: 12 additions & 0 deletions src/core/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type links struct {
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
quic *linkQUIC // QUIC interface support
ws *linkWS // WS interface support
wss *linkWSS // WSS interface support
// _links can only be modified safely from within the links actor
_links map[linkInfo]*link // *link is nil if connection in progress
}
Expand Down Expand Up @@ -97,6 +99,8 @@ func (l *links) init(c *Core) error {
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l.quic = l.newLinkQUIC()
l.ws = l.newLinkWS()
l.wss = l.newLinkWSS()
l._links = make(map[linkInfo]*link)

var listeners []ListenAddress
Expand Down Expand Up @@ -417,6 +421,10 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
protocol = l.unix
case "quic":
protocol = l.quic
case "ws":
protocol = l.ws
case "wss":
protocol = l.wss
default:
cancel()
return nil, ErrLinkUnrecognisedSchema
Expand Down Expand Up @@ -545,6 +553,10 @@ func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options
dialer = l.unix
case "quic":
dialer = l.quic
case "ws":
dialer = l.ws
case "wss":
dialer = l.wss
default:
return nil, ErrLinkUnrecognisedSchema
}
Expand Down
123 changes: 123 additions & 0 deletions src/core/link_ws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package core

import (
"context"
"net"
"net/http"
"net/url"
"time"

"github.com/Arceliar/phony"
"nhooyr.io/websocket"
)

type linkWS struct {
phony.Inbox
*links
}

type linkWSConn struct {
net.Conn
}

type linkWSListener struct {
ch chan *linkWSConn
ctx context.Context
httpServer *http.Server
listener net.Listener
}

type wsServer struct {
ch chan *linkWSConn
ctx context.Context
}

func (l *linkWSListener) Accept() (net.Conn, error) {
qs := <-l.ch
if qs == nil {
return nil, context.Canceled
}
return qs, nil
}

func (l *linkWSListener) Addr() net.Addr {
return l.listener.Addr()
}

func (l *linkWSListener) Close() error {
if err := l.httpServer.Shutdown(l.ctx); err != nil {
return err
}
return l.listener.Close()
}

func (s *wsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/health" || r.URL.Path == "/healthz" {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
return
}

c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: []string{"ygg-ws"},
})
if err != nil {
return
}

if c.Subprotocol() != "ygg-ws" {
c.Close(websocket.StatusPolicyViolation, "client must speak the ygg-ws subprotocol")
return
}

s.ch <- &linkWSConn{
Conn: websocket.NetConn(s.ctx, c, websocket.MessageBinary),
}
}

func (l *links) newLinkWS() *linkWS {
lt := &linkWS{
links: l,
}
return lt
}

func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{
Subprotocols: []string{"ygg-ws"},
})
if err != nil {
return nil, err
}
return &linkWSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
}

func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
nl, err := net.Listen("tcp", url.Host)
if err != nil {
return nil, err
}

ch := make(chan *linkWSConn)

httpServer := &http.Server{
Handler: &wsServer{
ch: ch,
ctx: ctx,
},
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
}

lwl := &linkWSListener{
ch: ch,
ctx: ctx,
httpServer: httpServer,
listener: nl,
}
go lwl.httpServer.Serve(nl) // nolint:errcheck
return lwl, nil
}
43 changes: 43 additions & 0 deletions src/core/link_wss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package core

import (
"context"
"fmt"
"net"
"net/url"

"github.com/Arceliar/phony"
"nhooyr.io/websocket"
)

type linkWSS struct {
phony.Inbox
*links
}

type linkWSSConn struct {
net.Conn
}

func (l *links) newLinkWSS() *linkWSS {
lwss := &linkWSS{
links: l,
}
return lwss
}

func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{
Subprotocols: []string{"ygg-ws"},
})
if err != nil {
return nil, err
}
return &linkWSSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
}

func (l *linkWSS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
return nil, fmt.Errorf("WSS listener not supported, use WS listener behind reverse proxy instead")
}

0 comments on commit 5ea16e6

Please sign in to comment.