mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 10:01:30 +00:00
Add obfs-local and v2ray-plugin support for shadowsocks outbound
This commit is contained in:
parent
5a9913eca5
commit
ce567ffdde
|
@ -26,6 +26,8 @@ type ShadowsocksOutboundOptions struct {
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
|
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
UoT bool `json:"udp_over_tcp,omitempty"`
|
UoT bool `json:"udp_over_tcp,omitempty"`
|
||||||
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
|
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/transport/sip003"
|
||||||
"github.com/sagernet/sing-shadowsocks"
|
"github.com/sagernet/sing-shadowsocks"
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
@ -27,6 +28,7 @@ type Shadowsocks struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
method shadowsocks.Method
|
method shadowsocks.Method
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
|
plugin sip003.Plugin
|
||||||
uot bool
|
uot bool
|
||||||
multiplexDialer N.Dialer
|
multiplexDialer N.Dialer
|
||||||
}
|
}
|
||||||
|
@ -49,6 +51,12 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
uot: options.UoT,
|
uot: options.UoT,
|
||||||
}
|
}
|
||||||
|
if options.Plugin != "" {
|
||||||
|
outbound.plugin, err = sip003.CreatePlugin(options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if !options.UoT {
|
if !options.UoT {
|
||||||
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -135,7 +143,13 @@ type shadowsocksDialer Shadowsocks
|
||||||
func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
var outConn net.Conn
|
||||||
|
var err error
|
||||||
|
if h.plugin != nil {
|
||||||
|
outConn, err = h.plugin.DialContext(ctx)
|
||||||
|
} else {
|
||||||
|
outConn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
4
transport/simple-obfs/README.md
Normal file
4
transport/simple-obfs/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# simple-obfs
|
||||||
|
|
||||||
|
mod from https://github.com/Dreamacro/clash/transport/simple-obfs
|
||||||
|
version: 1.11.8
|
94
transport/simple-obfs/http.go
Normal file
94
transport/simple-obfs/http.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
B "github.com/sagernet/sing/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||||
|
type HTTPObfs struct {
|
||||||
|
net.Conn
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
buf []byte
|
||||||
|
offset int
|
||||||
|
firstRequest bool
|
||||||
|
firstResponse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||||
|
if ho.buf != nil {
|
||||||
|
n := copy(b, ho.buf[ho.offset:])
|
||||||
|
ho.offset += n
|
||||||
|
if ho.offset == len(ho.buf) {
|
||||||
|
B.Put(ho.buf)
|
||||||
|
ho.buf = nil
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ho.firstResponse {
|
||||||
|
buf := B.Get(B.BufferSize)
|
||||||
|
n, err := ho.Conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
B.Put(buf)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||||
|
if idx == -1 {
|
||||||
|
B.Put(buf)
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
ho.firstResponse = false
|
||||||
|
length := n - (idx + 4)
|
||||||
|
n = copy(b, buf[idx+4:n])
|
||||||
|
if length > n {
|
||||||
|
ho.buf = buf[:idx+4+length]
|
||||||
|
ho.offset = idx + 4 + n
|
||||||
|
} else {
|
||||||
|
B.Put(buf)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return ho.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||||
|
if ho.firstRequest {
|
||||||
|
randBytes := make([]byte, 16)
|
||||||
|
rand.Read(randBytes)
|
||||||
|
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||||
|
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
|
||||||
|
req.Header.Set("Upgrade", "websocket")
|
||||||
|
req.Header.Set("Connection", "Upgrade")
|
||||||
|
req.Host = ho.host
|
||||||
|
if ho.port != "80" {
|
||||||
|
req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
|
||||||
|
}
|
||||||
|
req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes))
|
||||||
|
req.ContentLength = int64(len(b))
|
||||||
|
err := req.Write(ho.Conn)
|
||||||
|
ho.firstRequest = false
|
||||||
|
return len(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ho.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPObfs return a HTTPObfs
|
||||||
|
func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn {
|
||||||
|
return &HTTPObfs{
|
||||||
|
Conn: conn,
|
||||||
|
firstRequest: true,
|
||||||
|
firstResponse: true,
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
}
|
||||||
|
}
|
200
transport/simple-obfs/tls.go
Normal file
200
transport/simple-obfs/tls.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
B "github.com/sagernet/sing/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSObfs is shadowsocks tls simple-obfs implementation
|
||||||
|
type TLSObfs struct {
|
||||||
|
net.Conn
|
||||||
|
server string
|
||||||
|
remain int
|
||||||
|
firstRequest bool
|
||||||
|
firstResponse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
||||||
|
buf := B.Get(discardN)
|
||||||
|
_, err := io.ReadFull(to.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
B.Put(buf)
|
||||||
|
|
||||||
|
sizeBuf := make([]byte, 2)
|
||||||
|
_, err = io.ReadFull(to.Conn, sizeBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
length := int(binary.BigEndian.Uint16(sizeBuf))
|
||||||
|
if length > len(b) {
|
||||||
|
n, err := to.Conn.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
to.remain = length - n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadFull(to.Conn, b[:length])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *TLSObfs) Read(b []byte) (int, error) {
|
||||||
|
if to.remain > 0 {
|
||||||
|
length := to.remain
|
||||||
|
if length > len(b) {
|
||||||
|
length = len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := io.ReadFull(to.Conn, b[:length])
|
||||||
|
to.remain -= n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if to.firstResponse {
|
||||||
|
// type + ver + lensize + 91 = 96
|
||||||
|
// type + ver + lensize + 1 = 6
|
||||||
|
// type + ver = 3
|
||||||
|
to.firstResponse = false
|
||||||
|
return to.read(b, 105)
|
||||||
|
}
|
||||||
|
|
||||||
|
// type + ver = 3
|
||||||
|
return to.read(b, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *TLSObfs) Write(b []byte) (int, error) {
|
||||||
|
length := len(b)
|
||||||
|
for i := 0; i < length; i += chunkSize {
|
||||||
|
end := i + chunkSize
|
||||||
|
if end > length {
|
||||||
|
end = length
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := to.write(b[i:end])
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *TLSObfs) write(b []byte) (int, error) {
|
||||||
|
if to.firstRequest {
|
||||||
|
helloMsg := makeClientHelloMsg(b, to.server)
|
||||||
|
_, err := to.Conn.Write(helloMsg)
|
||||||
|
to.firstRequest = false
|
||||||
|
return len(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := B.NewSize(5 + len(b))
|
||||||
|
defer buf.Release()
|
||||||
|
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||||
|
buf.Write(b)
|
||||||
|
_, err := to.Conn.Write(buf.Bytes())
|
||||||
|
return len(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSObfs return a SimpleObfs
|
||||||
|
func NewTLSObfs(conn net.Conn, server string) net.Conn {
|
||||||
|
return &TLSObfs{
|
||||||
|
Conn: conn,
|
||||||
|
server: server,
|
||||||
|
firstRequest: true,
|
||||||
|
firstResponse: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeClientHelloMsg(data []byte, server string) []byte {
|
||||||
|
random := make([]byte, 28)
|
||||||
|
sessionID := make([]byte, 32)
|
||||||
|
rand.Read(random)
|
||||||
|
rand.Read(sessionID)
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
// handshake, TLS 1.0 version, length
|
||||||
|
buf.WriteByte(22)
|
||||||
|
buf.Write([]byte{0x03, 0x01})
|
||||||
|
length := uint16(212 + len(data) + len(server))
|
||||||
|
buf.WriteByte(byte(length >> 8))
|
||||||
|
buf.WriteByte(byte(length & 0xff))
|
||||||
|
|
||||||
|
// clientHello, length, TLS 1.2 version
|
||||||
|
buf.WriteByte(1)
|
||||||
|
buf.WriteByte(0)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))
|
||||||
|
buf.Write([]byte{0x03, 0x03})
|
||||||
|
|
||||||
|
// random with timestamp, sid len, sid
|
||||||
|
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||||
|
buf.Write(random)
|
||||||
|
buf.WriteByte(32)
|
||||||
|
buf.Write(sessionID)
|
||||||
|
|
||||||
|
// cipher suites
|
||||||
|
buf.Write([]byte{0x00, 0x38})
|
||||||
|
buf.Write([]byte{
|
||||||
|
0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
|
||||||
|
0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a,
|
||||||
|
0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d,
|
||||||
|
0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,
|
||||||
|
})
|
||||||
|
|
||||||
|
// compression
|
||||||
|
buf.Write([]byte{0x01, 0x00})
|
||||||
|
|
||||||
|
// extension length
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))
|
||||||
|
|
||||||
|
// session ticket
|
||||||
|
buf.Write([]byte{0x00, 0x23})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(data)))
|
||||||
|
buf.Write(data)
|
||||||
|
|
||||||
|
// server name
|
||||||
|
buf.Write([]byte{0x00, 0x00})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)+5))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)+3))
|
||||||
|
buf.WriteByte(0)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)))
|
||||||
|
buf.Write([]byte(server))
|
||||||
|
|
||||||
|
// ec_point
|
||||||
|
buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02})
|
||||||
|
|
||||||
|
// groups
|
||||||
|
buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})
|
||||||
|
|
||||||
|
// signature
|
||||||
|
buf.Write([]byte{
|
||||||
|
0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05,
|
||||||
|
0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01,
|
||||||
|
0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
|
||||||
|
})
|
||||||
|
|
||||||
|
// encrypt then mac
|
||||||
|
buf.Write([]byte{0x00, 0x16, 0x00, 0x00})
|
||||||
|
|
||||||
|
// extended master secret
|
||||||
|
buf.Write([]byte{0x00, 0x17, 0x00, 0x00})
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
119
transport/sip003/args.go
Normal file
119
transport/sip003/args.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package sip003
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mod from https://github.com/shadowsocks/v2ray-plugin/blob/master/args.go
|
||||||
|
|
||||||
|
// Args maps a string key to a list of values. It is similar to url.Values.
|
||||||
|
type Args map[string][]string
|
||||||
|
|
||||||
|
// Get the first value associated with the given key. If there are any values
|
||||||
|
// associated with the key, the value return has the value and ok is set to
|
||||||
|
// true. If there are no values for the given key, value is "" and ok is false.
|
||||||
|
// If you need access to multiple values, use the map directly.
|
||||||
|
func (args Args) Get(key string) (value string, ok bool) {
|
||||||
|
if args == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
vals, ok := args[key]
|
||||||
|
if !ok || len(vals) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return vals[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Append value to the list of values for key.
|
||||||
|
func (args Args) Add(key, value string) {
|
||||||
|
args[key] = append(args[key], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the index of the next unescaped byte in s that is in the term set, or
|
||||||
|
// else the length of the string if no terminators appear. Additionally return
|
||||||
|
// the unescaped string up to the returned index.
|
||||||
|
func indexUnescaped(s string, term []byte) (int, string, error) {
|
||||||
|
var i int
|
||||||
|
unesc := make([]byte, 0)
|
||||||
|
for i = 0; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
// A terminator byte?
|
||||||
|
if bytes.IndexByte(term, b) != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b == '\\' {
|
||||||
|
i++
|
||||||
|
if i >= len(s) {
|
||||||
|
return 0, "", fmt.Errorf("nothing following final escape in %q", s)
|
||||||
|
}
|
||||||
|
b = s[i]
|
||||||
|
}
|
||||||
|
unesc = append(unesc, b)
|
||||||
|
}
|
||||||
|
return i, string(unesc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePluginOptions Parse a name–value mapping as from SS_PLUGIN_OPTIONS.
|
||||||
|
//
|
||||||
|
// "<value> is a k=v string value with options that are to be passed to the
|
||||||
|
// transport. semicolons, equal signs and backslashes must be escaped
|
||||||
|
// with a backslash."
|
||||||
|
// Example: secret=nou;cache=/tmp/cache;secret=yes
|
||||||
|
func ParsePluginOptions(s string) (opts Args, err error) {
|
||||||
|
opts = make(Args)
|
||||||
|
if len(s) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
var key, value string
|
||||||
|
var offset, begin int
|
||||||
|
|
||||||
|
if i >= len(s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
begin = i
|
||||||
|
// Read the key.
|
||||||
|
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(key) == 0 {
|
||||||
|
err = fmt.Errorf("empty key in %q", s[begin:i])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i += offset
|
||||||
|
// End of string or no equals sign?
|
||||||
|
if i >= len(s) || s[i] != '=' {
|
||||||
|
opts.Add(key, "1")
|
||||||
|
// Skip the semicolon.
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Skip the equals sign.
|
||||||
|
i++
|
||||||
|
// Read the value.
|
||||||
|
offset, value, err = indexUnescaped(s[i:], []byte{';'})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i += offset
|
||||||
|
opts.Add(key, value)
|
||||||
|
// Skip the semicolon.
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape backslashes and all the bytes that are in set.
|
||||||
|
func backslashEscape(s string, set []byte) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, b := range []byte(s) {
|
||||||
|
if b == '\\' || bytes.IndexByte(set, b) != -1 {
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
}
|
||||||
|
buf.WriteByte(b)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
59
transport/sip003/obfs.go
Normal file
59
transport/sip003/obfs.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package sip003
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/transport/simple-obfs"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Plugin = (*ObfsLocal)(nil)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterPlugin("obfs-local", newObfsLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||||
|
var plugin ObfsLocal
|
||||||
|
mode := "http"
|
||||||
|
if obfsMode, loaded := pluginOpts.Get("obfs"); loaded {
|
||||||
|
mode = obfsMode
|
||||||
|
}
|
||||||
|
if obfsHost, loaded := pluginOpts.Get("obfs-host"); loaded {
|
||||||
|
plugin.host = obfsHost
|
||||||
|
}
|
||||||
|
switch mode {
|
||||||
|
case "http":
|
||||||
|
case "tls":
|
||||||
|
plugin.tls = true
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown obfs mode ", mode)
|
||||||
|
}
|
||||||
|
plugin.port = F.ToString(serverAddr.Port)
|
||||||
|
return &plugin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObfsLocal struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
tls bool
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObfsLocal) DialContext(ctx context.Context) (net.Conn, error) {
|
||||||
|
conn, err := o.dialer.DialContext(ctx, N.NetworkTCP, o.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !o.tls {
|
||||||
|
return obfs.NewHTTPObfs(conn, o.host, o.port), nil
|
||||||
|
} else {
|
||||||
|
return obfs.NewTLSObfs(conn, o.host), nil
|
||||||
|
}
|
||||||
|
}
|
35
transport/sip003/plugin.go
Normal file
35
transport/sip003/plugin.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package sip003
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PluginConstructor func(pluginArgs Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error)
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
DialContext(ctx context.Context) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var plugins map[string]PluginConstructor
|
||||||
|
|
||||||
|
func RegisterPlugin(name string, constructor PluginConstructor) {
|
||||||
|
plugins[name] = constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePlugin(name string, pluginArgs string, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||||
|
pluginOptions, err := ParsePluginOptions(pluginArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse plugin_opts")
|
||||||
|
}
|
||||||
|
constructor, loaded := plugins[name]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("plugin not found: ", name)
|
||||||
|
}
|
||||||
|
return constructor(pluginOptions, router, dialer, serverAddr)
|
||||||
|
}
|
80
transport/sip003/v2ray.go
Normal file
80
transport/sip003/v2ray.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package sip003
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/transport/v2ray"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterPlugin("v2ray-plugin", newV2RayPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||||
|
var tlsOptions option.OutboundTLSOptions
|
||||||
|
if _, loaded := pluginOpts.Get("tls"); loaded {
|
||||||
|
tlsOptions.Enabled = true
|
||||||
|
}
|
||||||
|
if certPath, certLoaded := pluginOpts.Get("cert"); certLoaded {
|
||||||
|
tlsOptions.CertificatePath = certPath
|
||||||
|
}
|
||||||
|
if certRaw, certLoaded := pluginOpts.Get("certRaw"); certLoaded {
|
||||||
|
certHead := "-----BEGIN CERTIFICATE-----"
|
||||||
|
certTail := "-----END CERTIFICATE-----"
|
||||||
|
fixedCert := certHead + "\n" + certRaw + "\n" + certTail
|
||||||
|
tlsOptions.Certificate = fixedCert
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := "websocket"
|
||||||
|
if modeOpt, loaded := pluginOpts.Get("mode"); loaded {
|
||||||
|
mode = modeOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
host := "cloudfront.com"
|
||||||
|
path := "/"
|
||||||
|
|
||||||
|
if hostOpt, loaded := pluginOpts.Get("host"); loaded {
|
||||||
|
host = hostOpt
|
||||||
|
}
|
||||||
|
if pathOpt, loaded := pluginOpts.Get("path"); loaded {
|
||||||
|
path = pathOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsClient tls.Config
|
||||||
|
var err error
|
||||||
|
if tlsOptions.Enabled {
|
||||||
|
tlsClient, err = tls.NewClient(router, serverAddr.AddrString(), tlsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var transportOptions option.V2RayTransportOptions
|
||||||
|
switch mode {
|
||||||
|
case "websocket":
|
||||||
|
transportOptions = option.V2RayTransportOptions{
|
||||||
|
Type: C.V2RayTransportTypeWebsocket,
|
||||||
|
WebsocketOptions: option.V2RayWebsocketOptions{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Host": host,
|
||||||
|
},
|
||||||
|
Path: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case "quic":
|
||||||
|
transportOptions = option.V2RayTransportOptions{
|
||||||
|
Type: C.V2RayTransportTypeQUIC,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("v2ray-plugin: unknown mode: " + mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)
|
||||||
|
}
|
Loading…
Reference in a new issue