mirror of
https://github.com/daeuniverse/dae.git
synced 2024-12-22 16:24:40 +07:00
fix: outbound
This commit is contained in:
parent
5de32db41b
commit
07d48c7732
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
|
@ -1,50 +1,19 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/outbound/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: "",
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
@ -272,10 +272,10 @@ type CheckOption struct {
|
||||
func (d *Dialer) ActivateCheck() {
|
||||
d.tickerMu.Lock()
|
||||
defer d.tickerMu.Unlock()
|
||||
if d.InstanceOption.CheckEnabled {
|
||||
if d.InstanceOption.DisableCheck || d.checkActivated {
|
||||
return
|
||||
}
|
||||
d.InstanceOption.CheckEnabled = true
|
||||
d.checkActivated = true
|
||||
go d.aliveBackground()
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
@ -6,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -29,29 +35,26 @@ type Dialer struct {
|
||||
checkCh chan time.Time
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
checkActivated bool
|
||||
}
|
||||
|
||||
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 {
|
||||
CheckEnabled bool
|
||||
DisableCheck bool
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Name string
|
||||
Address string
|
||||
Protocol string
|
||||
Link string
|
||||
D.Property
|
||||
SubscriptionTag string
|
||||
}
|
||||
|
||||
@ -77,9 +80,6 @@ func NewDialer(dialer netproxy.Dialer, option *GlobalOption, iOption InstanceOpt
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
if iOption.CheckEnabled {
|
||||
go d.aliveBackground()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
softwindDirect "github.com/daeuniverse/outbound/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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/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()
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
@ -1,74 +1,23 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
D "github.com/daeuniverse/outbound/dialer"
|
||||
"github.com/daeuniverse/outbound/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,
|
||||
d, _p, err := D.NewNetproxyDialerFromLink(direct.SymmetricDirect, &gOption.ExtraOption, link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := Property{
|
||||
Property: *_p,
|
||||
SubscriptionTag: subscriptionTag,
|
||||
}
|
||||
for i := len(links) - 1; i >= 0; i-- {
|
||||
link := strings.TrimSpace(links[i])
|
||||
u, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creator, ok := fromLinkCreators[u.Scheme]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected link type: %v", u.Scheme)
|
||||
}
|
||||
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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/protocol"
|
||||
"github.com/daeuniverse/outbound/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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/protocol"
|
||||
"github.com/daeuniverse/outbound/protocol/shadowsocks_stream"
|
||||
"github.com/daeuniverse/outbound/transport/shadowsocksr/obfs"
|
||||
"github.com/daeuniverse/outbound/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,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
@ -1,104 +0,0 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/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/outbound/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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/protocol"
|
||||
"github.com/daeuniverse/outbound/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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/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,3 +1,8 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package dialer
|
||||
|
||||
import "time"
|
||||
|
@ -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/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/protocol"
|
||||
"github.com/daeuniverse/outbound/protocol/direct"
|
||||
"github.com/daeuniverse/outbound/protocol/http"
|
||||
"github.com/daeuniverse/outbound/transport/grpc"
|
||||
"github.com/daeuniverse/outbound/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 ""
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package outbound
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package outbound
|
||||
@ -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{DisableCheck: false}, p)
|
||||
return d
|
||||
}
|
||||
|
||||
func TestDialerGroup_Select_Fixed(t *testing.T) {
|
||||
option := &dialer.GlobalOption{
|
||||
Log: log,
|
||||
@ -38,14 +44,15 @@ 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{
|
||||
Policy: consts.DialerSelectionPolicy_Fixed,
|
||||
FixedIndex: fixedIndex,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Fixed,
|
||||
FixedIndex: fixedIndex,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
for i := 0; i < 10; i++ {
|
||||
d, _, err := g.Select(TestNetworkType, false)
|
||||
if err != nil {
|
||||
@ -78,20 +85,21 @@ 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{
|
||||
Policy: consts.DialerSelectionPolicy_MinLastLatency,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_MinLastLatency,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
|
||||
// Test 1000 times.
|
||||
for i := 0; i < 1000; i++ {
|
||||
@ -147,15 +155,16 @@ 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{
|
||||
Policy: consts.DialerSelectionPolicy_Random,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
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))
|
||||
for i := 0; i < 100; i++ {
|
||||
d, _, err := g.Select(TestNetworkType, false)
|
||||
@ -186,15 +195,16 @@ 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{
|
||||
Policy: consts.DialerSelectionPolicy_Random,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}},
|
||||
DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Random,
|
||||
}, func(alive bool, networkType *dialer.NetworkType, isInit bool) {})
|
||||
zeroTarget := 3
|
||||
g.MustGetAliveDialerSet(TestNetworkType).NotifyLatencyChange(dialers[zeroTarget], false)
|
||||
count := make([]int, len(dialers))
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package outbound
|
||||
|
@ -1,17 +1,18 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/dae/pkg/config_parser"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,20 +29,22 @@ const (
|
||||
)
|
||||
|
||||
type DialerSet struct {
|
||||
log *logrus.Logger
|
||||
dialers []*dialer.Dialer
|
||||
nodeToTagMap map[*dialer.Dialer]string
|
||||
}
|
||||
|
||||
func NewDialerSetFromLinks(option *dialer.GlobalOption, tagToNodeList map[string][]string) *DialerSet {
|
||||
s := &DialerSet{
|
||||
log: option.Log,
|
||||
dialers: make([]*dialer.Dialer, 0),
|
||||
nodeToTagMap: make(map[*dialer.Dialer]string),
|
||||
}
|
||||
for subscriptionTag, nodes := range tagToNodeList {
|
||||
for _, node := range nodes {
|
||||
d, err := dialer.NewFromLink(option, dialer.InstanceOption{CheckEnabled: false}, node, subscriptionTag)
|
||||
d, err := dialer.NewFromLink(option, dialer.InstanceOption{DisableCheck: false}, node, subscriptionTag)
|
||||
if err != nil {
|
||||
option.Log.Infof("failed to parse node: %v", err)
|
||||
s.log.Infof("failed to parse node: %v", err)
|
||||
continue
|
||||
}
|
||||
s.dialers = append(s.dialers, d)
|
||||
@ -69,24 +72,29 @@ func (s *DialerSet) filterHit(dialer *dialer.Dialer, filters []*config_parser.Fu
|
||||
switch filter.Name {
|
||||
case FilterInput_Name:
|
||||
// Or
|
||||
loop:
|
||||
for _, param := range filter.Params {
|
||||
switch param.Key {
|
||||
case FilterKey_Name_Regex:
|
||||
matched, _ := regexp.MatchString(param.Val, dialer.Property().Name)
|
||||
regex, err := regexp2.Compile(param.Val, 0)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("bad regexp in filter %v: %w", filter.String(false, true, true), err)
|
||||
}
|
||||
matched, _ := regex.MatchString(dialer.Property().Name)
|
||||
//logrus.Warnln(param.Val, matched, dialer.Name())
|
||||
if matched {
|
||||
subFilterHit = true
|
||||
break
|
||||
break loop
|
||||
}
|
||||
case FilterKey_Name_Keyword:
|
||||
if strings.Contains(dialer.Property().Name, param.Val) {
|
||||
subFilterHit = true
|
||||
break
|
||||
break loop
|
||||
}
|
||||
case "":
|
||||
if dialer.Property().Name == param.Val {
|
||||
subFilterHit = true
|
||||
break
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name)
|
||||
@ -94,20 +102,25 @@ func (s *DialerSet) filterHit(dialer *dialer.Dialer, filters []*config_parser.Fu
|
||||
}
|
||||
case FilterInput_SubscriptionTag:
|
||||
// Or
|
||||
loop2:
|
||||
for _, param := range filter.Params {
|
||||
switch param.Key {
|
||||
case FilterInput_SubscriptionTag_Regex:
|
||||
matched, _ := regexp.MatchString(param.Val, s.nodeToTagMap[dialer])
|
||||
//logrus.Warnln(param.Val, matched, dialer.Name())
|
||||
regex, err := regexp2.Compile(param.Val, 0)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("bad regexp in filter %v: %w", filter.String(false, true, true), err)
|
||||
}
|
||||
matched, _ := regex.MatchString(s.nodeToTagMap[dialer])
|
||||
if matched {
|
||||
subFilterHit = true
|
||||
break
|
||||
break loop2
|
||||
}
|
||||
//logrus.Warnln(param.Val, matched, dialer.Name())
|
||||
case "":
|
||||
// Full
|
||||
if s.nodeToTagMap[dialer] == param.Val {
|
||||
subFilterHit = true
|
||||
break
|
||||
break loop2
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package outbound
|
||||
|
@ -1,7 +0,0 @@
|
||||
package simpleobfs
|
||||
|
||||
import "github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
|
||||
func init() {
|
||||
dialer.FromLinkRegister("simpleobfs", NewSimpleObfs)
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/http.go
|
||||
|
||||
package simpleobfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/pkg/fastrand"
|
||||
"github.com/daeuniverse/outbound/pool"
|
||||
)
|
||||
|
||||
// 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/outbound/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,200 +0,0 @@
|
||||
// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/tls.go
|
||||
|
||||
package simpleobfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/outbound/netproxy"
|
||||
"github.com/daeuniverse/outbound/pkg/fastrand"
|
||||
)
|
||||
|
||||
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/outbound/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/outbound/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)
|
||||
}
|
||||
}
|
@ -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/outbound/pool"
|
||||
"github.com/daeuniverse/outbound/protocol/direct"
|
||||
"github.com/daeuniverse/outbound/transport/grpc"
|
||||
@ -224,24 +225,17 @@ func NewControlPlane(
|
||||
log.Warnln("AllowInsecure is enabled, but it is not recommended. Please make sure you have to turn it on.")
|
||||
}
|
||||
option := &dialer.GlobalOption{
|
||||
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,
|
||||
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},
|
||||
CheckInterval: global.CheckInterval,
|
||||
CheckTolerance: global.CheckTolerance,
|
||||
CheckDnsTcp: true,
|
||||
AllowInsecure: global.AllowInsecure,
|
||||
TlsImplementation: global.TlsImplementation,
|
||||
UtlsImitate: global.UtlsImitate,
|
||||
}
|
||||
|
||||
// Dial mode.
|
||||
@ -255,9 +249,9 @@ func NewControlPlane(
|
||||
}
|
||||
disableKernelAliveCallback := dialMode != consts.DialMode_Ip
|
||||
_direct, directProperty := dialer.NewDirectDialer(option, true)
|
||||
direct := dialer.NewDialer(_direct, option, dialer.InstanceOption{CheckEnabled: false}, directProperty)
|
||||
direct := dialer.NewDialer(_direct, option, dialer.InstanceOption{DisableCheck: true}, directProperty)
|
||||
_block, blockProperty := dialer.NewBlockDialer(option, func() { /*Dialer Outbound*/ })
|
||||
block := dialer.NewDialer(_block, option, dialer.InstanceOption{CheckEnabled: false}, blockProperty)
|
||||
block := dialer.NewDialer(_block, option, dialer.InstanceOption{DisableCheck: true}, blockProperty)
|
||||
outbounds := []*outbound.DialerGroup{
|
||||
outbound.NewDialerGroup(option, consts.OutboundDirect.String(),
|
||||
[]*dialer.Dialer{direct}, []*dialer.Annotation{{}},
|
||||
|
5
go.mod
5
go.mod
@ -11,11 +11,11 @@ require (
|
||||
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-20240423150318-97fdbb427e02
|
||||
github.com/dlclark/regexp2 v1.11.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
|
||||
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
|
||||
github.com/refraction-networking/utls v1.6.4
|
||||
github.com/safchain/ethtool v0.3.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
@ -33,7 +33,7 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.6 // 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
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
@ -65,6 +65,7 @@ require (
|
||||
github.com/mzz2017/disk-bloom v1.0.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.4 // 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
|
||||
|
2
go.sum
2
go.sum
@ -29,6 +29,8 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFP
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||
github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY=
|
||||
|
Loading…
Reference in New Issue
Block a user