add wsc packet connection

This commit is contained in:
Mobin 2025-08-31 20:08:42 +03:30
parent 1d4cd315a6
commit 007bcb78d1
6 changed files with 380 additions and 54 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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,

View 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()
}
*/

View 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
*/