mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-12-23 07:35:39 +00:00
Implement websocket (ws:// and wss://) links (#1152)
ws:// can be listened and dialed wss:// is a convenience link for ws:// that supports dialing to ws:// peer. --------- Signed-off-by: Vasyl Gello <vasek.gello@gmail.com> Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
This commit is contained in:
parent
da7ebde828
commit
5ea16e63a1
1
go.mod
1
go.mod
@ -19,6 +19,7 @@ require (
|
|||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.16.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
|
nhooyr.io/websocket v1.8.11
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
2
go.sum
2
go.sum
@ -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=
|
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 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
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=
|
||||||
|
@ -37,6 +37,8 @@ type links struct {
|
|||||||
unix *linkUNIX // UNIX interface support
|
unix *linkUNIX // UNIX interface support
|
||||||
socks *linkSOCKS // SOCKS interface support
|
socks *linkSOCKS // SOCKS interface support
|
||||||
quic *linkQUIC // QUIC 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 can only be modified safely from within the links actor
|
||||||
_links map[linkInfo]*link // *link is nil if connection in progress
|
_links map[linkInfo]*link // *link is nil if connection in progress
|
||||||
}
|
}
|
||||||
@ -97,6 +99,8 @@ func (l *links) init(c *Core) error {
|
|||||||
l.unix = l.newLinkUNIX()
|
l.unix = l.newLinkUNIX()
|
||||||
l.socks = l.newLinkSOCKS()
|
l.socks = l.newLinkSOCKS()
|
||||||
l.quic = l.newLinkQUIC()
|
l.quic = l.newLinkQUIC()
|
||||||
|
l.ws = l.newLinkWS()
|
||||||
|
l.wss = l.newLinkWSS()
|
||||||
l._links = make(map[linkInfo]*link)
|
l._links = make(map[linkInfo]*link)
|
||||||
|
|
||||||
var listeners []ListenAddress
|
var listeners []ListenAddress
|
||||||
@ -417,6 +421,10 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
|||||||
protocol = l.unix
|
protocol = l.unix
|
||||||
case "quic":
|
case "quic":
|
||||||
protocol = l.quic
|
protocol = l.quic
|
||||||
|
case "ws":
|
||||||
|
protocol = l.ws
|
||||||
|
case "wss":
|
||||||
|
protocol = l.wss
|
||||||
default:
|
default:
|
||||||
cancel()
|
cancel()
|
||||||
return nil, ErrLinkUnrecognisedSchema
|
return nil, ErrLinkUnrecognisedSchema
|
||||||
@ -545,6 +553,10 @@ func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options
|
|||||||
dialer = l.unix
|
dialer = l.unix
|
||||||
case "quic":
|
case "quic":
|
||||||
dialer = l.quic
|
dialer = l.quic
|
||||||
|
case "ws":
|
||||||
|
dialer = l.ws
|
||||||
|
case "wss":
|
||||||
|
dialer = l.wss
|
||||||
default:
|
default:
|
||||||
return nil, ErrLinkUnrecognisedSchema
|
return nil, ErrLinkUnrecognisedSchema
|
||||||
}
|
}
|
||||||
|
123
src/core/link_ws.go
Normal file
123
src/core/link_ws.go
Normal file
@ -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
src/core/link_wss.go
Normal file
43
src/core/link_wss.go
Normal file
@ -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")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user