mirror of https://github.com/Dreamacro/clash.git
174 lines
3.1 KiB
Go
174 lines
3.1 KiB
Go
package obfs
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
type SessionStatus = byte
|
|
|
|
const (
|
|
SessionStatusNew SessionStatus = 0x01
|
|
SessionStatusKeep SessionStatus = 0x02
|
|
SessionStatusEnd SessionStatus = 0x03
|
|
SessionStatusKeepAlive SessionStatus = 0x04
|
|
)
|
|
|
|
const (
|
|
OptionNone = byte(0x00)
|
|
OptionData = byte(0x01)
|
|
OptionError = byte(0x02)
|
|
)
|
|
|
|
type MuxOption struct {
|
|
ID [2]byte
|
|
Port uint16
|
|
Host string
|
|
Type string
|
|
}
|
|
|
|
// Mux is an mux-compatible client for v2ray-plugin, not a complete implementation
|
|
type Mux struct {
|
|
net.Conn
|
|
buf bytes.Buffer
|
|
id [2]byte
|
|
length [2]byte
|
|
status [2]byte
|
|
otb []byte
|
|
remain int
|
|
}
|
|
|
|
func (m *Mux) Read(b []byte) (int, error) {
|
|
if m.remain != 0 {
|
|
length := m.remain
|
|
if len(b) < m.remain {
|
|
length = len(b)
|
|
}
|
|
|
|
n, err := m.Conn.Read(b[:length])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
m.remain = m.remain - n
|
|
return n, nil
|
|
}
|
|
|
|
for {
|
|
_, err := io.ReadFull(m.Conn, m.length[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
length := binary.BigEndian.Uint16(m.length[:])
|
|
if length > 512 {
|
|
return 0, errors.New("invalid metalen")
|
|
}
|
|
|
|
_, err = io.ReadFull(m.Conn, m.id[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
_, err = m.Conn.Read(m.status[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
opcode := m.status[0]
|
|
if opcode == SessionStatusKeepAlive {
|
|
continue
|
|
}
|
|
|
|
opts := m.status[1]
|
|
|
|
if opts != OptionData {
|
|
continue
|
|
}
|
|
|
|
_, err = io.ReadFull(m.Conn, m.length[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
dataLen := int(binary.BigEndian.Uint16(m.length[:]))
|
|
m.remain = dataLen
|
|
if dataLen > len(b) {
|
|
dataLen = len(b)
|
|
}
|
|
|
|
n, err := m.Conn.Read(b[:dataLen])
|
|
m.remain -= n
|
|
return n, err
|
|
}
|
|
}
|
|
|
|
func (m *Mux) Write(b []byte) (int, error) {
|
|
if m.otb != nil {
|
|
// create a sub connection
|
|
if _, err := m.Conn.Write(m.otb); err != nil {
|
|
return 0, err
|
|
}
|
|
m.otb = nil
|
|
}
|
|
m.buf.Reset()
|
|
binary.Write(&m.buf, binary.BigEndian, uint16(4))
|
|
m.buf.Write(m.id[:])
|
|
m.buf.WriteByte(SessionStatusKeep)
|
|
m.buf.WriteByte(OptionData)
|
|
binary.Write(&m.buf, binary.BigEndian, uint16(len(b)))
|
|
m.buf.Write(b)
|
|
|
|
return m.Conn.Write(m.buf.Bytes())
|
|
}
|
|
|
|
func (m *Mux) Close() error {
|
|
_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return m.Conn.Close()
|
|
}
|
|
|
|
func NewMux(conn net.Conn, option MuxOption) *Mux {
|
|
buf := &bytes.Buffer{}
|
|
|
|
// fill empty length
|
|
buf.Write([]byte{0x0, 0x0})
|
|
buf.Write(option.ID[:])
|
|
buf.WriteByte(SessionStatusNew)
|
|
buf.WriteByte(OptionNone)
|
|
|
|
// tcp
|
|
netType := byte(0x1)
|
|
if option.Type == "udp" {
|
|
netType = byte(0x2)
|
|
}
|
|
buf.WriteByte(netType)
|
|
|
|
// port
|
|
binary.Write(buf, binary.BigEndian, option.Port)
|
|
|
|
// address
|
|
ip := net.ParseIP(option.Host)
|
|
if ip == nil {
|
|
buf.WriteByte(0x2)
|
|
buf.WriteString(option.Host)
|
|
} else if ipv4 := ip.To4(); ipv4 != nil {
|
|
buf.WriteByte(0x1)
|
|
buf.Write(ipv4)
|
|
} else {
|
|
buf.WriteByte(0x3)
|
|
buf.Write(ip.To16())
|
|
}
|
|
|
|
metadata := buf.Bytes()
|
|
binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
|
|
|
|
return &Mux{
|
|
Conn: conn,
|
|
id: option.ID,
|
|
otb: metadata,
|
|
}
|
|
}
|