mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-09 13:04:06 +08:00
impl wsc transport and protocol
This commit is contained in:
parent
ed35dbe6e0
commit
1d4cd315a6
14
adapter/wsc.go
Normal file
14
adapter/wsc.go
Normal file
@ -0,0 +1,14 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type WSCServerTransport interface {
|
||||
}
|
||||
|
||||
type WSCClientTransport interface {
|
||||
DialContext(ctx context.Context, network string, endpoint string) (net.Conn, error)
|
||||
Close(ctx context.Context) error
|
||||
}
|
202
outbound/wsc.go
202
outbound/wsc.go
@ -3,11 +3,7 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@ -16,32 +12,21 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/wsc"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
)
|
||||
|
||||
var _ adapter.Outbound = &WSC{}
|
||||
|
||||
var _ net.Conn = &wscConn{}
|
||||
|
||||
type WSC struct {
|
||||
myOutboundAdapter
|
||||
dialer N.Dialer
|
||||
serverAddr metadata.Socksaddr
|
||||
auth string
|
||||
path string
|
||||
tlsConfig tls.Config
|
||||
}
|
||||
|
||||
type wscConn struct {
|
||||
net.Conn
|
||||
reader *wsutil.Reader
|
||||
mu sync.Mutex
|
||||
dialer N.Dialer
|
||||
tlsConfig tls.Config
|
||||
client adapter.WSCClientTransport
|
||||
}
|
||||
|
||||
func NewWSC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WSCOutboundOptions) (*WSC, error) {
|
||||
@ -59,19 +44,19 @@ func NewWSC(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
tag: tag,
|
||||
dependencies: withDialerDependency(options.DialerOptions),
|
||||
},
|
||||
dialer: outboundDialer,
|
||||
serverAddr: options.ServerOptions.Build(),
|
||||
auth: options.Auth,
|
||||
path: options.Path,
|
||||
dialer: outboundDialer,
|
||||
}
|
||||
if outbound.auth == "" {
|
||||
|
||||
serverAddr := options.ServerOptions.Build()
|
||||
|
||||
if options.Auth == "" {
|
||||
return nil, exceptions.New("Invalid Auth to use in authentications")
|
||||
}
|
||||
if !outbound.serverAddr.IsValid() {
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, exceptions.New("Invalid server address")
|
||||
}
|
||||
if outbound.path == "" {
|
||||
outbound.path = "/"
|
||||
if options.Path == "" {
|
||||
options.Path = "/"
|
||||
}
|
||||
if options.TLS != nil {
|
||||
outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||
@ -80,6 +65,14 @@ func NewWSC(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
}
|
||||
}
|
||||
|
||||
outbound.client = &wsc.Client{
|
||||
Auth: options.Auth,
|
||||
Host: serverAddr.String(),
|
||||
Path: options.Path,
|
||||
TLS: outbound.tlsConfig,
|
||||
Dialer: outbound.dialer,
|
||||
}
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@ -91,13 +84,7 @@ func (wsc *WSC) DialContext(ctx context.Context, network string, destination met
|
||||
return nil, exceptions.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
wsc.logger.InfoContext(ctx, "WSC outbound connection to ", destination)
|
||||
|
||||
conn, err := wsc.newWscConn(ctx, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
return wsc.client.DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (wsc *WSC) ListenPacket(ctx context.Context, destination metadata.Socksaddr) (net.PacketConn, error) {
|
||||
@ -125,153 +112,12 @@ func (wsc *WSC) NewPacketConnection(ctx context.Context, conn network.PacketConn
|
||||
// fmt.Println("wsc packet conn data is : ", dest, " | ", dest.Network(), " | ", buffer.Len())
|
||||
|
||||
// time.Sleep(time.Second * 10)
|
||||
return NewPacketConnection(ctx, wsc.dialer, conn, metadata)
|
||||
// return NewPacketConnection(ctx, wsc.dialer, conn, metadata)
|
||||
return NewPacketConnection(ctx, wsc, conn, metadata)
|
||||
}
|
||||
|
||||
func (wsc *WSC) Close() error {
|
||||
return wsc.cleanup()
|
||||
}
|
||||
|
||||
func (wsc *WSC) cleanup() error {
|
||||
scheme := "http"
|
||||
var tlsConfig *tls.STDConfig
|
||||
if wsc.tlsConfig != nil {
|
||||
scheme = "https"
|
||||
var err error
|
||||
tlsConfig, err = wsc.tlsConfig.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: wsc.serverAddr.String(),
|
||||
Path: "/cleanup",
|
||||
RawQuery: "",
|
||||
}
|
||||
pQuery := pURL.Query()
|
||||
pQuery.Set("auth", wsc.auth)
|
||||
pURL.RawQuery = pQuery.Encode()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return wsc.dialer.DialContext(ctx, network, metadata.ParseSocksaddr(addr))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, pURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *WSC) newWscConn(ctx context.Context, endpoint metadata.Socksaddr) (*wscConn, error) {
|
||||
scheme := "ws"
|
||||
if wsc.tlsConfig != nil {
|
||||
scheme = "wss"
|
||||
}
|
||||
|
||||
pURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: wsc.serverAddr.String(),
|
||||
Path: wsc.path,
|
||||
RawQuery: "",
|
||||
}
|
||||
pQuery := pURL.Query()
|
||||
pQuery.Set("auth", wsc.auth)
|
||||
pQuery.Set("ep", endpoint.String())
|
||||
pURL.RawQuery = pQuery.Encode()
|
||||
|
||||
dialer := ws.Dialer{
|
||||
NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := wsc.dialer.DialContext(ctx, N.NetworkTCP, metadata.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if wsc.tlsConfig != nil {
|
||||
conn, err = tls.ClientHandshake(ctx, conn, wsc.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
wsConn, _, _, err := dialer.Dial(ctx, pURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := wsutil.NewReader(wsConn, ws.StateClientSide)
|
||||
|
||||
return &wscConn{
|
||||
Conn: wsConn,
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cli *wscConn) Close() error {
|
||||
cli.mu.Lock()
|
||||
defer cli.mu.Unlock()
|
||||
_ = wsutil.WriteClientMessage(cli.Conn, ws.OpClose, nil)
|
||||
return cli.Conn.Close()
|
||||
}
|
||||
|
||||
func (cli *wscConn) Read(b []byte) (n int, err error) {
|
||||
err = nil
|
||||
var header ws.Header
|
||||
for {
|
||||
n, err = cli.reader.Read(b)
|
||||
if n > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !exceptions.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {
|
||||
return
|
||||
}
|
||||
|
||||
header, err = cli.reader.NextFrame()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch header.OpCode {
|
||||
case ws.OpBinary, ws.OpText, ws.OpContinuation:
|
||||
continue
|
||||
case ws.OpPing:
|
||||
wsutil.WriteClientMessage(cli.Conn, ws.OpPong, nil)
|
||||
case ws.OpPong:
|
||||
continue
|
||||
case ws.OpClose:
|
||||
err = io.EOF
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *wscConn) Write(b []byte) (n int, err error) {
|
||||
cli.mu.Lock()
|
||||
defer cli.mu.Unlock()
|
||||
if err := wsutil.WriteClientBinary(cli.Conn, b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
return wsc.client.Close(ctx)
|
||||
}
|
||||
|
76
transport/wsc/client.go
Normal file
76
transport/wsc/client.go
Normal file
@ -0,0 +1,76 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ adapter.WSCClientTransport = &Client{}
|
||||
|
||||
type Client struct {
|
||||
Auth string
|
||||
Host string
|
||||
Path string
|
||||
TLS tls.Config
|
||||
Dialer N.Dialer
|
||||
}
|
||||
|
||||
func (cli *Client) DialContext(ctx context.Context, network string, endpoint string) (net.Conn, error) {
|
||||
return cli.newConn(ctx, network, endpoint)
|
||||
}
|
||||
|
||||
func (cli *Client) Close(ctx context.Context) error {
|
||||
return cli.cleanup(ctx)
|
||||
}
|
||||
|
||||
func (cli *Client) cleanup(ctx context.Context) error {
|
||||
scheme := "http"
|
||||
var tlsConfig *tls.STDConfig
|
||||
if cli.TLS != nil {
|
||||
scheme = "https"
|
||||
var err error
|
||||
tlsConfig, err = cli.TLS.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: cli.Host,
|
||||
Path: "/cleanup",
|
||||
RawQuery: "",
|
||||
}
|
||||
pQuery := pURL.Query()
|
||||
pQuery.Set("auth", cli.Auth)
|
||||
pURL.RawQuery = pQuery.Encode()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return cli.Dialer.DialContext(ctx, network, metadata.ParseSocksaddr(addr))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, pURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
122
transport/wsc/client_conn.go
Normal file
122
transport/wsc/client_conn.go
Normal file
@ -0,0 +1,122 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
)
|
||||
|
||||
var _ net.Conn = &clientConn{}
|
||||
|
||||
type clientConn struct {
|
||||
net.Conn
|
||||
reader *wsutil.Reader
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (cli *Client) newConn(ctx context.Context, network string, endpoint string) (*clientConn, error) {
|
||||
scheme := "ws"
|
||||
if cli.TLS != nil {
|
||||
scheme = "wss"
|
||||
}
|
||||
|
||||
pURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: cli.Host,
|
||||
Path: cli.Path,
|
||||
RawQuery: "",
|
||||
}
|
||||
pQuery := pURL.Query()
|
||||
pQuery.Set("auth", cli.Auth)
|
||||
pQuery.Set("ep", endpoint)
|
||||
pQuery.Set("net", network)
|
||||
pURL.RawQuery = pQuery.Encode()
|
||||
|
||||
dialer := ws.Dialer{
|
||||
NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := cli.Dialer.DialContext(ctx, N.NetworkTCP, metadata.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cli.TLS != nil {
|
||||
conn, err = tls.ClientHandshake(ctx, conn, cli.TLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
conn, _, _, err := dialer.Dial(ctx, pURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := wsutil.NewReader(conn, ws.StateClientSide)
|
||||
|
||||
return &clientConn{
|
||||
Conn: conn,
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (conn *clientConn) Close() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
_ = wsutil.WriteClientMessage(conn.Conn, ws.OpClose, nil)
|
||||
return conn.Conn.Close()
|
||||
}
|
||||
|
||||
func (conn *clientConn) Read(b []byte) (n int, err error) {
|
||||
err = nil
|
||||
var header ws.Header
|
||||
for {
|
||||
n, err = conn.reader.Read(b)
|
||||
if n > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !exceptions.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {
|
||||
return
|
||||
}
|
||||
|
||||
header, err = conn.reader.NextFrame()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch header.OpCode {
|
||||
case ws.OpBinary, ws.OpText, ws.OpContinuation:
|
||||
continue
|
||||
case ws.OpPing:
|
||||
wsutil.WriteClientMessage(conn.Conn, ws.OpPong, nil)
|
||||
case ws.OpPong:
|
||||
continue
|
||||
case ws.OpClose:
|
||||
err = io.EOF
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *clientConn) Write(b []byte) (n int, err error) {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if err := wsutil.WriteClientBinary(conn.Conn, b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user