mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-12 08:49:53 +07:00
feat/refactor: refactor outbound and support v2ray-plugin (#390)
This commit is contained in:
@ -6,45 +6,14 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
)
|
||||
|
||||
type blockDialer struct {
|
||||
DialCallback func()
|
||||
}
|
||||
|
||||
func (d *blockDialer) Dial(network, addr string) (c netproxy.Conn, err error) {
|
||||
magicNetwork, err := netproxy.ParseMagicNetwork(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch magicNetwork.Network {
|
||||
case "tcp":
|
||||
return d.DialTcp(addr)
|
||||
case "udp":
|
||||
return d.DialUdp(addr)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *blockDialer) DialTcp(addr string) (c netproxy.Conn, err error) {
|
||||
d.DialCallback()
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
func (d *blockDialer) DialUdp(addr string) (c netproxy.PacketConn, err error) {
|
||||
d.DialCallback()
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
|
||||
func NewBlockDialer(option *GlobalOption, dialCallback func()) (netproxy.Dialer, *Property) {
|
||||
return &blockDialer{DialCallback: dialCallback}, &Property{
|
||||
Name: "block",
|
||||
Address: "",
|
||||
Protocol: "",
|
||||
Link: "",
|
||||
d, _p := D.NewBlockDialer(&option.ExtraOption, dialCallback)
|
||||
return d, &Property{
|
||||
Property: *_p,
|
||||
SubscriptionTag: "",
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -32,15 +33,13 @@ type Dialer struct {
|
||||
}
|
||||
|
||||
type GlobalOption struct {
|
||||
D.ExtraOption
|
||||
Log *logrus.Logger
|
||||
TcpCheckOptionRaw TcpCheckOptionRaw // Lazy parse
|
||||
CheckDnsOptionRaw CheckDnsOptionRaw // Lazy parse
|
||||
CheckInterval time.Duration
|
||||
CheckTolerance time.Duration
|
||||
CheckDnsTcp bool
|
||||
AllowInsecure bool
|
||||
TlsImplementation string
|
||||
UtlsImitate string
|
||||
}
|
||||
|
||||
type InstanceOption struct {
|
||||
@ -48,10 +47,7 @@ type InstanceOption struct {
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Name string
|
||||
Address string
|
||||
Protocol string
|
||||
Link string
|
||||
D.Property
|
||||
SubscriptionTag string
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,14 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
softwindDirect "github.com/daeuniverse/softwind/protocol/direct"
|
||||
)
|
||||
|
||||
func NewDirectDialer(option *GlobalOption, fullcone bool) (netproxy.Dialer, *Property) {
|
||||
property := &Property{
|
||||
Name: "direct",
|
||||
Address: "",
|
||||
Protocol: "",
|
||||
Link: "",
|
||||
}
|
||||
if fullcone {
|
||||
return softwindDirect.FullconeDirect, property
|
||||
} else {
|
||||
return softwindDirect.SymmetricDirect, property
|
||||
d, _p := D.NewDirectDialer(&option.ExtraOption, fullcone)
|
||||
return d, &Property{
|
||||
Property: *_p,
|
||||
SubscriptionTag: "",
|
||||
}
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("http", NewHTTP)
|
||||
dialer.FromLinkRegister("https", NewHTTP)
|
||||
}
|
||||
|
||||
type HTTP struct {
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SNI string `json:"sni"`
|
||||
Protocol string `json:"protocol"`
|
||||
AllowInsecure bool `json:"allowInsecure"`
|
||||
}
|
||||
|
||||
func NewHTTP(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseHTTPURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err)
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func ParseHTTPURL(link string) (data *HTTP, err error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err)
|
||||
}
|
||||
pwd, _ := u.User.Password()
|
||||
strPort := u.Port()
|
||||
if strPort == "" {
|
||||
if u.Scheme == "http" {
|
||||
strPort = "80"
|
||||
} else if u.Scheme == "https" {
|
||||
strPort = "443"
|
||||
}
|
||||
}
|
||||
port, err := strconv.Atoi(strPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when parsing port: %w", err)
|
||||
}
|
||||
allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify"))
|
||||
}
|
||||
return &HTTP{
|
||||
Name: u.Fragment,
|
||||
Server: u.Hostname(),
|
||||
Port: port,
|
||||
Username: u.User.Username(),
|
||||
Password: pwd,
|
||||
SNI: u.Query().Get("sni"),
|
||||
Protocol: u.Scheme,
|
||||
AllowInsecure: allowInsecure,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *HTTP) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
u := s.URL()
|
||||
d, err := http.NewHTTPProxy(&u, nextDialer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: u.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *HTTP) URL() url.URL {
|
||||
u := url.URL{
|
||||
Scheme: s.Protocol,
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Fragment: s.Name,
|
||||
}
|
||||
if s.SNI != "" {
|
||||
u.RawQuery = url.Values{"sni": []string{s.SNI}, "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}}.Encode()
|
||||
}
|
||||
if s.Username != "" {
|
||||
if s.Password != "" {
|
||||
u.User = url.UserPassword(s.Username, s.Password)
|
||||
} else {
|
||||
u.User = url.User(s.Username)
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *HTTP) ExportToURL() string {
|
||||
u := s.URL()
|
||||
return u.String()
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package juicity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("juicity", NewJuicity)
|
||||
}
|
||||
|
||||
type Juicity struct {
|
||||
Name string
|
||||
Server string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Sni string
|
||||
AllowInsecure bool
|
||||
CongestionControl string
|
||||
PinnedCertchainSha256 string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func NewJuicity(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseJuicityURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *Juicity) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
d := nextDialer
|
||||
var err error
|
||||
var flags protocol.Flags
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{"h3"},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ServerName: s.Sni,
|
||||
InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure,
|
||||
}
|
||||
if s.PinnedCertchainSha256 != "" {
|
||||
pinnedHash, err := base64.URLEncoding.DecodeString(s.PinnedCertchainSha256)
|
||||
if err != nil {
|
||||
pinnedHash, err = base64.StdEncoding.DecodeString(s.PinnedCertchainSha256)
|
||||
if err != nil {
|
||||
pinnedHash, err = hex.DecodeString(s.PinnedCertchainSha256)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to decode PinnedCertchainSha256")
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
if !bytes.Equal(common.GenerateCertChainHash(rawCerts), pinnedHash) {
|
||||
return fmt.Errorf("pinned hash of cert chain does not match")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if d, err = protocol.NewDialer("juicity", d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Feature1: s.CongestionControl,
|
||||
TlsConfig: tlsConfig,
|
||||
User: s.User,
|
||||
Password: s.Password,
|
||||
IsClient: true,
|
||||
Flags: flags,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseJuicityURL(u string) (data *Juicity, err error) {
|
||||
//trojan://password@server:port#escape(remarks)
|
||||
t, err := url.Parse(u)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid trojan format")
|
||||
return
|
||||
}
|
||||
allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify"))
|
||||
}
|
||||
sni := t.Query().Get("peer")
|
||||
if sni == "" {
|
||||
sni = t.Query().Get("sni")
|
||||
}
|
||||
if sni == "" {
|
||||
sni = t.Hostname()
|
||||
}
|
||||
port, err := strconv.Atoi(t.Port())
|
||||
if err != nil {
|
||||
return nil, dialer.InvalidParameterErr
|
||||
}
|
||||
password, _ := t.User.Password()
|
||||
data = &Juicity{
|
||||
Name: t.Fragment,
|
||||
Server: t.Hostname(),
|
||||
Port: port,
|
||||
User: t.User.Username(),
|
||||
Password: password,
|
||||
Sni: sni,
|
||||
AllowInsecure: allowInsecure,
|
||||
CongestionControl: t.Query().Get("congestion_control"),
|
||||
PinnedCertchainSha256: t.Query().Get("pinned_certchain_sha256"),
|
||||
Protocol: "juicity",
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t *Juicity) ExportToURL() string {
|
||||
u := &url.URL{
|
||||
Scheme: "juicity",
|
||||
User: url.UserPassword(t.User, t.Password),
|
||||
Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)),
|
||||
Fragment: t.Name,
|
||||
}
|
||||
q := u.Query()
|
||||
if t.AllowInsecure {
|
||||
q.Set("allow_insecure", "1")
|
||||
}
|
||||
common.SetValue(&q, "sni", t.Sni)
|
||||
common.SetValue(&q, "congestion_control", t.CongestionControl)
|
||||
common.SetValue(&q, "pinned_certchain_sha256", t.PinnedCertchainSha256)
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
@ -6,69 +6,18 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/protocol/direct"
|
||||
)
|
||||
|
||||
type FromLinkCreator func(gOption *GlobalOption, nextDialer netproxy.Dialer, link string) (dialer netproxy.Dialer, property *Property, err error)
|
||||
|
||||
var fromLinkCreators = make(map[string]FromLinkCreator)
|
||||
|
||||
func FromLinkRegister(name string, creator FromLinkCreator) {
|
||||
fromLinkCreators[name] = creator
|
||||
}
|
||||
|
||||
func NewFromLink(gOption *GlobalOption, iOption InstanceOption, link string, subscriptionTag string) (*Dialer, error) {
|
||||
/// Get overwritten name.
|
||||
overwrittenName, linklike := common.GetTagFromLinkLikePlaintext(link)
|
||||
links := strings.Split(linklike, "->")
|
||||
d := direct.SymmetricDirect
|
||||
p := &Property{
|
||||
Name: "",
|
||||
Address: "",
|
||||
Protocol: "",
|
||||
Link: link,
|
||||
SubscriptionTag: subscriptionTag,
|
||||
}
|
||||
for i := len(links) - 1; i >= 0; i-- {
|
||||
link := strings.TrimSpace(links[i])
|
||||
u, err := url.Parse(link)
|
||||
d, _p, err := D.NewNetproxyDialerFromLink(direct.SymmetricDirect, &gOption.ExtraOption, link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creator, ok := fromLinkCreators[u.Scheme]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected link type: %v", u.Scheme)
|
||||
p := Property{
|
||||
Property: *_p,
|
||||
SubscriptionTag: subscriptionTag,
|
||||
}
|
||||
var _property *Property
|
||||
d, _property, err = creator(gOption, d, link)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create %v: %w", link, err)
|
||||
}
|
||||
if p.Name == "" {
|
||||
p.Name = _property.Name
|
||||
} else {
|
||||
p.Name = _property.Name + "->" + p.Name
|
||||
}
|
||||
if p.Protocol == "" {
|
||||
p.Protocol = _property.Protocol
|
||||
} else {
|
||||
p.Protocol = _property.Protocol + "->" + p.Protocol
|
||||
}
|
||||
if p.Address == "" {
|
||||
p.Address = _property.Address
|
||||
} else {
|
||||
p.Address = _property.Address + "->" + p.Address
|
||||
}
|
||||
}
|
||||
if overwrittenName != "" {
|
||||
p.Name = overwrittenName
|
||||
}
|
||||
node := NewDialer(d, gOption, iOption, p)
|
||||
return node, nil
|
||||
return NewDialer(d, gOption, iOption, &p), nil
|
||||
}
|
||||
|
@ -1,250 +0,0 @@
|
||||
package shadowsocks
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/dae/component/outbound/transport/simpleobfs"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
"github.com/daeuniverse/softwind/protocol/shadowsocks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Use random salt by default to decrease the boot time
|
||||
shadowsocks.DefaultSaltGeneratorType = shadowsocks.RandomSaltGeneratorType
|
||||
|
||||
dialer.FromLinkRegister("shadowsocks", NewShadowsocksFromLink)
|
||||
dialer.FromLinkRegister("ss", NewShadowsocksFromLink)
|
||||
}
|
||||
|
||||
type Shadowsocks struct {
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
Password string `json:"password"`
|
||||
Cipher string `json:"cipher"`
|
||||
Plugin Sip003 `json:"plugin"`
|
||||
UDP bool `json:"udp"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func NewShadowsocksFromLink(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (npd netproxy.Dialer, property *dialer.Property, err error) {
|
||||
s, err := ParseSSURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
var err error
|
||||
d := nextDialer
|
||||
switch s.Plugin.Name {
|
||||
case "simple-obfs":
|
||||
switch s.Plugin.Opts.Obfs {
|
||||
case "http", "tls":
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported obfs %v of plugin %v", s.Plugin.Opts.Obfs, s.Plugin.Name)
|
||||
}
|
||||
host := s.Plugin.Opts.Host
|
||||
if host == "" {
|
||||
host = "cloudflare.com"
|
||||
}
|
||||
path := s.Plugin.Opts.Path
|
||||
uSimpleObfs := url.URL{
|
||||
Scheme: "simple-obfs",
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
RawQuery: url.Values{
|
||||
"obfs": []string{s.Plugin.Opts.Obfs},
|
||||
"host": []string{host},
|
||||
"uri": []string{path},
|
||||
}.Encode(),
|
||||
}
|
||||
d, _, err = simpleobfs.NewSimpleObfs(option, d, uSimpleObfs.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
}
|
||||
var nextDialerName string
|
||||
switch s.Cipher {
|
||||
case "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305":
|
||||
nextDialerName = "shadowsocks"
|
||||
case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "des-cfb", "bf-cfb", "cast5-cfb", "rc4-md5", "rc4-md5-6", "chacha20", "chacha20-ietf", "salsa20", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "rc4", "none", "plain":
|
||||
nextDialerName = "shadowsocks_stream"
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported shadowsocks encryption method: %v", s.Cipher)
|
||||
}
|
||||
d, err = protocol.NewDialer(nextDialerName, d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Cipher: s.Cipher,
|
||||
Password: s.Password,
|
||||
IsClient: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseSSURL(u string) (data *Shadowsocks, err error) {
|
||||
// parse attempts to parse ss:// links
|
||||
parse := func(content string) (v *Shadowsocks, ok bool) {
|
||||
// try to parse in the format of ss://BASE64(method:password)@server:port/?plugin=xxxx#name
|
||||
u, err := url.Parse(content)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
username := u.User.String()
|
||||
username, _ = common.Base64UrlDecode(username)
|
||||
arr := strings.SplitN(username, ":", 2)
|
||||
if len(arr) != 2 {
|
||||
return nil, false
|
||||
}
|
||||
cipher := arr[0]
|
||||
password := arr[1]
|
||||
var sip003 Sip003
|
||||
plugin := u.Query().Get("plugin")
|
||||
if len(plugin) > 0 {
|
||||
sip003 = ParseSip003(plugin)
|
||||
}
|
||||
port, err := strconv.Atoi(u.Port())
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &Shadowsocks{
|
||||
Cipher: strings.ToLower(cipher),
|
||||
Password: password,
|
||||
Server: u.Hostname(),
|
||||
Port: port,
|
||||
Name: u.Fragment,
|
||||
Plugin: sip003,
|
||||
UDP: sip003.Name == "",
|
||||
Protocol: "shadowsocks",
|
||||
}, true
|
||||
}
|
||||
var (
|
||||
v *Shadowsocks
|
||||
ok bool
|
||||
)
|
||||
content := u
|
||||
// try to parse the ss:// link, if it fails, base64 decode first
|
||||
if v, ok = parse(content); !ok {
|
||||
// 进行base64解码,并unmarshal到VmessInfo上
|
||||
t := content[5:]
|
||||
var l, r string
|
||||
if ind := strings.Index(t, "#"); ind > -1 {
|
||||
l = t[:ind]
|
||||
r = t[ind+1:]
|
||||
} else {
|
||||
l = t
|
||||
}
|
||||
l, err = common.Base64StdDecode(l)
|
||||
if err != nil {
|
||||
l, err = common.Base64UrlDecode(l)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
t = "ss://" + l
|
||||
if len(r) > 0 {
|
||||
t += "#" + r
|
||||
}
|
||||
v, ok = parse(t)
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: unrecognized ss address", dialer.InvalidParameterErr)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type Sip003 struct {
|
||||
Name string `json:"name"`
|
||||
Opts Sip003Opts `json:"opts"`
|
||||
}
|
||||
type Sip003Opts struct {
|
||||
Tls string `json:"tls"` // for v2ray-plugin
|
||||
Obfs string `json:"obfs"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"uri"`
|
||||
}
|
||||
|
||||
func ParseSip003Opts(opts string) Sip003Opts {
|
||||
var sip003Opts Sip003Opts
|
||||
fields := strings.Split(opts, ";")
|
||||
for i := range fields {
|
||||
a := strings.Split(fields[i], "=")
|
||||
if len(a) == 1 {
|
||||
// to avoid panic
|
||||
a = append(a, "")
|
||||
}
|
||||
switch a[0] {
|
||||
case "tls":
|
||||
sip003Opts.Tls = "tls"
|
||||
case "obfs", "mode":
|
||||
sip003Opts.Obfs = a[1]
|
||||
case "obfs-path", "obfs-uri", "path":
|
||||
if !strings.HasPrefix(a[1], "/") {
|
||||
a[1] += "/"
|
||||
}
|
||||
sip003Opts.Path = a[1]
|
||||
case "obfs-host", "host":
|
||||
sip003Opts.Host = a[1]
|
||||
}
|
||||
}
|
||||
return sip003Opts
|
||||
}
|
||||
func ParseSip003(plugin string) Sip003 {
|
||||
var sip003 Sip003
|
||||
fields := strings.SplitN(plugin, ";", 2)
|
||||
switch fields[0] {
|
||||
case "obfs-local", "simpleobfs":
|
||||
sip003.Name = "simple-obfs"
|
||||
default:
|
||||
sip003.Name = fields[0]
|
||||
}
|
||||
sip003.Opts = ParseSip003Opts(fields[1])
|
||||
return sip003
|
||||
}
|
||||
|
||||
func (s *Sip003) String() string {
|
||||
list := []string{s.Name}
|
||||
if s.Opts.Obfs != "" {
|
||||
list = append(list, "obfs="+s.Opts.Obfs)
|
||||
}
|
||||
if s.Opts.Host != "" {
|
||||
list = append(list, "obfs-host="+s.Opts.Host)
|
||||
}
|
||||
if s.Opts.Path != "" {
|
||||
list = append(list, "obfs-uri="+s.Opts.Path)
|
||||
}
|
||||
return strings.Join(list, ";")
|
||||
}
|
||||
|
||||
func (s *Shadowsocks) ExportToURL() string {
|
||||
// sip002
|
||||
u := &url.URL{
|
||||
Scheme: "ss",
|
||||
User: url.User(strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte(s.Cipher+":"+s.Password)), "=")),
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Fragment: s.Name,
|
||||
}
|
||||
if s.Plugin.Name != "" {
|
||||
q := u.Query()
|
||||
q.Set("plugin", s.Plugin.String())
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
return u.String()
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package shadowsocksr
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
"github.com/daeuniverse/softwind/protocol/shadowsocks_stream"
|
||||
"github.com/daeuniverse/softwind/transport/shadowsocksr/obfs"
|
||||
"github.com/daeuniverse/softwind/transport/shadowsocksr/proto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("shadowsocksr", NewShadowsocksR)
|
||||
dialer.FromLinkRegister("ssr", NewShadowsocksR)
|
||||
}
|
||||
|
||||
type ShadowsocksR struct {
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
Password string `json:"password"`
|
||||
Cipher string `json:"cipher"`
|
||||
Proto string `json:"proto"`
|
||||
ProtoParam string `json:"protoParam"`
|
||||
Obfs string `json:"obfs"`
|
||||
ObfsParam string `json:"obfsParam"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func NewShadowsocksR(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseSSRURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
d := nextDialer
|
||||
obfsDialer, err := obfs.NewDialer(d, &obfs.ObfsParam{
|
||||
ObfsHost: s.Server,
|
||||
ObfsPort: uint16(s.Port),
|
||||
Obfs: s.Obfs,
|
||||
ObfsParam: s.ObfsParam,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
d = obfsDialer
|
||||
d, err = shadowsocks_stream.NewDialer(d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Cipher: s.Cipher,
|
||||
Password: s.Password,
|
||||
IsClient: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
d = &proto.Dialer{
|
||||
NextDialer: d,
|
||||
Protocol: s.Proto,
|
||||
ProtocolParam: s.ProtoParam,
|
||||
ObfsOverhead: obfsDialer.ObfsOverhead(),
|
||||
}
|
||||
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseSSRURL(u string) (data *ShadowsocksR, err error) {
|
||||
// parse attempts to parse ss:// links
|
||||
parse := func(content string) (v ShadowsocksR, ok bool) {
|
||||
arr := strings.Split(content, "/?")
|
||||
if strings.Contains(content, ":") && len(arr) < 2 {
|
||||
content += "/?remarks=&protoparam=&obfsparam="
|
||||
arr = strings.Split(content, "/?")
|
||||
} else if len(arr) != 2 {
|
||||
return v, false
|
||||
}
|
||||
pre := strings.Split(arr[0], ":")
|
||||
if len(pre) > 6 {
|
||||
//if the length is more than 6, it means that the host contains the characters:,
|
||||
//re-merge the first few groups into the host
|
||||
pre[len(pre)-6] = strings.Join(pre[:len(pre)-5], ":")
|
||||
pre = pre[len(pre)-6:]
|
||||
} else if len(pre) < 6 {
|
||||
return v, false
|
||||
}
|
||||
q, err := url.ParseQuery(arr[1])
|
||||
if err != nil {
|
||||
return v, false
|
||||
}
|
||||
pswd, _ := common.Base64UrlDecode(pre[5])
|
||||
add, _ := common.Base64UrlDecode(pre[0])
|
||||
remarks, _ := common.Base64UrlDecode(q.Get("remarks"))
|
||||
protoparam, _ := common.Base64UrlDecode(q.Get("protoparam"))
|
||||
obfsparam, _ := common.Base64UrlDecode(q.Get("obfsparam"))
|
||||
port, err := strconv.Atoi(pre[1])
|
||||
if err != nil {
|
||||
return v, false
|
||||
}
|
||||
v = ShadowsocksR{
|
||||
Name: remarks,
|
||||
Server: add,
|
||||
Port: port,
|
||||
Password: pswd,
|
||||
Cipher: pre[3],
|
||||
Proto: pre[2],
|
||||
ProtoParam: protoparam,
|
||||
Obfs: pre[4],
|
||||
ObfsParam: obfsparam,
|
||||
Protocol: "shadowsocksr",
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
content := u[6:]
|
||||
var (
|
||||
info ShadowsocksR
|
||||
ok bool
|
||||
)
|
||||
// try parsing the ssr:// link, if it fails, base64 decode first
|
||||
if info, ok = parse(content); !ok {
|
||||
// perform base64 decoding and parse again
|
||||
content, err = common.Base64StdDecode(content)
|
||||
if err != nil {
|
||||
content, err = common.Base64UrlDecode(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
info, ok = parse(content)
|
||||
}
|
||||
if !ok {
|
||||
err = fmt.Errorf("%w: unrecognized ssr address", dialer.InvalidParameterErr)
|
||||
return
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *ShadowsocksR) ExportToURL() string {
|
||||
/* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */
|
||||
return fmt.Sprintf("ssr://%v", strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte(
|
||||
fmt.Sprintf(
|
||||
"%v:%v:%v:%v:%v/?remarks=%v&protoparam=%v&obfsparam=%v",
|
||||
net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
s.Proto,
|
||||
s.Cipher,
|
||||
s.Obfs,
|
||||
base64.URLEncoding.EncodeToString([]byte(s.Password)),
|
||||
base64.URLEncoding.EncodeToString([]byte(s.Name)),
|
||||
base64.URLEncoding.EncodeToString([]byte(s.ProtoParam)),
|
||||
base64.URLEncoding.EncodeToString([]byte(s.ObfsParam)),
|
||||
),
|
||||
)), "="))
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol/socks5"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("socks", NewSocks) // socks -> socks5
|
||||
//dialer.FromLinkRegister("socks4", NewSocks)
|
||||
//dialer.FromLinkRegister("socks4a", NewSocks)
|
||||
dialer.FromLinkRegister("socks5", NewSocks)
|
||||
}
|
||||
|
||||
type Socks struct {
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func NewSocks(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseSocksURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, dialer.InvalidParameterErr
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *Socks) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
link := s.ExportToURL()
|
||||
d := nextDialer
|
||||
switch s.Protocol {
|
||||
case "", "socks", "socks5":
|
||||
d, err := socks5.NewSocks5Dialer(link, d) // Socks5 Proxy supports full-cone.
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: link,
|
||||
}, nil
|
||||
//case "socks4", "socks4a":
|
||||
// d, err := socks4.NewSocks4Dialer(link, &proxy.Direct{})
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return dialer.NewDialer(d, false, s.Name, s.Protocol, link), nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unexpected protocol: %v", s.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseSocksURL(link string) (data *Socks, err error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, dialer.InvalidParameterErr
|
||||
}
|
||||
pwd, _ := u.User.Password()
|
||||
strPort := u.Port()
|
||||
port, err := strconv.Atoi(strPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// socks -> socks5
|
||||
if u.Scheme == "socks" {
|
||||
u.Scheme = "socks5"
|
||||
}
|
||||
return &Socks{
|
||||
Name: u.Fragment,
|
||||
Server: u.Hostname(),
|
||||
Port: port,
|
||||
Username: u.User.Username(),
|
||||
Password: pwd,
|
||||
Protocol: u.Scheme,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Socks) ExportToURL() string {
|
||||
var user *url.Userinfo
|
||||
if s.Password != "" {
|
||||
user = url.UserPassword(s.Username, s.Password)
|
||||
} else {
|
||||
user = url.User(s.Username)
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: s.Protocol,
|
||||
User: user,
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Fragment: s.Name,
|
||||
}
|
||||
return u.String()
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2023, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package socks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/common/netutils"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/protocol/direct"
|
||||
dnsmessage "github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestSocks5(t *testing.T) {
|
||||
c, err := ParseSocksURL("socks5://192.168.31.6:1081")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log := logrus.StandardLogger()
|
||||
d, _, err := c.Dialer(&dialer.GlobalOption{
|
||||
Log: log,
|
||||
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{
|
||||
Log: log,
|
||||
Raw: []string{"http://gstatic.com/generate_204"},
|
||||
ResolverNetwork: "udp",
|
||||
Method: "HEAD",
|
||||
},
|
||||
CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{
|
||||
Raw: []string{"dns.google.com:53"},
|
||||
ResolverNetwork: "udp",
|
||||
},
|
||||
CheckInterval: 10 * time.Second,
|
||||
CheckTolerance: 0,
|
||||
CheckDnsTcp: true,
|
||||
AllowInsecure: false,
|
||||
TlsImplementation: "",
|
||||
UtlsImitate: "",
|
||||
}, direct.SymmetricDirect)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
addrs, err := netutils.ResolveNetip(ctx, d, netip.MustParseAddrPort("8.8.8.8:53"), "apple.com", dnsmessage.TypeA, "udp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(addrs)
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
package trojan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/transport/tls"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/dae/component/outbound/transport/ws"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
"github.com/daeuniverse/softwind/transport/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("trojan", NewTrojan)
|
||||
dialer.FromLinkRegister("trojan-go", NewTrojan)
|
||||
}
|
||||
|
||||
type Trojan struct {
|
||||
Name string `json:"name"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
Password string `json:"password"`
|
||||
Sni string `json:"sni"`
|
||||
Type string `json:"type"`
|
||||
Encryption string `json:"encryption"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
AllowInsecure bool `json:"allowInsecure"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func NewTrojan(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseTrojanURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *Trojan) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
d := nextDialer
|
||||
var err error
|
||||
if s.Type != "grpc" {
|
||||
// grpc contains tls
|
||||
u := url.URL{
|
||||
Scheme: option.TlsImplementation,
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
RawQuery: url.Values{
|
||||
"sni": []string{s.Sni},
|
||||
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
|
||||
"utlsImitate": []string{option.UtlsImitate},
|
||||
}.Encode(),
|
||||
}
|
||||
if d, _, err = tls.NewTls(option, d, u.String()); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
// "tls,ws,ss,trojanc"
|
||||
switch s.Type {
|
||||
case "ws":
|
||||
u := url.URL{
|
||||
Scheme: "ws",
|
||||
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
RawQuery: url.Values{
|
||||
"host": []string{s.Host},
|
||||
"path": []string{s.Path},
|
||||
}.Encode(),
|
||||
}
|
||||
if d, _, err = ws.NewWs(option, d, u.String()); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "grpc":
|
||||
serviceName := s.ServiceName
|
||||
if serviceName == "" {
|
||||
serviceName = "GunService"
|
||||
}
|
||||
d = &grpc.Dialer{
|
||||
NextDialer: &netproxy.ContextDialerConverter{Dialer: d},
|
||||
ServiceName: serviceName,
|
||||
ServerName: s.Sni,
|
||||
AllowInsecure: s.AllowInsecure || option.AllowInsecure,
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(s.Encryption, "ss;") {
|
||||
fields := strings.SplitN(s.Encryption, ";", 3)
|
||||
if d, err = protocol.NewDialer("shadowsocks", d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Cipher: fields[1],
|
||||
Password: fields[2],
|
||||
IsClient: false,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if d, err = protocol.NewDialer("trojanc", d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Password: s.Password,
|
||||
IsClient: true,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseTrojanURL(u string) (data *Trojan, err error) {
|
||||
//trojan://password@server:port#escape(remarks)
|
||||
t, err := url.Parse(u)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid trojan format")
|
||||
return
|
||||
}
|
||||
allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify"))
|
||||
}
|
||||
sni := t.Query().Get("peer")
|
||||
if sni == "" {
|
||||
sni = t.Query().Get("sni")
|
||||
}
|
||||
if sni == "" {
|
||||
sni = t.Hostname()
|
||||
}
|
||||
port, err := strconv.Atoi(t.Port())
|
||||
if err != nil {
|
||||
return nil, dialer.InvalidParameterErr
|
||||
}
|
||||
data = &Trojan{
|
||||
Name: t.Fragment,
|
||||
Server: t.Hostname(),
|
||||
Port: port,
|
||||
Password: t.User.Username(),
|
||||
Sni: sni,
|
||||
AllowInsecure: allowInsecure,
|
||||
Protocol: "trojan",
|
||||
}
|
||||
if t.Query().Get("type") != "" {
|
||||
t.Scheme = "trojan-go"
|
||||
}
|
||||
if t.Scheme == "trojan-go" {
|
||||
data.Protocol = "trojan-go"
|
||||
data.Encryption = t.Query().Get("encryption")
|
||||
data.Host = t.Query().Get("host")
|
||||
data.Path = t.Query().Get("path")
|
||||
data.Type = t.Query().Get("type")
|
||||
data.ServiceName = t.Query().Get("serviceName")
|
||||
if data.Type == "grpc" && data.ServiceName == "" {
|
||||
data.ServiceName = data.Path
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t *Trojan) ExportToURL() string {
|
||||
u := &url.URL{
|
||||
Scheme: "trojan",
|
||||
User: url.User(t.Password),
|
||||
Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)),
|
||||
Fragment: t.Name,
|
||||
}
|
||||
q := u.Query()
|
||||
if t.AllowInsecure {
|
||||
q.Set("allowInsecure", "1")
|
||||
}
|
||||
common.SetValue(&q, "sni", t.Sni)
|
||||
|
||||
if t.Protocol == "trojan-go" {
|
||||
u.Scheme = "trojan-go"
|
||||
common.SetValue(&q, "host", t.Host)
|
||||
common.SetValue(&q, "encryption", t.Encryption)
|
||||
common.SetValue(&q, "type", t.Type)
|
||||
common.SetValue(&q, "path", t.Path)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
package tuic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("tuic", NewTuic)
|
||||
}
|
||||
|
||||
type Tuic struct {
|
||||
Name string
|
||||
Server string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Sni string
|
||||
AllowInsecure bool
|
||||
DisableSni bool
|
||||
CongestionControl string
|
||||
Alpn []string
|
||||
Protocol string
|
||||
UdpRelayMode string
|
||||
}
|
||||
|
||||
func NewTuic(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
s, err := ParseTuicURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *Tuic) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
|
||||
d := nextDialer
|
||||
var err error
|
||||
var flags protocol.Flags
|
||||
if s.UdpRelayMode == "quic" {
|
||||
flags |= protocol.Flags_Tuic_UdpRelayModeQuic
|
||||
}
|
||||
if d, err = protocol.NewDialer("tuic", d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Feature1: s.CongestionControl,
|
||||
TlsConfig: &tls.Config{
|
||||
NextProtos: s.Alpn,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ServerName: s.Sni,
|
||||
InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure,
|
||||
},
|
||||
User: s.User,
|
||||
Password: s.Password,
|
||||
IsClient: true,
|
||||
Flags: flags,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Name,
|
||||
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseTuicURL(u string) (data *Tuic, err error) {
|
||||
//trojan://password@server:port#escape(remarks)
|
||||
t, err := url.Parse(u)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid trojan format")
|
||||
return
|
||||
}
|
||||
var alpn []string
|
||||
if t.Query().Has("alpn") {
|
||||
alpn = strings.Split(t.Query().Get("alpn"), ",")
|
||||
for i := range alpn {
|
||||
alpn[i] = strings.TrimSpace(alpn[i])
|
||||
}
|
||||
}
|
||||
allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify"))
|
||||
}
|
||||
sni := t.Query().Get("peer")
|
||||
if sni == "" {
|
||||
sni = t.Query().Get("sni")
|
||||
}
|
||||
if sni == "" {
|
||||
sni = t.Hostname()
|
||||
}
|
||||
disableSni, _ := strconv.ParseBool(t.Query().Get("disable_sni"))
|
||||
if disableSni {
|
||||
sni = ""
|
||||
allowInsecure = true
|
||||
}
|
||||
port, err := strconv.Atoi(t.Port())
|
||||
if err != nil {
|
||||
return nil, dialer.InvalidParameterErr
|
||||
}
|
||||
password, _ := t.User.Password()
|
||||
data = &Tuic{
|
||||
Name: t.Fragment,
|
||||
Server: t.Hostname(),
|
||||
Port: port,
|
||||
User: t.User.Username(),
|
||||
Password: password,
|
||||
Sni: sni,
|
||||
AllowInsecure: allowInsecure,
|
||||
DisableSni: disableSni,
|
||||
CongestionControl: t.Query().Get("congestion_control"),
|
||||
Alpn: alpn,
|
||||
UdpRelayMode: strings.ToLower(t.Query().Get("udp_relay_mode")),
|
||||
Protocol: "tuic",
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t *Tuic) ExportToURL() string {
|
||||
u := &url.URL{
|
||||
Scheme: "tuic",
|
||||
User: url.UserPassword(t.User, t.Password),
|
||||
Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)),
|
||||
Fragment: t.Name,
|
||||
}
|
||||
q := u.Query()
|
||||
if t.AllowInsecure {
|
||||
q.Set("allow_insecure", "1")
|
||||
}
|
||||
common.SetValue(&q, "sni", t.Sni)
|
||||
if t.DisableSni {
|
||||
common.SetValue(&q, "disable_sni", "1")
|
||||
}
|
||||
if t.CongestionControl != "" {
|
||||
common.SetValue(&q, "congestion_control", t.CongestionControl)
|
||||
}
|
||||
if len(t.Alpn) > 0 {
|
||||
common.SetValue(&q, "alpn", strings.Join(t.Alpn, ","))
|
||||
}
|
||||
if t.UdpRelayMode != "" {
|
||||
common.SetValue(&q, "udp_relay_mode", t.UdpRelayMode)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package v2ray
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
||||
)
|
||||
|
||||
func getAutoCipher() string {
|
||||
if hasAESGCMHardwareSupport {
|
||||
return "aes-128-gcm"
|
||||
}
|
||||
return "chacha20-ietf-poly1305"
|
||||
}
|
@ -1,384 +0,0 @@
|
||||
package v2ray
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/dae/component/outbound/transport/tls"
|
||||
"github.com/daeuniverse/dae/component/outbound/transport/ws"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/protocol"
|
||||
"github.com/daeuniverse/softwind/protocol/direct"
|
||||
"github.com/daeuniverse/softwind/protocol/http"
|
||||
"github.com/daeuniverse/softwind/transport/grpc"
|
||||
"github.com/daeuniverse/softwind/transport/meek"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("vmess", NewV2Ray)
|
||||
dialer.FromLinkRegister("vless", NewV2Ray)
|
||||
}
|
||||
|
||||
type V2Ray struct {
|
||||
Ps string `json:"ps"`
|
||||
Add string `json:"add"`
|
||||
Port string `json:"port"`
|
||||
ID string `json:"id"`
|
||||
Aid string `json:"aid"`
|
||||
Net string `json:"net"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
SNI string `json:"sni"`
|
||||
Path string `json:"path"`
|
||||
TLS string `json:"tls"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Alpn string `json:"alpn,omitempty"`
|
||||
AllowInsecure bool `json:"allowInsecure"`
|
||||
V string `json:"v"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
func NewV2Ray(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
var (
|
||||
s *V2Ray
|
||||
err error
|
||||
)
|
||||
switch {
|
||||
case strings.HasPrefix(link, "vmess://"):
|
||||
s, err = ParseVmessURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if s.Aid != "0" && s.Aid != "" {
|
||||
return nil, nil, fmt.Errorf("%w: aid: %v, we only support AEAD encryption", dialer.UnexpectedFieldErr, s.Aid)
|
||||
}
|
||||
case strings.HasPrefix(link, "vless://"):
|
||||
s, err = ParseVlessURL(link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, dialer.InvalidParameterErr
|
||||
}
|
||||
return s.Dialer(option, nextDialer)
|
||||
}
|
||||
|
||||
func (s *V2Ray) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (npd netproxy.Dialer, property *dialer.Property, err error) {
|
||||
d := nextDialer
|
||||
switch s.Protocol {
|
||||
case "vmess", "vless":
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("V2Ray.Dialer: unexpected protocol: %v", s.Protocol)
|
||||
}
|
||||
|
||||
switch strings.ToLower(s.Net) {
|
||||
case "ws":
|
||||
scheme := "ws"
|
||||
if s.TLS == "tls" || s.TLS == "xtls" {
|
||||
scheme = "wss"
|
||||
}
|
||||
sni := s.SNI
|
||||
if sni == "" {
|
||||
sni = s.Host
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: net.JoinHostPort(s.Add, s.Port),
|
||||
Path: s.Path,
|
||||
RawQuery: url.Values{
|
||||
"host": []string{s.Host},
|
||||
"sni": []string{sni},
|
||||
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
|
||||
}.Encode(),
|
||||
}
|
||||
d, _, err = ws.NewWs(option, d, u.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "tcp":
|
||||
if s.TLS == "tls" || s.TLS == "xtls" {
|
||||
sni := s.SNI
|
||||
if sni == "" {
|
||||
sni = s.Host
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: option.TlsImplementation,
|
||||
Host: net.JoinHostPort(s.Add, s.Port),
|
||||
RawQuery: url.Values{
|
||||
"sni": []string{sni},
|
||||
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
|
||||
"utlsImitate": []string{option.UtlsImitate},
|
||||
}.Encode(),
|
||||
}
|
||||
d, _, err = tls.NewTls(option, d, u.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if s.Type != "none" && s.Type != "" {
|
||||
return nil, nil, fmt.Errorf("%w: type: %v", dialer.UnexpectedFieldErr, s.Type)
|
||||
}
|
||||
case "grpc":
|
||||
sni := s.SNI
|
||||
if sni == "" {
|
||||
sni = s.Host
|
||||
}
|
||||
serviceName := s.Path
|
||||
if serviceName == "" {
|
||||
serviceName = "GunService"
|
||||
}
|
||||
d = &grpc.Dialer{
|
||||
NextDialer: &netproxy.ContextDialerConverter{Dialer: d},
|
||||
ServiceName: serviceName,
|
||||
ServerName: sni,
|
||||
AllowInsecure: s.AllowInsecure || option.AllowInsecure,
|
||||
}
|
||||
case "http", "http2", "h2":
|
||||
sni := s.SNI
|
||||
if sni == "" {
|
||||
sni = s.Add
|
||||
}
|
||||
scheme := "http"
|
||||
if s.TLS == "tls" {
|
||||
scheme = "https"
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: net.JoinHostPort(s.Add, s.Port),
|
||||
Path: s.Path,
|
||||
RawQuery: url.Values{
|
||||
"sni": []string{sni},
|
||||
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
|
||||
"tlsImplementation": []string{option.TlsImplementation},
|
||||
"utlsImitate": []string{option.UtlsImitate},
|
||||
"host": []string{s.Host},
|
||||
"alpn": []string{s.Alpn},
|
||||
"transport": []string{"1"},
|
||||
}.Encode(),
|
||||
}
|
||||
d, err = http.NewHTTPProxy(&u, direct.SymmetricDirect)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "meek":
|
||||
if strings.HasPrefix(s.Path, "https://") && s.TLS != "tls" && s.TLS != "utls" {
|
||||
return nil, nil, fmt.Errorf("%w: meek: tls should be enabled", dialer.InvalidParameterErr)
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "meek",
|
||||
Host: net.JoinHostPort(s.Add, s.Port),
|
||||
RawQuery: url.Values{
|
||||
"url": []string{s.Path},
|
||||
"alpn": []string{s.Alpn},
|
||||
"serverName": []string{s.SNI},
|
||||
"skipCertVerify": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
|
||||
}.Encode(),
|
||||
}
|
||||
|
||||
d, err = meek.NewDialer(u.String(), d)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("%w: network: %v", dialer.UnexpectedFieldErr, s.Net)
|
||||
}
|
||||
|
||||
if d, err = protocol.NewDialer(s.Protocol, d, protocol.Header{
|
||||
ProxyAddress: net.JoinHostPort(s.Add, s.Port),
|
||||
Cipher: getAutoCipher(),
|
||||
Password: s.ID,
|
||||
IsClient: true,
|
||||
//Flags: protocol.Flags_VMess_UsePacketAddr,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, &dialer.Property{
|
||||
Name: s.Ps,
|
||||
Address: net.JoinHostPort(s.Add, s.Port),
|
||||
Protocol: s.Protocol,
|
||||
Link: s.ExportToURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseVlessURL(vless string) (data *V2Ray, err error) {
|
||||
u, err := url.Parse(vless)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = &V2Ray{
|
||||
Ps: u.Fragment,
|
||||
Add: u.Hostname(),
|
||||
Port: u.Port(),
|
||||
ID: u.User.String(),
|
||||
Net: u.Query().Get("type"),
|
||||
Type: u.Query().Get("headerType"),
|
||||
SNI: u.Query().Get("sni"),
|
||||
Host: u.Query().Get("host"),
|
||||
Path: u.Query().Get("path"),
|
||||
TLS: u.Query().Get("security"),
|
||||
Flow: u.Query().Get("flow"),
|
||||
Alpn: u.Query().Get("alpn"),
|
||||
Protocol: "vless",
|
||||
}
|
||||
if data.Net == "" {
|
||||
data.Net = "tcp"
|
||||
}
|
||||
if data.Net == "grpc" {
|
||||
data.Path = u.Query().Get("serviceName")
|
||||
}
|
||||
|
||||
if data.Net == "meek" {
|
||||
data.Path = u.Query().Get("url")
|
||||
}
|
||||
|
||||
if data.Type == "" {
|
||||
data.Type = "none"
|
||||
}
|
||||
if data.TLS == "" {
|
||||
data.TLS = "none"
|
||||
}
|
||||
if data.Flow == "" {
|
||||
data.Flow = "xtls-rprx-direct"
|
||||
}
|
||||
if data.Type == "mkcp" || data.Type == "kcp" {
|
||||
data.Path = u.Query().Get("seed")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func ParseVmessURL(vmess string) (data *V2Ray, err error) {
|
||||
var info V2Ray
|
||||
// perform base64 decoding and unmarshal to VmessInfo
|
||||
raw, err := common.Base64StdDecode(vmess[8:])
|
||||
if err != nil {
|
||||
raw, err = common.Base64UrlDecode(vmess[8:])
|
||||
}
|
||||
if err != nil {
|
||||
// not in json format, try to resolve as vmess://BASE64(Security:ID@Add:Port)?remarks=Ps&obfsParam=Host&Path=Path&obfs=Net&tls=TLS
|
||||
var u *url.URL
|
||||
u, err = url.Parse(vmess)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`.*:(.+)@(.+):(\d+)`)
|
||||
s := strings.Split(vmess[8:], "?")[0]
|
||||
s, err = common.Base64StdDecode(s)
|
||||
if err != nil {
|
||||
s, _ = common.Base64UrlDecode(s)
|
||||
}
|
||||
subMatch := re.FindStringSubmatch(s)
|
||||
if subMatch == nil {
|
||||
err = fmt.Errorf("unrecognized vmess address")
|
||||
return
|
||||
}
|
||||
q := u.Query()
|
||||
ps := q.Get("remarks")
|
||||
if ps == "" {
|
||||
ps = q.Get("remark")
|
||||
}
|
||||
obfs := q.Get("obfs")
|
||||
obfsParam := q.Get("obfsParam")
|
||||
path := q.Get("path")
|
||||
if obfs == "kcp" || obfs == "mkcp" {
|
||||
m := make(map[string]string)
|
||||
//cater to v2rayN definition
|
||||
_ = jsoniter.Unmarshal([]byte(obfsParam), &m)
|
||||
path = m["seed"]
|
||||
obfsParam = ""
|
||||
}
|
||||
aid := q.Get("alterId")
|
||||
if aid == "" {
|
||||
aid = q.Get("aid")
|
||||
}
|
||||
sni := q.Get("peer")
|
||||
info = V2Ray{
|
||||
ID: subMatch[1],
|
||||
Add: subMatch[2],
|
||||
Port: subMatch[3],
|
||||
Ps: ps,
|
||||
Host: jsoniter.Get([]byte(obfsParam), "host").ToString(),
|
||||
Path: path,
|
||||
Net: obfs,
|
||||
Aid: aid,
|
||||
TLS: map[string]string{"1": "tls"}[q.Get("tls")],
|
||||
SNI: sni,
|
||||
AllowInsecure: false,
|
||||
}
|
||||
if info.Net == "websocket" {
|
||||
info.Net = "ws"
|
||||
}
|
||||
} else {
|
||||
err = jsoniter.Unmarshal([]byte(raw), &info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// correct the wrong vmess as much as possible
|
||||
if strings.HasPrefix(info.Host, "/") && info.Path == "" {
|
||||
info.Path = info.Host
|
||||
info.Host = ""
|
||||
}
|
||||
if info.Aid == "" {
|
||||
info.Aid = "0"
|
||||
}
|
||||
info.Protocol = "vmess"
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (s *V2Ray) ExportToURL() string {
|
||||
switch s.Protocol {
|
||||
case "vless":
|
||||
// https://github.com/XTLS/Xray-core/issues/91
|
||||
var query = make(url.Values)
|
||||
common.SetValue(&query, "type", s.Net)
|
||||
common.SetValue(&query, "security", s.TLS)
|
||||
switch s.Net {
|
||||
case "websocket", "ws", "http", "h2":
|
||||
common.SetValue(&query, "path", s.Path)
|
||||
common.SetValue(&query, "host", s.Host)
|
||||
case "mkcp", "kcp":
|
||||
common.SetValue(&query, "headerType", s.Type)
|
||||
common.SetValue(&query, "seed", s.Path)
|
||||
case "tcp":
|
||||
common.SetValue(&query, "headerType", s.Type)
|
||||
common.SetValue(&query, "host", s.Host)
|
||||
common.SetValue(&query, "path", s.Path)
|
||||
case "grpc":
|
||||
common.SetValue(&query, "serviceName", s.Path)
|
||||
case "meek":
|
||||
common.SetValue(&query, "url", s.Host)
|
||||
}
|
||||
|
||||
//TODO: QUIC
|
||||
if s.TLS != "none" {
|
||||
common.SetValue(&query, "sni", s.Host) // FIXME: it may be different from ws's host
|
||||
common.SetValue(&query, "alpn", s.Alpn)
|
||||
}
|
||||
if s.TLS == "xtls" {
|
||||
common.SetValue(&query, "flow", s.Flow)
|
||||
}
|
||||
|
||||
U := url.URL{
|
||||
Scheme: "vless",
|
||||
User: url.User(s.ID),
|
||||
Host: net.JoinHostPort(s.Add, s.Port),
|
||||
RawQuery: query.Encode(),
|
||||
Fragment: s.Ps,
|
||||
}
|
||||
return U.String()
|
||||
case "vmess":
|
||||
s.V = "2"
|
||||
b, _ := jsoniter.Marshal(s)
|
||||
return "vmess://" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(b), "=")
|
||||
}
|
||||
//log.Warn("unexpected protocol: %v", v.Protocol)
|
||||
return ""
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/daeuniverse/dae/common/consts"
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
_ "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -28,6 +28,12 @@ var TestNetworkType = &dialer.NetworkType{
|
||||
|
||||
var log = logger.NewLogger("trace", false, nil)
|
||||
|
||||
func newDirectDialer(option *dialer.GlobalOption, fullcone bool) *dialer.Dialer {
|
||||
_d, p := dialer.NewDirectDialer(option, true)
|
||||
d := dialer.NewDialer(_d, option, dialer.InstanceOption{CheckEnabled: false}, p)
|
||||
return d
|
||||
}
|
||||
|
||||
func TestDialerGroup_Select_Fixed(t *testing.T) {
|
||||
option := &dialer.GlobalOption{
|
||||
Log: log,
|
||||
@ -38,11 +44,12 @@ func TestDialerGroup_Select_Fixed(t *testing.T) {
|
||||
CheckDnsTcp: false,
|
||||
}
|
||||
dialers := []*dialer.Dialer{
|
||||
dialer.NewDirectDialer(option, true),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
newDirectDialer(option, true),
|
||||
newDirectDialer(option, false),
|
||||
}
|
||||
fixedIndex := 1
|
||||
g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Fixed,
|
||||
FixedIndex: fixedIndex,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
@ -78,18 +85,19 @@ func TestDialerGroup_Select_MinLastLatency(t *testing.T) {
|
||||
CheckInterval: 15 * time.Second,
|
||||
}
|
||||
dialers := []*dialer.Dialer{
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
}
|
||||
g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_MinLastLatency,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
|
||||
@ -147,13 +155,14 @@ func TestDialerGroup_Select_Random(t *testing.T) {
|
||||
CheckInterval: 15 * time.Second,
|
||||
}
|
||||
dialers := []*dialer.Dialer{
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
}
|
||||
g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Random,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
count := make([]int, len(dialers))
|
||||
@ -186,13 +195,14 @@ func TestDialerGroup_SetAlive(t *testing.T) {
|
||||
CheckInterval: 15 * time.Second,
|
||||
}
|
||||
dialers := []*dialer.Dialer{
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
dialer.NewDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
newDirectDialer(option, false),
|
||||
}
|
||||
g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Random,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
zeroTarget := 3
|
||||
|
@ -6,17 +6,17 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/http"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/juicity"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/shadowsocks"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/shadowsocksr"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/socks"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/trojan"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/tuic"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/dialer/v2ray"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/transport/simpleobfs"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/transport/tls"
|
||||
_ "github.com/daeuniverse/dae/component/outbound/transport/ws"
|
||||
_ "github.com/daeuniverse/outbound/dialer/http"
|
||||
_ "github.com/daeuniverse/outbound/dialer/juicity"
|
||||
_ "github.com/daeuniverse/outbound/dialer/shadowsocks"
|
||||
_ "github.com/daeuniverse/outbound/dialer/shadowsocksr"
|
||||
_ "github.com/daeuniverse/outbound/dialer/socks"
|
||||
_ "github.com/daeuniverse/outbound/dialer/trojan"
|
||||
_ "github.com/daeuniverse/outbound/dialer/tuic"
|
||||
_ "github.com/daeuniverse/outbound/dialer/v2ray"
|
||||
_ "github.com/daeuniverse/outbound/transport/simpleobfs"
|
||||
_ "github.com/daeuniverse/outbound/transport/tls"
|
||||
_ "github.com/daeuniverse/outbound/transport/ws"
|
||||
_ "github.com/daeuniverse/softwind/protocol/juicity"
|
||||
_ "github.com/daeuniverse/softwind/protocol/shadowsocks"
|
||||
_ "github.com/daeuniverse/softwind/protocol/trojanc"
|
||||
|
@ -1,7 +0,0 @@
|
||||
package simpleobfs
|
||||
|
||||
import "github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("simpleobfs", NewSimpleObfs)
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/http.go
|
||||
|
||||
package simpleobfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/pkg/fastrand"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||
type HTTPObfs struct {
|
||||
netproxy.Conn
|
||||
host string
|
||||
port string
|
||||
path string
|
||||
buf []byte
|
||||
offset int
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
wMu sync.Mutex
|
||||
rMu sync.Mutex
|
||||
}
|
||||
|
||||
func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||
ho.rMu.Lock()
|
||||
defer ho.rMu.Unlock()
|
||||
if ho.buf != nil {
|
||||
n := copy(b, ho.buf[ho.offset:])
|
||||
ho.offset += n
|
||||
if ho.offset == len(ho.buf) {
|
||||
pool.Put(ho.buf)
|
||||
ho.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if ho.firstResponse {
|
||||
buf := pool.Get(1 << 15)
|
||||
n, err := ho.Conn.Read(buf)
|
||||
if err != nil {
|
||||
pool.Put(buf)
|
||||
return 0, err
|
||||
}
|
||||
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
pool.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 {
|
||||
pool.Put(buf)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return ho.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||
ho.wMu.Lock()
|
||||
defer ho.wMu.Unlock()
|
||||
if ho.firstRequest {
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s%s", ho.host, ho.path), bytes.NewBuffer(b[:]))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", fastrand.Rand().Int()%87, fastrand.Rand().Int()%2))
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
if ho.port != "80" {
|
||||
req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
|
||||
}
|
||||
randBytes := make([]byte, 16)
|
||||
fastrand.Read(randBytes)
|
||||
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 netproxy.Conn, host string, port string, path string) netproxy.Conn {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
return &HTTPObfs{
|
||||
Conn: conn,
|
||||
firstRequest: true,
|
||||
firstResponse: true,
|
||||
host: host,
|
||||
port: port,
|
||||
path: path,
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package simpleobfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
)
|
||||
|
||||
type ObfsType int
|
||||
|
||||
const (
|
||||
HTTP ObfsType = iota
|
||||
TLS
|
||||
)
|
||||
|
||||
// SimpleObfs is a base http-obfs struct
|
||||
type SimpleObfs struct {
|
||||
dialer netproxy.Dialer
|
||||
obfstype ObfsType
|
||||
addr string
|
||||
path string
|
||||
host string
|
||||
}
|
||||
|
||||
// NewSimpleobfs returns a simpleobfs proxy.
|
||||
func NewSimpleObfs(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("simpleobfs: %w", err)
|
||||
}
|
||||
|
||||
t := &SimpleObfs{
|
||||
dialer: nextDialer,
|
||||
addr: u.Host,
|
||||
}
|
||||
query := u.Query()
|
||||
obfstype := query.Get("type")
|
||||
if obfstype == "" {
|
||||
obfstype = query.Get("obfs")
|
||||
}
|
||||
switch strings.ToLower(obfstype) {
|
||||
case "http":
|
||||
t.obfstype = HTTP
|
||||
case "tls":
|
||||
t.obfstype = TLS
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported obfs type %v", obfstype)
|
||||
}
|
||||
t.host = query.Get("host")
|
||||
t.path = query.Get("path")
|
||||
if t.path == "" {
|
||||
t.path = query.Get("uri")
|
||||
}
|
||||
return t, &dialer.Property{
|
||||
Name: u.Fragment,
|
||||
Address: t.addr,
|
||||
Protocol: "simpleobfs(" + obfstype + ")",
|
||||
Link: link,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SimpleObfs) Dial(network, addr string) (c netproxy.Conn, err error) {
|
||||
magicNetwork, err := netproxy.ParseMagicNetwork(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch magicNetwork.Network {
|
||||
case "tcp":
|
||||
rc, err := s.dialer.Dial(network, s.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[simpleobfs]: dial to %s: %w", s.addr, err)
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.host != "" {
|
||||
host = s.host
|
||||
}
|
||||
switch s.obfstype {
|
||||
case HTTP:
|
||||
c = NewHTTPObfs(rc, host, port, s.path)
|
||||
case TLS:
|
||||
c = NewTLSObfs(rc, host)
|
||||
}
|
||||
return c, err
|
||||
case "udp":
|
||||
return nil, fmt.Errorf("%w: simpleobfs+udp", netproxy.UnsupportedTunnelTypeError)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network)
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/tls.go
|
||||
|
||||
package simpleobfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/pkg/fastrand"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||
)
|
||||
|
||||
// TLSObfs is shadowsocks tls simple-obfs implementation
|
||||
type TLSObfs struct {
|
||||
netproxy.Conn
|
||||
server string
|
||||
remain int
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
rMu sync.Mutex
|
||||
wMu sync.Mutex
|
||||
}
|
||||
|
||||
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
||||
buf := make([]byte, discardN)
|
||||
_, err := io.ReadFull(to.Conn, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
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) {
|
||||
to.rMu.Lock()
|
||||
defer to.rMu.Unlock()
|
||||
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) {
|
||||
to.wMu.Lock()
|
||||
defer to.wMu.Unlock()
|
||||
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 := &bytes.Buffer{}
|
||||
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 netproxy.Conn, server string) netproxy.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)
|
||||
fastrand.Read(random)
|
||||
fastrand.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()
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package tls
|
||||
|
||||
import "github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("tls", NewTls)
|
||||
dialer.FromLinkRegister("utls", NewTls)
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
// Tls is a base Tls struct
|
||||
type Tls struct {
|
||||
dialer netproxy.Dialer
|
||||
addr string
|
||||
serverName string
|
||||
skipVerify bool
|
||||
tlsImplentation string
|
||||
utlsImitate string
|
||||
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// NewTls returns a Tls infra.
|
||||
func NewTls(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("NewTls: %w", err)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
|
||||
tlsImplentation := u.Scheme
|
||||
utlsImitate := query.Get("utlsImitate")
|
||||
if tlsImplentation == "tls" && option.TlsImplementation != "" {
|
||||
tlsImplentation = option.TlsImplementation
|
||||
utlsImitate = option.UtlsImitate
|
||||
}
|
||||
t := &Tls{
|
||||
dialer: nextDialer,
|
||||
addr: u.Host,
|
||||
tlsImplentation: tlsImplentation,
|
||||
utlsImitate: utlsImitate,
|
||||
serverName: query.Get("sni"),
|
||||
}
|
||||
if t.serverName == "" {
|
||||
t.serverName = u.Hostname()
|
||||
}
|
||||
|
||||
// skipVerify
|
||||
allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify"))
|
||||
}
|
||||
t.skipVerify = allowInsecure || option.AllowInsecure
|
||||
t.tlsConfig = &tls.Config{
|
||||
ServerName: t.serverName,
|
||||
InsecureSkipVerify: t.skipVerify,
|
||||
}
|
||||
if len(query.Get("alpn")) > 0 {
|
||||
t.tlsConfig.NextProtos = strings.Split(query.Get("alpn"), ",")
|
||||
}
|
||||
|
||||
return t, &dialer.Property{
|
||||
Name: u.Fragment,
|
||||
Address: t.addr,
|
||||
Protocol: tlsImplentation,
|
||||
Link: link,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Tls) Dial(network, addr string) (c netproxy.Conn, err error) {
|
||||
magicNetwork, err := netproxy.ParseMagicNetwork(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch magicNetwork.Network {
|
||||
case "tcp":
|
||||
rc, err := s.dialer.Dial(network, s.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[Tls]: dial to %s: %w", s.addr, err)
|
||||
}
|
||||
|
||||
var tlsConn interface {
|
||||
netproxy.Conn
|
||||
Handshake() error
|
||||
}
|
||||
|
||||
switch s.tlsImplentation {
|
||||
case "tls":
|
||||
tlsConn = tls.Client(&netproxy.FakeNetConn{
|
||||
Conn: rc,
|
||||
LAddr: nil,
|
||||
RAddr: nil,
|
||||
}, s.tlsConfig)
|
||||
|
||||
case "utls":
|
||||
clientHelloID, err := nameToUtlsClientHelloID(s.utlsImitate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConn = utls.UClient(&netproxy.FakeNetConn{
|
||||
Conn: rc,
|
||||
LAddr: nil,
|
||||
RAddr: nil,
|
||||
}, uTLSConfigFromTLSConfig(s.tlsConfig), *clientHelloID)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown tls implementation: %v", s.tlsImplentation)
|
||||
}
|
||||
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, err
|
||||
case "udp":
|
||||
return nil, fmt.Errorf("%w: tls+udp", netproxy.UnsupportedTunnelTypeError)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network)
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
func uTLSConfigFromTLSConfig(config *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
ServerName: config.ServerName,
|
||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
var clientHelloIDMap = map[string]*utls.ClientHelloID{
|
||||
"randomized": &utls.HelloRandomized,
|
||||
"randomizedalpn": &utls.HelloRandomizedALPN,
|
||||
"randomizednoalpn": &utls.HelloRandomizedNoALPN,
|
||||
"firefox_auto": &utls.HelloFirefox_Auto,
|
||||
"firefox_55": &utls.HelloFirefox_55,
|
||||
"firefox_56": &utls.HelloFirefox_56,
|
||||
"firefox_63": &utls.HelloFirefox_63,
|
||||
"firefox_65": &utls.HelloFirefox_65,
|
||||
"firefox_99": &utls.HelloFirefox_99,
|
||||
"firefox_102": &utls.HelloFirefox_102,
|
||||
"firefox_105": &utls.HelloFirefox_105,
|
||||
"chrome_auto": &utls.HelloChrome_Auto,
|
||||
"chrome_58": &utls.HelloChrome_58,
|
||||
"chrome_62": &utls.HelloChrome_62,
|
||||
"chrome_70": &utls.HelloChrome_70,
|
||||
"chrome_72": &utls.HelloChrome_72,
|
||||
"chrome_83": &utls.HelloChrome_83,
|
||||
"chrome_87": &utls.HelloChrome_87,
|
||||
"chrome_96": &utls.HelloChrome_96,
|
||||
"chrome_100": &utls.HelloChrome_100,
|
||||
"chrome_102": &utls.HelloChrome_102,
|
||||
"ios_auto": &utls.HelloIOS_Auto,
|
||||
"ios_11_1": &utls.HelloIOS_11_1,
|
||||
"ios_12_1": &utls.HelloIOS_12_1,
|
||||
"ios_13": &utls.HelloIOS_13,
|
||||
"ios_14": &utls.HelloIOS_14,
|
||||
"android_11_okhttp": &utls.HelloAndroid_11_OkHttp,
|
||||
"edge_auto": &utls.HelloEdge_Auto,
|
||||
"edge_85": &utls.HelloEdge_85,
|
||||
"edge_106": &utls.HelloEdge_106,
|
||||
"safari_auto": &utls.HelloSafari_Auto,
|
||||
"safari_16_0": &utls.HelloSafari_16_0,
|
||||
"360_auto": &utls.Hello360_Auto,
|
||||
"360_7_5": &utls.Hello360_7_5,
|
||||
"360_11_0": &utls.Hello360_11_0,
|
||||
"qq_auto": &utls.HelloQQ_Auto,
|
||||
"qq_11_1": &utls.HelloQQ_11_1,
|
||||
}
|
||||
|
||||
func nameToUtlsClientHelloID(name string) (*utls.ClientHelloID, error) {
|
||||
clientHelloID, ok := clientHelloIDMap[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown uTLS Client Hello ID: %s", name)
|
||||
}
|
||||
return clientHelloID, nil
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gorilla/websocket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
*websocket.Conn
|
||||
readBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
func newConn(wsc *websocket.Conn) *conn {
|
||||
return &conn{
|
||||
Conn: wsc,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
if c.readBuffer.Len() > 0 {
|
||||
return c.readBuffer.Read(b)
|
||||
}
|
||||
_, msg, err := c.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n = copy(b, msg)
|
||||
if n < len(msg) {
|
||||
c.readBuffer.Write(msg[n:])
|
||||
}
|
||||
return n, nil
|
||||
|
||||
}
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
return len(b), c.Conn.WriteMessage(websocket.BinaryMessage, b)
|
||||
}
|
||||
|
||||
func (c *conn) SetDeadline(t time.Time) error {
|
||||
_ = c.Conn.SetReadDeadline(t)
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("ws", NewWs)
|
||||
dialer.FromLinkRegister("wss", NewWs)
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Ws is a base Ws struct
|
||||
type Ws struct {
|
||||
dialer netproxy.Dialer
|
||||
wsAddr string
|
||||
header http.Header
|
||||
tlsClientConfig *tls.Config
|
||||
}
|
||||
|
||||
// NewWs returns a Ws infra.
|
||||
func NewWs(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("NewWs: %w", err)
|
||||
}
|
||||
|
||||
t := &Ws{
|
||||
dialer: nextDialer,
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
host := query.Get("host")
|
||||
if host == "" {
|
||||
host = u.Hostname()
|
||||
}
|
||||
t.header = http.Header{}
|
||||
t.header.Set("Host", host)
|
||||
|
||||
wsUrl := url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
}
|
||||
t.wsAddr = wsUrl.String() + u.Path
|
||||
if u.Scheme == "wss" {
|
||||
allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure"))
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure"))
|
||||
}
|
||||
if !allowInsecure {
|
||||
allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify"))
|
||||
}
|
||||
// TODO: utls
|
||||
t.tlsClientConfig = &tls.Config{
|
||||
ServerName: query.Get("sni"),
|
||||
InsecureSkipVerify: allowInsecure || option.AllowInsecure,
|
||||
}
|
||||
if len(query.Get("alpn")) > 0 {
|
||||
t.tlsClientConfig.NextProtos = strings.Split(query.Get("alpn"), ",")
|
||||
}
|
||||
}
|
||||
return t, &dialer.Property{
|
||||
Name: u.Fragment,
|
||||
Address: wsUrl.Host,
|
||||
Protocol: u.Scheme,
|
||||
Link: link,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Ws) Dial(network, addr string) (c netproxy.Conn, err error) {
|
||||
magicNetwork, err := netproxy.ParseMagicNetwork(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch magicNetwork.Network {
|
||||
case "tcp":
|
||||
wsDialer := &websocket.Dialer{
|
||||
NetDial: func(_, addr string) (net.Conn, error) {
|
||||
c, err := s.dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &netproxy.FakeNetConn{
|
||||
Conn: c,
|
||||
LAddr: nil,
|
||||
RAddr: nil,
|
||||
}, nil
|
||||
},
|
||||
TLSClientConfig: s.tlsClientConfig,
|
||||
}
|
||||
rc, _, err := wsDialer.Dial(s.wsAddr, s.header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[Ws]: dial to %s: %w", s.wsAddr, err)
|
||||
}
|
||||
return newConn(rc), err
|
||||
case "udp":
|
||||
return nil, fmt.Errorf("%w: ws+udp", netproxy.UnsupportedTunnelTypeError)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network)
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ func (c *controlPlaneCore) outboundAliveChangeCallback(outbound uint8, dryrun bo
|
||||
}
|
||||
c.log.WithFields(logrus.Fields{
|
||||
"outboundId": outbound,
|
||||
}).Warnf("Outbound <%v> %v -> %v, notify the kernel program.", c.outboundId2Name[outbound], networkType.StringWithoutDns(), strAlive)
|
||||
}).Tracef("Outbound <%v> %v -> %v, notify the kernel program.", c.outboundId2Name[outbound], networkType.StringWithoutDns(), strAlive)
|
||||
}
|
||||
|
||||
value := uint32(0)
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/daeuniverse/dae/config"
|
||||
"github.com/daeuniverse/dae/pkg/config_parser"
|
||||
internal "github.com/daeuniverse/dae/pkg/ebpf_internal"
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"github.com/daeuniverse/softwind/protocol/direct"
|
||||
"github.com/daeuniverse/softwind/transport/grpc"
|
||||
@ -229,24 +230,16 @@ func NewControlPlane(
|
||||
log.Warnln("AllowInsecure is enabled, but it is not recommended. Please make sure you have to turn it on.")
|
||||
}
|
||||
option := &dialer.GlobalOption{
|
||||
ExtraOption: D.ExtraOption{
|
||||
AllowInsecure: global.AllowInsecure,
|
||||
TlsImplementation: global.TlsImplementation,
|
||||
UtlsImitate: global.UtlsImitate},
|
||||
Log: log,
|
||||
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{
|
||||
Raw: global.TcpCheckUrl,
|
||||
Log: log,
|
||||
ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae),
|
||||
Method: global.TcpCheckHttpMethod,
|
||||
},
|
||||
CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{
|
||||
Raw: global.UdpCheckDns,
|
||||
ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae),
|
||||
Somark: global.SoMarkFromDae,
|
||||
},
|
||||
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Method: global.TcpCheckHttpMethod},
|
||||
CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Somark: global.SoMarkFromDae},
|
||||
CheckInterval: global.CheckInterval,
|
||||
CheckTolerance: global.CheckTolerance,
|
||||
CheckDnsTcp: true,
|
||||
AllowInsecure: global.AllowInsecure,
|
||||
TlsImplementation: global.TlsImplementation,
|
||||
UtlsImitate: global.UtlsImitate,
|
||||
}
|
||||
|
||||
// Dial mode.
|
||||
|
@ -19,6 +19,7 @@
|
||||
- [x] Stream Ciphers
|
||||
- [x] simple-obfs
|
||||
- [ ] v2ray-plugin
|
||||
- [x] Websocket (+TLS)
|
||||
- [x] ShadowsocksR
|
||||
- [x] Trojan
|
||||
- [x] Trojan-gfw
|
||||
|
@ -18,6 +18,7 @@
|
||||
- [x] Stream Ciphers
|
||||
- [x] simple-obfs
|
||||
- [ ] v2ray-plugin
|
||||
- [x] Websocket (+TLS)
|
||||
- [x] ShadowsocksR
|
||||
- [x] Trojan
|
||||
- [x] Trojan-gfw
|
||||
|
23
go.mod
23
go.mod
@ -1,6 +1,8 @@
|
||||
module github.com/daeuniverse/dae
|
||||
|
||||
go 1.21
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.21.3
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
@ -8,8 +10,8 @@ require (
|
||||
github.com/bits-and-blooms/bloom/v3 v3.5.0
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d
|
||||
github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d
|
||||
github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
@ -20,9 +22,9 @@ require (
|
||||
github.com/v2rayA/ahocorasick-domain v0.0.0-20231231085011-99ceb8ef3208
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/sys v0.11.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
@ -32,15 +34,17 @@ require (
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/mzz2017/quic-go v0.0.0-20231230054300-5221ce9164a3 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.37.4 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@ -60,17 +64,18 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mzz2017/disk-bloom v1.0.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/refraction-networking/utls v1.3.2
|
||||
github.com/refraction-networking/utls v1.4.3 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/term v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
google.golang.org/grpc v1.57.0 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/daeuniverse/softwind => ../softwind
|
||||
// replace github.com/daeuniverse/outbound => ../outbound
|
||||
|
||||
// replace github.com/mzz2017/quic-go => ../quic-go
|
||||
|
||||
|
32
go.sum
32
go.sum
@ -13,6 +13,8 @@ github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUg
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q=
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M=
|
||||
github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d h1:hEZDwJvoTATxtNU8/kirJP9GK0tFxekXzT00cGXO0xg=
|
||||
github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d/go.mod h1:RlBqzRS0OfxDxmD1bgNfTmz9uzs8wQSmSG2vonMwSd0=
|
||||
github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1 h1:qh16GLF9TfntnLIwos49rj7Yj4EHICDe9ToesIjTm1c=
|
||||
github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1/go.mod h1:ly72DcZIxHlKbOEz1qaSCh99lr7ns8T5JLpfww8hXrI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -116,8 +118,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
|
||||
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
|
||||
github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM=
|
||||
github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@ -158,8 +162,8 @@ go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
|
||||
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -170,8 +174,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -192,14 +196,14 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
@ -209,8 +213,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
Reference in New Issue
Block a user