mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-09 13:04:06 +08:00
add wsc packet connection
This commit is contained in:
parent
1d4cd315a6
commit
007bcb78d1
@ -10,5 +10,6 @@ type WSCServerTransport interface {
|
||||
|
||||
type WSCClientTransport interface {
|
||||
DialContext(ctx context.Context, network string, endpoint string) (net.Conn, error)
|
||||
ListenPacket(ctx context.Context, network string, endpoint string) (net.PacketConn, error)
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
@ -92,7 +92,8 @@ func (wsc *WSC) ListenPacket(ctx context.Context, destination metadata.Socksaddr
|
||||
meta.Outbound = wsc.tag
|
||||
meta.Destination = destination
|
||||
wsc.logger.InfoContext(ctx, "WSC outbound packet to ", destination)
|
||||
return wsc.dialer.ListenPacket(ctx, destination)
|
||||
// return wsc.dialer.ListenPacket(ctx, destination)
|
||||
return wsc.client.ListenPacket(ctx, N.NetworkUDP, destination.String())
|
||||
}
|
||||
|
||||
func (wsc *WSC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
@ -101,18 +102,6 @@ func (wsc *WSC) NewConnection(ctx context.Context, conn net.Conn, metadata adapt
|
||||
|
||||
func (wsc *WSC) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext) error {
|
||||
fmt.Println("new packet conn: ", metadata)
|
||||
// fmt.Println("wsc packet conn: ", metadata, " | ", conn)
|
||||
// buffer := buf.NewPacket()
|
||||
// defer buffer.Release()
|
||||
// dest, err := conn.ReadPacket(buffer)
|
||||
// if err != nil {
|
||||
// fmt.Println("error wsc packet conn: ", err)
|
||||
// return err
|
||||
// }
|
||||
// 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, conn, metadata)
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/ws"
|
||||
)
|
||||
|
||||
var _ adapter.WSCClientTransport = &Client{}
|
||||
@ -26,10 +27,57 @@ func (cli *Client) DialContext(ctx context.Context, network string, endpoint str
|
||||
return cli.newConn(ctx, network, endpoint)
|
||||
}
|
||||
|
||||
func (cli *Client) ListenPacket(ctx context.Context, network string, endpoint string) (net.PacketConn, error) {
|
||||
return cli.newPacketConn(ctx, network, endpoint)
|
||||
}
|
||||
|
||||
func (cli *Client) Close(ctx context.Context) error {
|
||||
return cli.cleanup(ctx)
|
||||
}
|
||||
|
||||
func (cli *Client) newWSConn(ctx context.Context, network string, endpoint string) (net.Conn, 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
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (cli *Client) cleanup(ctx context.Context) error {
|
||||
scheme := "http"
|
||||
var tlsConfig *tls.STDConfig
|
||||
|
@ -4,13 +4,9 @@ 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"
|
||||
)
|
||||
@ -24,47 +20,11 @@ type clientConn struct {
|
||||
}
|
||||
|
||||
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())
|
||||
conn, err := cli.newWSConn(ctx, network, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := wsutil.NewReader(conn, ws.StateClientSide)
|
||||
|
||||
return &clientConn{
|
||||
Conn: conn,
|
||||
reader: reader,
|
||||
|
213
transport/wsc/client_packet_conn.go
Normal file
213
transport/wsc/client_packet_conn.go
Normal file
@ -0,0 +1,213 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
)
|
||||
|
||||
var _ net.PacketConn = &clientPacketConn{}
|
||||
|
||||
type clientPacketConn struct {
|
||||
net.Conn
|
||||
reader *wsutil.Reader
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (cli *Client) newPacketConn(ctx context.Context, network string, endpoint string) (*clientPacketConn, error) {
|
||||
conn, err := cli.newWSConn(ctx, network, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader := wsutil.NewReader(conn, ws.StateClientSide)
|
||||
return &clientPacketConn{
|
||||
Conn: conn,
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (packetConn *clientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (packetConn *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// func (c *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
// destination := M.SocksaddrFromNet(addr)
|
||||
// buffer := buf.NewSize(M.SocksaddrSerializer.AddrPortLen(destination) + len(p))
|
||||
// defer buffer.Release()
|
||||
// if err = M.SocksaddrSerializer.WriteAddrPort(buffer, destination); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
// if _, err = buffer.Write(p); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
// c.mu.Lock()
|
||||
// defer c.mu.Unlock()
|
||||
// if err = wsutil.WriteClientBinary(c.Conn, buffer.Bytes()); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
// return len(p), nil
|
||||
// }
|
||||
|
||||
func (packetConn *clientPacketConn) Close() error {
|
||||
packetConn.mu.Lock()
|
||||
defer packetConn.mu.Unlock()
|
||||
_ = wsutil.WriteClientMessage(packetConn.Conn, ws.OpClose, nil)
|
||||
return packetConn.Conn.Close()
|
||||
}
|
||||
|
||||
/*
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
)
|
||||
|
||||
// clientPacketConn implements net.PacketConn over WebSocket.
|
||||
type clientPacketConn struct {
|
||||
net.Conn
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// newPacketConn dials a WebSocket endpoint for packet based communications.
|
||||
func (cli *Client) newPacketConn(ctx context.Context, network string, endpoint string) (*clientPacketConn, 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)
|
||||
if network != "" {
|
||||
pQuery.Set("net", network)
|
||||
}
|
||||
if endpoint != "" {
|
||||
pQuery.Set("ep", endpoint)
|
||||
}
|
||||
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, M.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
|
||||
}
|
||||
return &clientPacketConn{Conn: conn}, nil
|
||||
}
|
||||
|
||||
// ListenPacket creates a packet-oriented WebSocket connection.
|
||||
func (cli *Client) ListenPacket(ctx context.Context, network string, endpoint string) (net.PacketConn, error) {
|
||||
return cli.newPacketConn(ctx, network, endpoint)
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||
msg, err := wsutil.ReadServerBinary(c.Conn)
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
}
|
||||
reader := bytes.NewReader(msg)
|
||||
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
}
|
||||
_, err = buffer.Write(msg[len(msg)-reader.Len():])
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
}
|
||||
return destination, nil
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination)))
|
||||
if err := M.SocksaddrSerializer.WriteAddrPort(header, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return wsutil.WriteClientBinary(c.Conn, buffer.Bytes())
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
msg, err := wsutil.ReadServerBinary(c.Conn)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
reader := bytes.NewReader(msg)
|
||||
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
n = copy(p, msg[len(msg)-reader.Len():])
|
||||
if destination.IsFqdn() {
|
||||
addr = destination
|
||||
} else {
|
||||
addr = destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
destination := M.SocksaddrFromNet(addr)
|
||||
buffer := buf.NewSize(M.SocksaddrSerializer.AddrPortLen(destination) + len(p))
|
||||
defer buffer.Release()
|
||||
if err = M.SocksaddrSerializer.WriteAddrPort(buffer, destination); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = buffer.Write(p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if err = wsutil.WriteClientBinary(c.Conn, buffer.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_ = wsutil.WriteClientMessage(c.Conn, ws.OpClose, nil)
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) LocalAddr() net.Addr {
|
||||
return c.Conn.LocalAddr()
|
||||
}
|
||||
*/
|
115
transport/wsc/packet_conn_payload.go
Normal file
115
transport/wsc/packet_conn_payload.go
Normal file
@ -0,0 +1,115 @@
|
||||
package wsc
|
||||
|
||||
type packetConnPayload struct {
|
||||
ip [16]byte
|
||||
port uint16
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Header is 18 bytes: 16 for IP + 2 for port (big-endian)
|
||||
type Header struct {
|
||||
IP [16]byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
const (
|
||||
headerLen = 18
|
||||
)
|
||||
|
||||
// ipv4ToMapped fills a 16-byte buffer with ::ffff:w.x.y.z
|
||||
func ipv4ToMapped(v4 net.IP, dst *[16]byte) {
|
||||
// v4 must be 4 bytes (no zone)
|
||||
copy(dst[:], []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0xff, 0xff,
|
||||
v4[0], v4[1], v4[2], v4[3],
|
||||
})
|
||||
}
|
||||
|
||||
// ipTo16Mapped returns a 16-byte IPv6 form.
|
||||
// - IPv6 stays as-is (compressed/expanded form doesn't matter; we copy the 16 raw bytes).
|
||||
// - IPv4 becomes IPv4-mapped IPv6 ::ffff:w.x.y.z
|
||||
func ipTo16Mapped(ip net.IP) ([16]byte, error) {
|
||||
var out [16]byte
|
||||
if ip == nil {
|
||||
return out, errors.New("nil IP")
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
ipv4ToMapped(v4, &out)
|
||||
return out, nil
|
||||
}
|
||||
v6 := ip.To16()
|
||||
if v6 == nil || len(v6) != 16 {
|
||||
return out, errors.New("invalid IP")
|
||||
}
|
||||
copy(out[:], v6)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NewHeader builds a Header from net.IP + port.
|
||||
func NewHeader(ip net.IP, port int) (Header, error) {
|
||||
var h Header
|
||||
ip16, err := ipTo16Mapped(ip)
|
||||
if err != nil {
|
||||
return h, err
|
||||
}
|
||||
h.IP = ip16
|
||||
if port < 0 || port > 65535 {
|
||||
return h, errors.New("invalid port")
|
||||
}
|
||||
h.Port = uint16(port)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// FromTCPAddr / FromUDPAddr convenience.
|
||||
func FromTCPAddr(a *net.TCPAddr) (Header, error) { return NewHeader(a.IP, a.Port) }
|
||||
func FromUDPAddr(a *net.UDPAddr) (Header, error) { return NewHeader(a.IP, a.Port) }
|
||||
|
||||
// MarshalBinary -> 18 bytes
|
||||
func (h Header) MarshalBinary() []byte {
|
||||
b := make([]byte, headerLen)
|
||||
copy(b[:16], h.IP[:])
|
||||
binary.BigEndian.PutUint16(b[16:], h.Port)
|
||||
return b
|
||||
}
|
||||
|
||||
// UnmarshalBinary <- 18 bytes
|
||||
func (h *Header) UnmarshalBinary(b []byte) error {
|
||||
if len(b) < headerLen {
|
||||
return errors.New("short header")
|
||||
}
|
||||
copy(h.IP[:], b[:16])
|
||||
h.Port = binary.BigEndian.Uint16(b[16:18])
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToNetAddr returns a *net.TCPAddr or *net.UDPAddr-ready IP & port.
|
||||
// If the address is IPv4-mapped, it returns the 4-byte form for convenience.
|
||||
func (h Header) ToIPPort() (net.IP, int) {
|
||||
ip := net.IP(h.IP[:]).To16()
|
||||
// Detect IPv4-mapped ::ffff:w.x.y.z and convert back to v4 if you like:
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4, int(h.Port)
|
||||
}
|
||||
return ip, int(h.Port)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Encode
|
||||
dst := net.ParseIP("192.0.2.10")
|
||||
hdr, _ := NewHeader(dst, 443)
|
||||
wireBytes := hdr.MarshalBinary() // 18 bytes ready to send
|
||||
|
||||
// Decode
|
||||
var got Header
|
||||
_ = got.UnmarshalBinary(wireBytes)
|
||||
ip, port := got.ToIPPort() // ip is 4-byte 192.0.2.10, port=443
|
||||
*/
|
Loading…
x
Reference in New Issue
Block a user