mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-17 05:11:05 +03:00
Add Snell protocol. Refactor MASQUE HTTP/2, Fair Queue. Update XHTTP, OpenVPN, Sudoku, Fallback. Fixes
This commit is contained in:
408
transport/snell/snell.go
Normal file
408
transport/snell/snell.go
Normal file
@@ -0,0 +1,408 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
const (
|
||||
Version1 = 1
|
||||
Version2 = 2
|
||||
Version3 = 3
|
||||
Version4 = 4
|
||||
Version5 = 5
|
||||
DefaultSnellVersion = Version1
|
||||
|
||||
// max packet length
|
||||
maxLength = 0x3FFF
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
CommandConnectV2 byte = 5
|
||||
CommandUDP byte = 6
|
||||
CommandUDPForward byte = 1
|
||||
|
||||
CommandTunnel byte = 0
|
||||
CommandPong byte = 1
|
||||
CommandError byte = 2
|
||||
|
||||
Version byte = 1
|
||||
)
|
||||
|
||||
// Snell wraps an encrypted stream and handles the snell reply header.
|
||||
type Snell struct {
|
||||
net.Conn
|
||||
buffer [1]byte
|
||||
reply bool
|
||||
}
|
||||
|
||||
func (s *Snell) Read(b []byte) (int, error) {
|
||||
if err := s.ReadReply(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (s *Snell) ReadReply() error {
|
||||
if s.reply {
|
||||
return nil
|
||||
}
|
||||
if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
s.reply = true
|
||||
if s.buffer[0] == CommandTunnel {
|
||||
return nil
|
||||
} else if s.buffer[0] != CommandError {
|
||||
return errors.New("command not support")
|
||||
}
|
||||
if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
errcode := int(s.buffer[0])
|
||||
if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
length := int(s.buffer[0])
|
||||
msg := make([]byte, length)
|
||||
if _, err := io.ReadFull(s.Conn, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg))
|
||||
}
|
||||
|
||||
func WriteHeader(conn net.Conn, host string, port uint, version int) error {
|
||||
return WriteHeaderWithReuse(conn, host, port, version, false)
|
||||
}
|
||||
|
||||
func WriteHeaderWithReuse(conn net.Conn, host string, port uint, version int, reuse bool) error {
|
||||
buffer := &bytes.Buffer{}
|
||||
buffer.WriteByte(Version)
|
||||
if version == Version2 || reuse {
|
||||
buffer.WriteByte(CommandConnectV2)
|
||||
} else {
|
||||
buffer.WriteByte(CommandConnect)
|
||||
}
|
||||
buffer.WriteByte(0)
|
||||
buffer.WriteByte(uint8(len(host)))
|
||||
buffer.WriteString(host)
|
||||
binary.Write(buffer, binary.BigEndian, uint16(port))
|
||||
if _, err := conn.Write(buffer.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUDPHeader(conn net.Conn, version int) error {
|
||||
if version < Version3 {
|
||||
return errors.New("unsupport UDP version")
|
||||
}
|
||||
_, err := conn.Write([]byte{Version, CommandUDP, 0x00})
|
||||
return err
|
||||
}
|
||||
|
||||
// HalfClose only works after the request negotiated the reuse command.
|
||||
func HalfClose(conn net.Conn) error {
|
||||
if err := writeZeroChunk(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
if s, ok := conn.(*Snell); ok {
|
||||
s.reply = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamConn wraps a raw connection with the snell stream cipher for the given version.
|
||||
func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
||||
if version >= Version4 {
|
||||
return &Snell{Conn: newV4Conn(conn, psk)}
|
||||
}
|
||||
var cipher Cipher
|
||||
if version != Version1 {
|
||||
cipher = NewAES128GCM(psk)
|
||||
} else {
|
||||
cipher = NewChacha20Poly1305(psk)
|
||||
}
|
||||
return &Snell{Conn: newAEADConn(conn, cipher)}
|
||||
}
|
||||
|
||||
// ServerStreamConn wraps a raw connection on the server side.
|
||||
func ServerStreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
||||
stream := StreamConn(conn, psk, version)
|
||||
stream.reply = true
|
||||
return stream
|
||||
}
|
||||
|
||||
func PacketConn(conn net.Conn) net.PacketConn {
|
||||
return &packetConn{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Snell) WritePacketFrame(b []byte) (int, error) {
|
||||
if fw, ok := s.Conn.(packetFrameWriter); ok {
|
||||
return fw.WritePacketFrame(b)
|
||||
}
|
||||
return s.Conn.Write(b)
|
||||
}
|
||||
|
||||
func WritePacket(w io.Writer, target, payload []byte) (int, error) {
|
||||
maxPayloadLength := maxLength - udpRequestHeaderLength(target)
|
||||
if maxPayloadLength <= 0 {
|
||||
return 0, errors.New("snell UDP address too large")
|
||||
}
|
||||
if len(payload) <= maxPayloadLength {
|
||||
return writePacket(w, target, payload)
|
||||
}
|
||||
return 0, errors.New("snell UDP payload too large")
|
||||
}
|
||||
|
||||
func WritePacketResponse(w io.Writer, addr net.Addr, payload []byte) (int, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
target := parseAddrToSocksAddr(addr)
|
||||
if len(target) == 0 {
|
||||
return 0, errors.New("snell UDP response address invalid")
|
||||
}
|
||||
switch target[0] {
|
||||
case atypIPv4:
|
||||
if len(target) < 1+net.IPv4len+2 {
|
||||
return 0, errors.New("snell UDP response address invalid")
|
||||
}
|
||||
buffer.WriteByte(0x04)
|
||||
buffer.Write(target[1 : 1+net.IPv4len+2])
|
||||
case atypIPv6:
|
||||
if len(target) < 1+net.IPv6len+2 {
|
||||
return 0, errors.New("snell UDP response address invalid")
|
||||
}
|
||||
buffer.WriteByte(0x06)
|
||||
buffer.Write(target[1 : 1+net.IPv6len+2])
|
||||
default:
|
||||
return 0, errors.New("snell UDP response address invalid")
|
||||
}
|
||||
buffer.Write(payload)
|
||||
var err error
|
||||
if fw, ok := w.(packetFrameWriter); ok {
|
||||
_, err = fw.WritePacketFrame(buffer.Bytes())
|
||||
} else {
|
||||
_, err = w.Write(buffer.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(payload), nil
|
||||
}
|
||||
|
||||
// UDPRequest is a parsed snell UDP forward request.
|
||||
type UDPRequest struct {
|
||||
Host string
|
||||
Ip netip.Addr
|
||||
Port uint16
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func ParseUDPRequest(packet []byte) (UDPRequest, error) {
|
||||
if len(packet) < 2 || packet[0] != CommandUDPForward {
|
||||
return UDPRequest{}, errors.New("snell invalid UDP request")
|
||||
}
|
||||
if hostLen := int(packet[1]); hostLen != 0 {
|
||||
if len(packet) <= 2+hostLen+2 {
|
||||
return UDPRequest{}, errors.New("snell invalid UDP domain request")
|
||||
}
|
||||
offset := 2 + hostLen
|
||||
return UDPRequest{
|
||||
Host: string(packet[2:offset]),
|
||||
Port: binary.BigEndian.Uint16(packet[offset : offset+2]),
|
||||
Payload: packet[offset+2:],
|
||||
}, nil
|
||||
}
|
||||
if len(packet) < 3 {
|
||||
return UDPRequest{}, errors.New("snell invalid UDP IP request")
|
||||
}
|
||||
switch packet[2] {
|
||||
case 0x04:
|
||||
if len(packet) < 3+net.IPv4len+2 {
|
||||
return UDPRequest{}, errors.New("snell invalid UDP IPv4 request")
|
||||
}
|
||||
offset := 3 + net.IPv4len
|
||||
ip, _ := netip.AddrFromSlice(packet[3:offset])
|
||||
return UDPRequest{
|
||||
Ip: ip.Unmap(),
|
||||
Port: binary.BigEndian.Uint16(packet[offset : offset+2]),
|
||||
Payload: packet[offset+2:],
|
||||
}, nil
|
||||
case 0x06:
|
||||
if len(packet) < 3+net.IPv6len+2 {
|
||||
return UDPRequest{}, errors.New("snell invalid UDP IPv6 request")
|
||||
}
|
||||
offset := 3 + net.IPv6len
|
||||
ip, _ := netip.AddrFromSlice(packet[3:offset])
|
||||
return UDPRequest{
|
||||
Ip: ip.Unmap(),
|
||||
Port: binary.BigEndian.Uint16(packet[offset : offset+2]),
|
||||
Payload: packet[offset+2:],
|
||||
}, nil
|
||||
default:
|
||||
return UDPRequest{}, errors.New("snell invalid UDP address type")
|
||||
}
|
||||
}
|
||||
|
||||
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) {
|
||||
b := buf.Get(buf.UDPBufferSize)
|
||||
defer buf.Put(b)
|
||||
n, err := r.Read(b)
|
||||
headLen := 1
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if n < headLen {
|
||||
return nil, 0, errors.New("insufficient UDP length")
|
||||
}
|
||||
switch b[0] {
|
||||
case 0x04:
|
||||
headLen += net.IPv4len + 2
|
||||
if n < headLen {
|
||||
err = errors.New("insufficient UDP length")
|
||||
break
|
||||
}
|
||||
b[0] = atypIPv4
|
||||
case 0x06:
|
||||
headLen += net.IPv6len + 2
|
||||
if n < headLen {
|
||||
err = errors.New("insufficient UDP length")
|
||||
break
|
||||
}
|
||||
b[0] = atypIPv6
|
||||
default:
|
||||
err = errors.New("ip version invalid")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
addr := splitSocksAddr(b[0:])
|
||||
if addr == nil {
|
||||
return nil, 0, errors.New("remote address invalid")
|
||||
}
|
||||
uAddr := addr.UDPAddr()
|
||||
if uAddr == nil {
|
||||
return nil, 0, errors.New("parse addr error")
|
||||
}
|
||||
length := len(payload)
|
||||
if n-headLen < length {
|
||||
length = n - headLen
|
||||
}
|
||||
copy(payload[:], b[headLen:headLen+length])
|
||||
return uAddr, length, nil
|
||||
}
|
||||
|
||||
var endSignal = []byte{}
|
||||
|
||||
type packetFrameWriter interface {
|
||||
WritePacketFrame([]byte) (int, error)
|
||||
}
|
||||
|
||||
func writeZeroChunk(conn net.Conn) error {
|
||||
if _, err := conn.Write(endSignal); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePacket(w io.Writer, target, payload []byte) (int, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
buffer.WriteByte(CommandUDPForward)
|
||||
switch target[0] {
|
||||
case atypDomainName:
|
||||
hostLen := target[1]
|
||||
if len(target) < 1+1+int(hostLen)+2 {
|
||||
return 0, errors.New("snell UDP address invalid")
|
||||
}
|
||||
buffer.Write(target[1 : 1+1+hostLen+2])
|
||||
case atypIPv4:
|
||||
if len(target) < 1+net.IPv4len+2 {
|
||||
return 0, errors.New("snell UDP address invalid")
|
||||
}
|
||||
buffer.Write([]byte{0x00, 0x04})
|
||||
buffer.Write(target[1 : 1+net.IPv4len+2])
|
||||
case atypIPv6:
|
||||
if len(target) < 1+net.IPv6len+2 {
|
||||
return 0, errors.New("snell UDP address invalid")
|
||||
}
|
||||
buffer.Write([]byte{0x00, 0x06})
|
||||
buffer.Write(target[1 : 1+net.IPv6len+2])
|
||||
default:
|
||||
return 0, errors.New("snell UDP address invalid")
|
||||
}
|
||||
buffer.Write(payload)
|
||||
if fw, ok := w.(packetFrameWriter); ok {
|
||||
_, err := fw.WritePacketFrame(buffer.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(payload), nil
|
||||
}
|
||||
_, err := w.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(payload), nil
|
||||
}
|
||||
|
||||
func udpRequestHeaderLength(target []byte) int {
|
||||
if len(target) == 0 {
|
||||
return maxLength + 1
|
||||
}
|
||||
switch target[0] {
|
||||
case atypDomainName:
|
||||
if len(target) < 2 {
|
||||
return maxLength + 1
|
||||
}
|
||||
return 1 + 1 + int(target[1]) + 2
|
||||
case atypIPv4:
|
||||
return 1 + 2 + net.IPv4len + 2
|
||||
case atypIPv6:
|
||||
return 1 + 2 + net.IPv6len + 2
|
||||
default:
|
||||
return maxLength + 1
|
||||
}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.Conn
|
||||
rMux sync.Mutex
|
||||
wMux sync.Mutex
|
||||
}
|
||||
|
||||
func (pc *packetConn) WritePacketFrame(b []byte) (int, error) {
|
||||
if s, ok := pc.Conn.(*Snell); ok {
|
||||
if fw, ok := s.Conn.(packetFrameWriter); ok {
|
||||
return fw.WritePacketFrame(b)
|
||||
}
|
||||
}
|
||||
return pc.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (pc *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
pc.wMux.Lock()
|
||||
defer pc.wMux.Unlock()
|
||||
return WritePacket(pc, parseAddrToSocksAddr(addr), b)
|
||||
}
|
||||
|
||||
func (pc *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
pc.rMux.Lock()
|
||||
defer pc.rMux.Unlock()
|
||||
addr, n, err := ReadPacket(pc.Conn, b)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return n, addr, nil
|
||||
}
|
||||
Reference in New Issue
Block a user