mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-24 17:41:29 +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
|
||||
Method string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
UoT bool `json:"udp_over_tcp,omitempty"`
|
||||
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/sip003"
|
||||
"github.com/sagernet/sing-shadowsocks"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||
"github.com/sagernet/sing/common"
|
||||
|
@ -27,6 +28,7 @@ type Shadowsocks struct {
|
|||
dialer N.Dialer
|
||||
method shadowsocks.Method
|
||||
serverAddr M.Socksaddr
|
||||
plugin sip003.Plugin
|
||||
uot bool
|
||||
multiplexDialer N.Dialer
|
||||
}
|
||||
|
@ -49,6 +51,12 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||
serverAddr: options.ServerOptions.Build(),
|
||||
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 {
|
||||
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
||||
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) {
|
||||
switch N.NetworkName(network) {
|
||||
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 {
|
||||
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