mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-19 22:08:07 +03:00
Add Snell protocol. Refactor MASQUE HTTP/2, Fair Queue. Update XHTTP, OpenVPN, Sudoku, Fallback. Fixes
This commit is contained in:
294
transport/snell/service.go
Normal file
294
transport/snell/service.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
obfs "github.com/sagernet/sing-box/transport/simple-obfs"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, clientID string)
|
||||
|
||||
NewPacketConnection(ctx context.Context, conn net.PacketConn, source M.Socksaddr, clientID string)
|
||||
}
|
||||
|
||||
type ServiceOptions struct {
|
||||
PSK []byte
|
||||
Version int
|
||||
ObfsMode string
|
||||
UDP bool
|
||||
Logger logger.ContextLogger
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
psk []byte
|
||||
version int
|
||||
obfsMode string
|
||||
udp bool
|
||||
logger logger.ContextLogger
|
||||
handler Handler
|
||||
}
|
||||
|
||||
func NewService(options ServiceOptions) (*Service, error) {
|
||||
version := options.Version
|
||||
if version == 0 {
|
||||
version = Version4
|
||||
}
|
||||
if version != Version4 && version != Version5 {
|
||||
return nil, fmt.Errorf("snell inbound version %d is not supported", version)
|
||||
}
|
||||
if len(options.PSK) == 0 {
|
||||
return nil, errors.New("snell inbound requires psk")
|
||||
}
|
||||
switch options.ObfsMode {
|
||||
case "", "http", "tls":
|
||||
default:
|
||||
return nil, fmt.Errorf("snell inbound obfs mode error: %s", options.ObfsMode)
|
||||
}
|
||||
return &Service{
|
||||
psk: options.PSK,
|
||||
version: version,
|
||||
obfsMode: options.ObfsMode,
|
||||
udp: options.UDP,
|
||||
logger: options.Logger,
|
||||
handler: options.Handler,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) NewConnection(ctx context.Context, rawConn net.Conn, source M.Socksaddr) error {
|
||||
conn := rawConn
|
||||
switch s.obfsMode {
|
||||
case "http":
|
||||
conn = obfs.NewHTTPObfsServer(conn)
|
||||
case "tls":
|
||||
conn = obfs.NewTLSObfsServer(conn)
|
||||
}
|
||||
stream := ServerStreamConn(conn, s.psk, s.version)
|
||||
for {
|
||||
reuse, err := s.handleRequest(ctx, stream, source)
|
||||
if err != nil || !reuse {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleRequest(ctx context.Context, stream *Snell, source M.Socksaddr) (bool, error) {
|
||||
br := bufio.NewReader(stream)
|
||||
version, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if version != Version {
|
||||
return false, fmt.Errorf("snell invalid protocol version: %d", version)
|
||||
}
|
||||
command, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if command == CommandPing {
|
||||
_, _ = stream.Write([]byte{CommandPong})
|
||||
return false, nil
|
||||
}
|
||||
clientID, err := readClientID(br)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch command {
|
||||
case CommandConnect, CommandConnectV2:
|
||||
return s.handleTCP(ctx, stream, br, command == CommandConnectV2, clientID, source)
|
||||
case CommandUDP:
|
||||
if !s.udp {
|
||||
return false, errors.New("snell UDP is disabled")
|
||||
}
|
||||
return false, s.handleUDP(ctx, stream, clientID, source)
|
||||
default:
|
||||
return false, fmt.Errorf("snell unknown command: %d", command)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleTCP(ctx context.Context, stream *Snell, br *bufio.Reader, reuse bool, clientID string, source M.Socksaddr) (bool, error) {
|
||||
hostLen, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if hostLen == 0 {
|
||||
return false, errors.New("snell connect host is empty")
|
||||
}
|
||||
hostBytes := make([]byte, int(hostLen))
|
||||
if _, err := io.ReadFull(br, hostBytes); err != nil {
|
||||
return false, err
|
||||
}
|
||||
var portBytes [2]byte
|
||||
if _, err := io.ReadFull(br, portBytes[:]); err != nil {
|
||||
return false, err
|
||||
}
|
||||
destination := M.ParseSocksaddrHostPort(string(hostBytes), binary.BigEndian.Uint16(portBytes[:]))
|
||||
conn := &tcpRequestConn{
|
||||
Conn: stream,
|
||||
reader: br,
|
||||
reuse: reuse,
|
||||
}
|
||||
s.handler.NewConnection(ctx, conn, source, destination, clientID)
|
||||
if !reuse {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *Service) handleUDP(ctx context.Context, stream *Snell, clientID string, source M.Socksaddr) error {
|
||||
if _, err := stream.Write([]byte{CommandTunnel}); err != nil {
|
||||
return err
|
||||
}
|
||||
pc := &serverPacketConn{
|
||||
conn: stream,
|
||||
writeMu: &sync.Mutex{},
|
||||
}
|
||||
s.handler.NewPacketConnection(ctx, pc, source, clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxPacketLength = 0x3fff
|
||||
|
||||
func readClientID(r *bufio.Reader) (string, error) {
|
||||
length, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
id := make([]byte, int(length))
|
||||
if _, err := io.ReadFull(r, id); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(id), nil
|
||||
}
|
||||
|
||||
func writeCommandError(w io.Writer, code byte, message string) error {
|
||||
msg := []byte(message)
|
||||
if len(msg) > 255 {
|
||||
msg = msg[:255]
|
||||
}
|
||||
buf := make([]byte, 0, 3+len(msg))
|
||||
buf = append(buf, CommandError, code, byte(len(msg)))
|
||||
buf = append(buf, msg...)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
type tcpRequestConn struct {
|
||||
net.Conn
|
||||
reader *bufio.Reader
|
||||
reuse bool
|
||||
writeMu sync.Mutex
|
||||
closeOnce sync.Once
|
||||
replyWritten bool
|
||||
}
|
||||
|
||||
func (c *tcpRequestConn) Read(p []byte) (int, error) {
|
||||
n, err := c.reader.Read(p)
|
||||
if errors.Is(err, ErrZeroChunk) {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *tcpRequestConn) Write(p []byte) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
if !c.replyWritten {
|
||||
payload := make([]byte, 1+len(p))
|
||||
payload[0] = CommandTunnel
|
||||
copy(payload[1:], p)
|
||||
if _, err := c.Conn.Write(payload); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.replyWritten = true
|
||||
return len(p), nil
|
||||
}
|
||||
return c.Conn.Write(p)
|
||||
}
|
||||
|
||||
func (c *tcpRequestConn) CloseWrite() error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c *tcpRequestConn) Close() error {
|
||||
var err error
|
||||
c.closeOnce.Do(func() {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
if !c.replyWritten {
|
||||
err = writeCommandError(c.Conn, 0x65, "Remote EOF")
|
||||
if !c.reuse {
|
||||
err = errors.Join(err, c.Conn.Close())
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.reuse {
|
||||
_, err = c.Conn.Write(nil)
|
||||
return
|
||||
}
|
||||
err = c.Conn.Close()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type serverPacketConn struct {
|
||||
conn *Snell
|
||||
writeMu *sync.Mutex
|
||||
readBuf []byte
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
if c.readBuf == nil {
|
||||
c.readBuf = make([]byte, maxPacketLength)
|
||||
}
|
||||
for {
|
||||
n, err := c.conn.Read(c.readBuf)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, ErrZeroChunk) {
|
||||
return 0, nil, io.EOF
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
request, err := ParseUDPRequest(c.readBuf[:n])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var destination M.Socksaddr
|
||||
if request.Ip.IsValid() {
|
||||
destination = M.SocksaddrFrom(request.Ip, request.Port)
|
||||
} else {
|
||||
destination = M.ParseSocksaddrHostPort(request.Host, request.Port)
|
||||
}
|
||||
length := copy(p, request.Payload)
|
||||
if destination.IsFqdn() {
|
||||
return length, destination, nil
|
||||
}
|
||||
return length, destination.UDPAddr(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
return WritePacketResponse(c.conn, addr, p)
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) Close() error { return c.conn.Close() }
|
||||
func (c *serverPacketConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
|
||||
func (c *serverPacketConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) }
|
||||
func (c *serverPacketConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) }
|
||||
func (c *serverPacketConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }
|
||||
Reference in New Issue
Block a user