feat/refactor: support the node-level proxy chain (#235)

* feat/refactor: support the chains node

* docs: update example.dae

* fix: order

* fix: http

---------

Co-authored-by: dae-bot[bot] <136105375+dae-bot[bot]@users.noreply.github.com>
This commit is contained in:
mzz
2023-07-23 20:36:57 +08:00
committed by GitHub
parent 9a2aecdb2c
commit a2f7c37f4d
24 changed files with 330 additions and 199 deletions

View File

@ -40,11 +40,11 @@ func (d *blockDialer) DialUdp(addr string) (c netproxy.PacketConn, err error) {
return nil, net.ErrClosed
}
func NewBlockDialer(option *GlobalOption, dialCallback func()) *Dialer {
return NewDialer(&blockDialer{DialCallback: dialCallback}, option, InstanceOption{CheckEnabled: false}, Property{
func NewBlockDialer(option *GlobalOption, dialCallback func()) (netproxy.Dialer, *Property) {
return &blockDialer{DialCallback: dialCallback}, &Property{
Name: "block",
Address: "",
Protocol: "",
Link: "",
})
}
}

View File

@ -17,9 +17,9 @@ var (
type Dialer struct {
*GlobalOption
InstanceOption InstanceOption
InstanceOption
netproxy.Dialer
property Property
property *Property
collectionFineMu sync.Mutex
collections [6]*collection
@ -57,7 +57,7 @@ type Property struct {
type AliveDialerSetSet map[*AliveDialerSet]int
// NewDialer is for register in general.
func NewDialer(dialer netproxy.Dialer, option *GlobalOption, iOption InstanceOption, property Property) *Dialer {
func NewDialer(dialer netproxy.Dialer, option *GlobalOption, iOption InstanceOption, property *Property) *Dialer {
var collections [6]*collection
for i := range collections {
collections[i] = newCollection()
@ -92,6 +92,6 @@ func (d *Dialer) Close() error {
return nil
}
func (d *Dialer) Property() Property {
func (d *Dialer) Property() *Property {
return d.property
}

View File

@ -1,19 +1,20 @@
package dialer
import (
"github.com/mzz2017/softwind/netproxy"
softwindDirect "github.com/mzz2017/softwind/protocol/direct"
)
func NewDirectDialer(option *GlobalOption, fullcone bool) *Dialer {
property := Property{
func NewDirectDialer(option *GlobalOption, fullcone bool) (netproxy.Dialer, *Property) {
property := &Property{
Name: "direct",
Address: "",
Protocol: "",
Link: "",
}
if fullcone {
return NewDialer(softwindDirect.FullconeDirect, option, InstanceOption{CheckEnabled: false}, property)
return softwindDirect.FullconeDirect, property
} else {
return NewDialer(softwindDirect.SymmetricDirect, option, InstanceOption{CheckEnabled: false}, property)
return softwindDirect.SymmetricDirect, property
}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol/http"
)
@ -28,15 +28,15 @@ type HTTP struct {
AllowInsecure bool `json:"allowInsecure"`
}
func NewHTTP(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseHTTPURL(link, option)
func NewHTTP(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseHTTPURL(link)
if err != nil {
return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err)
return nil, nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err)
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func ParseHTTPURL(link string, option *dialer.GlobalOption) (data *HTTP, err error) {
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)
@ -54,6 +54,16 @@ func ParseHTTPURL(link string, option *dialer.GlobalOption) (data *HTTP, err err
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(),
@ -62,22 +72,22 @@ func ParseHTTPURL(link string, option *dialer.GlobalOption) (data *HTTP, err err
Password: pwd,
SNI: u.Query().Get("sni"),
Protocol: u.Scheme,
AllowInsecure: option.AllowInsecure,
AllowInsecure: allowInsecure,
}, nil
}
func (s *HTTP) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
func (s *HTTP) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
u := s.URL()
d, err := http.NewHTTPProxy(&u, direct.SymmetricDirect) // HTTP Proxy does not support full-cone.
d, err := http.NewHTTPProxy(&u, nextDialer)
if err != nil {
return nil, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: u.String(),
}), nil
}, nil
}
func (s *HTTP) URL() url.URL {

View File

@ -8,11 +8,14 @@ package dialer
import (
"fmt"
"net/url"
"strings"
"github.com/daeuniverse/dae/common"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol/direct"
)
type FromLinkCreator func(gOption *GlobalOption, iOption InstanceOption, link string) (dialer *Dialer, err error)
type FromLinkCreator func(gOption *GlobalOption, nextDialer netproxy.Dialer, link string) (dialer netproxy.Dialer, property *Property, err error)
var fromLinkCreators = make(map[string]FromLinkCreator)
@ -20,24 +23,55 @@ func FromLinkRegister(name string, creator FromLinkCreator) {
fromLinkCreators[name] = creator
}
func NewFromLink(gOption *GlobalOption, iOption InstanceOption, link string) (dialer *Dialer, err error) {
func NewFromLink(gOption *GlobalOption, iOption InstanceOption, link string) (*Dialer, error) {
/// Get overwritten name.
overwrittenName, link := common.GetTagFromLinkLikePlaintext(link)
u, err := url.Parse(link)
if err != nil {
return nil, err
overwrittenName, linklike := common.GetTagFromLinkLikePlaintext(link)
links := strings.Split(linklike, "->")
d := direct.SymmetricDirect
p := &Property{
Name: "",
Address: "",
Protocol: "",
Link: link,
}
if creator, ok := fromLinkCreators[u.Scheme]; ok {
node, err := creator(gOption, iOption, link)
for i := len(links) - 1; i >= 0; i-- {
link := strings.TrimSpace(links[i])
u, err := url.Parse(link)
if err != nil {
return nil, err
}
// Overwrite node name using user given tag.
if overwrittenName != "" {
node.property.Name = overwrittenName
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
}
return node, err
} else {
return nil, fmt.Errorf("unexpected link type: %v", u.Scheme)
}
if overwrittenName != "" {
p.Name = overwrittenName
}
node := NewDialer(d, gOption, iOption, p)
// Overwrite node name using user given tag.
if overwrittenName != "" {
node.property.Name = overwrittenName
}
return node, nil
}

View File

@ -13,7 +13,6 @@ import (
"github.com/daeuniverse/dae/component/outbound/transport/simpleobfs"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/protocol/shadowsocks"
)
@ -36,24 +35,23 @@ type Shadowsocks struct {
Protocol string `json:"protocol"`
}
func NewShadowsocksFromLink(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
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, err
return nil, nil, err
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
var err error
var d netproxy.Dialer
d := nextDialer
switch s.Plugin.Name {
case "simple-obfs":
d = direct.SymmetricDirect // Simple-obfs does not supports UDP.
switch s.Plugin.Opts.Obfs {
case "http", "tls":
default:
return nil, fmt.Errorf("unsupported obfs %v of plugin %v", s.Plugin.Opts.Obfs, s.Plugin.Name)
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 == "" {
@ -69,12 +67,11 @@ func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.Instanc
"uri": []string{path},
}.Encode(),
}
d, err = simpleobfs.NewSimpleObfs(uSimpleObfs.String(), d)
d, _, err = simpleobfs.NewSimpleObfs(option, d, uSimpleObfs.String())
if err != nil {
return nil, err
return nil, nil, err
}
default:
d = direct.FullconeDirect // Shadowsocks Proxy supports full-cone.
}
var nextDialerName string
switch s.Cipher {
@ -83,7 +80,7 @@ func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.Instanc
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, fmt.Errorf("unsupported shadowsocks encryption method: %v", s.Cipher)
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)),
@ -92,14 +89,14 @@ func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, iOption dialer.Instanc
IsClient: true,
})
if err != nil {
return nil, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}, nil
}
func ParseSSURL(u string) (data *Shadowsocks, err error) {

View File

@ -10,8 +10,8 @@ import (
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/protocol/shadowsocks_stream"
"github.com/mzz2017/softwind/transport/shadowsocksr/obfs"
"github.com/mzz2017/softwind/transport/shadowsocksr/proto"
@ -35,16 +35,16 @@ type ShadowsocksR struct {
Protocol string `json:"protocol"`
}
func NewShadowsocksR(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
func NewShadowsocksR(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseSSRURL(link)
if err != nil {
return nil, err
return nil, nil, err
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
d := direct.SymmetricDirect
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),
@ -52,7 +52,7 @@ func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, iOption dialer.Instan
ObfsParam: s.ObfsParam,
})
if err != nil {
return nil, err
return nil, nil, err
}
d = obfsDialer
d, err = shadowsocks_stream.NewDialer(d, protocol.Header{
@ -62,7 +62,7 @@ func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, iOption dialer.Instan
IsClient: true,
})
if err != nil {
return nil, err
return nil, nil, err
}
d = &proto.Dialer{
NextDialer: d,
@ -71,12 +71,12 @@ func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, iOption dialer.Instan
ObfsOverhead: obfsDialer.ObfsOverhead(),
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}, nil
}
func ParseSSRURL(u string) (data *ShadowsocksR, err error) {

View File

@ -8,7 +8,7 @@ import (
"strconv"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol/socks5"
)
@ -28,28 +28,29 @@ type Socks struct {
Protocol string `json:"protocol"`
}
func NewSocks(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
func NewSocks(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseSocksURL(link)
if err != nil {
return nil, dialer.InvalidParameterErr
return nil, nil, dialer.InvalidParameterErr
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *Socks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
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, direct.FullconeDirect) // Socks5 Proxy supports full-cone.
d, err := socks5.NewSocks5Dialer(link, d) // Socks5 Proxy supports full-cone.
if err != nil {
return nil, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: link,
}), nil
}, nil
//case "socks4", "socks4a":
// d, err := socks4.NewSocks4Dialer(link, &proxy.Direct{})
// if err != nil {
@ -57,7 +58,7 @@ func (s *Socks) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
// }
// return dialer.NewDialer(d, false, s.Name, s.Protocol, link), nil
default:
return nil, fmt.Errorf("unexpected protocol: %v", s.Protocol)
return nil, nil, fmt.Errorf("unexpected protocol: %v", s.Protocol)
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/daeuniverse/dae/common/netutils"
"github.com/daeuniverse/dae/component/outbound/dialer"
dnsmessage "github.com/miekg/dns"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/sirupsen/logrus"
)
@ -23,7 +24,7 @@ func TestSocks5(t *testing.T) {
t.Fatal(err)
}
log := logrus.StandardLogger()
d, err := c.Dialer(&dialer.GlobalOption{
d, _, err := c.Dialer(&dialer.GlobalOption{
Log: log,
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{
Log: log,
@ -41,9 +42,7 @@ func TestSocks5(t *testing.T) {
AllowInsecure: false,
TlsImplementation: "",
UtlsImitate: "",
}, dialer.InstanceOption{
CheckEnabled: false,
})
}, direct.SymmetricDirect)
if err != nil {
t.Fatal(err)
}

View File

@ -2,18 +2,18 @@ package trojan
import (
"fmt"
"github.com/daeuniverse/dae/component/outbound/transport/tls"
"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/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol"
"github.com/mzz2017/softwind/protocol/direct"
"github.com/mzz2017/softwind/transport/grpc"
)
@ -37,36 +37,36 @@ type Trojan struct {
Protocol string `json:"protocol"`
}
func NewTrojan(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseTrojanURL(link, option)
func NewTrojan(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseTrojanURL(link)
if err != nil {
return nil, err
return nil, nil, err
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
d := direct.FullconeDirect // Trojan Proxy supports full-cone.
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)},
"utlsImitate": []string{option.UtlsImitate},
}.Encode(),
}
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
if d, err = tls.NewTls(u.String(), d); err != nil {
return nil, err
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{
u := url.URL{
Scheme: "ws",
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
RawQuery: url.Values{
@ -74,8 +74,8 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti
"path": []string{s.Path},
}.Encode(),
}
if d, err = ws.NewWs(u.String(), d); err != nil {
return nil, err
if d, _, err = ws.NewWs(option, d, u.String()); err != nil {
return nil, nil, err
}
case "grpc":
serviceName := s.ServiceName
@ -86,7 +86,7 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti
NextDialer: &netproxy.ContextDialer{Dialer: d},
ServiceName: serviceName,
ServerName: s.Sni,
AllowInsecure: s.AllowInsecure,
AllowInsecure: s.AllowInsecure || option.AllowInsecure,
}
}
if strings.HasPrefix(s.Encryption, "ss;") {
@ -97,7 +97,7 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti
Password: fields[2],
IsClient: false,
}); err != nil {
return nil, err
return nil, nil, err
}
}
if d, err = protocol.NewDialer("trojanc", d, protocol.Header{
@ -105,17 +105,17 @@ func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOpti
Password: s.Password,
IsClient: true,
}); err != nil {
return nil, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}, nil
}
func ParseTrojanURL(u string, option *dialer.GlobalOption) (data *Trojan, err error) {
func ParseTrojanURL(u string) (data *Trojan, err error) {
//trojan://password@server:port#escape(remarks)
t, err := url.Parse(u)
if err != nil {
@ -123,8 +123,14 @@ func ParseTrojanURL(u string, option *dialer.GlobalOption) (data *Trojan, err er
return
}
allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure"))
if !allowInsecure && option.AllowInsecure {
allowInsecure = true
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 == "" {

View File

@ -10,8 +10,8 @@ import (
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/netproxy"
"github.com/mzz2017/softwind/protocol"
"github.com/mzz2017/softwind/protocol/direct"
)
func init() {
@ -33,16 +33,16 @@ type Tuic struct {
UdpRelayMode string
}
func NewTuic(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
s, err := ParseTuicURL(link, option)
func NewTuic(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseTuicURL(link)
if err != nil {
return nil, err
return nil, nil, err
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *Tuic) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
d := direct.FullconeDirect // Tuic Proxy supports full-cone.
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" {
@ -51,23 +51,28 @@ func (s *Tuic) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption
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},
User: s.User,
Password: s.Password,
IsClient: true,
Flags: flags,
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, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Name,
Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}, nil
}
func ParseTuicURL(u string, option *dialer.GlobalOption) (data *Tuic, err error) {
func ParseTuicURL(u string) (data *Tuic, err error) {
//trojan://password@server:port#escape(remarks)
t, err := url.Parse(u)
if err != nil {
@ -85,8 +90,11 @@ func ParseTuicURL(u string, option *dialer.GlobalOption) (data *Tuic, err error)
if !allowInsecure {
allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure"))
}
if !allowInsecure && option.AllowInsecure {
allowInsecure = true
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 == "" {

View File

@ -44,40 +44,37 @@ type V2Ray struct {
Protocol string `json:"protocol"`
}
func NewV2Ray(option *dialer.GlobalOption, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
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, option)
s, err = ParseVmessURL(link)
if err != nil {
return nil, err
return nil, nil, err
}
if s.Aid != "0" && s.Aid != "" {
return nil, fmt.Errorf("%w: aid: %v, we only support AEAD encryption", dialer.UnexpectedFieldErr, 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, option)
s, err = ParseVlessURL(link)
if err != nil {
return nil, err
return nil, nil, err
}
default:
return nil, dialer.InvalidParameterErr
return nil, nil, dialer.InvalidParameterErr
}
return s.Dialer(option, iOption)
return s.Dialer(option, nextDialer)
}
func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (data *dialer.Dialer, err error) {
var d netproxy.Dialer
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":
d = direct.FullconeDirect // VMess Proxy supports full-cone.
case "vless":
d = direct.SymmetricDirect // VLESS Proxy does not yet support full-cone by softwind.
case "vmess", "vless":
default:
return nil, fmt.Errorf("V2Ray.Dialer: unexpected protocol: %v", s.Protocol)
return nil, nil, fmt.Errorf("V2Ray.Dialer: unexpected protocol: %v", s.Protocol)
}
switch strings.ToLower(s.Net) {
@ -97,12 +94,12 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
RawQuery: url.Values{
"host": []string{s.Host},
"sni": []string{sni},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure)},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
}.Encode(),
}
d, err = ws.NewWs(u.String(), d)
d, _, err = ws.NewWs(option, d, u.String())
if err != nil {
return nil, err
return nil, nil, err
}
case "tcp":
if s.TLS == "tls" || s.TLS == "xtls" {
@ -115,17 +112,17 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
Host: net.JoinHostPort(s.Add, s.Port),
RawQuery: url.Values{
"sni": []string{sni},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure)},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
"utlsImitate": []string{option.UtlsImitate},
}.Encode(),
}
d, err = tls.NewTls(u.String(), d)
d, _, err = tls.NewTls(option, d, u.String())
if err != nil {
return nil, err
return nil, nil, err
}
}
if s.Type != "none" && s.Type != "" {
return nil, fmt.Errorf("%w: type: %v", dialer.UnexpectedFieldErr, s.Type)
return nil, nil, fmt.Errorf("%w: type: %v", dialer.UnexpectedFieldErr, s.Type)
}
case "grpc":
sni := s.SNI
@ -140,7 +137,7 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
NextDialer: &netproxy.ContextDialer{Dialer: d},
ServiceName: serviceName,
ServerName: sni,
AllowInsecure: s.AllowInsecure,
AllowInsecure: s.AllowInsecure || option.AllowInsecure,
}
case "http", "http2", "h2":
sni := s.SNI
@ -157,7 +154,7 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
Path: s.Path,
RawQuery: url.Values{
"sni": []string{sni},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure)},
"allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)},
"tlsImplementation": []string{option.TlsImplementation},
"utlsImitate": []string{option.UtlsImitate},
"host": []string{s.Host},
@ -167,10 +164,10 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
}
d, err = http.NewHTTPProxy(&u, direct.SymmetricDirect)
if err != nil {
return nil, err
return nil, nil, err
}
default:
return nil, fmt.Errorf("%w: network: %v", dialer.UnexpectedFieldErr, s.Net)
return nil, nil, fmt.Errorf("%w: network: %v", dialer.UnexpectedFieldErr, s.Net)
}
if d, err = protocol.NewDialer(s.Protocol, d, protocol.Header{
@ -180,17 +177,17 @@ func (s *V2Ray) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOptio
IsClient: true,
//Flags: protocol.Flags_VMess_UsePacketAddr,
}); err != nil {
return nil, err
return nil, nil, err
}
return dialer.NewDialer(d, option, iOption, dialer.Property{
return d, &dialer.Property{
Name: s.Ps,
Address: net.JoinHostPort(s.Add, s.Port),
Protocol: s.Protocol,
Link: s.ExportToURL(),
}), nil
}, nil
}
func ParseVlessURL(vless string, option *dialer.GlobalOption) (data *V2Ray, err error) {
func ParseVlessURL(vless string) (data *V2Ray, err error) {
u, err := url.Parse(vless)
if err != nil {
return nil, err
@ -228,13 +225,10 @@ func ParseVlessURL(vless string, option *dialer.GlobalOption) (data *V2Ray, err
if data.Type == "mkcp" || data.Type == "kcp" {
data.Path = u.Query().Get("seed")
}
if option.AllowInsecure {
data.AllowInsecure = true
}
return data, nil
}
func ParseVmessURL(vmess string, option *dialer.GlobalOption) (data *V2Ray, err error) {
func ParseVmessURL(vmess string) (data *V2Ray, err error) {
var info V2Ray
// perform base64 decoding and unmarshal to VmessInfo
raw, err := common.Base64StdDecode(vmess[8:])
@ -252,7 +246,7 @@ func ParseVmessURL(vmess string, option *dialer.GlobalOption) (data *V2Ray, err
s := strings.Split(vmess[8:], "?")[0]
s, err = common.Base64StdDecode(s)
if err != nil {
s, err = common.Base64UrlDecode(s)
s, _ = common.Base64UrlDecode(s)
}
subMatch := re.FindStringSubmatch(s)
if subMatch == nil {
@ -310,9 +304,6 @@ func ParseVmessURL(vmess string, option *dialer.GlobalOption) (data *V2Ray, err
info.Aid = "0"
}
info.Protocol = "vmess"
if option.AllowInsecure {
info.AllowInsecure = true
}
return &info, nil
}

View File

@ -41,7 +41,7 @@ func NewDialerSetFromLinks(option *dialer.GlobalOption, tagToNodeList map[string
for _, node := range nodes {
d, err := dialer.NewFromLink(option, dialer.InstanceOption{CheckEnabled: false}, node)
if err != nil {
option.Log.Infof("failed to parse node: %v: %v", node, err)
option.Log.Infof("failed to parse node: %v", err)
continue
}
s.dialers = append(s.dialers, d)

View File

@ -13,6 +13,9 @@ import (
_ "github.com/daeuniverse/dae/component/outbound/dialer/trojan"
_ "github.com/daeuniverse/dae/component/outbound/dialer/tuic"
_ "github.com/daeuniverse/dae/component/outbound/dialer/v2ray"
_ "github.com/daeuniverse/dae/component/outbound/transport/simpleobfs"
_ "github.com/daeuniverse/dae/component/outbound/transport/tls"
_ "github.com/daeuniverse/dae/component/outbound/transport/ws"
_ "github.com/mzz2017/softwind/protocol/shadowsocks"
_ "github.com/mzz2017/softwind/protocol/trojanc"
_ "github.com/mzz2017/softwind/protocol/tuic"

View File

@ -0,0 +1,7 @@
package simpleobfs
import "github.com/daeuniverse/dae/component/outbound/dialer"
func init() {
dialer.FromLinkRegister("simpleobfs", NewSimpleObfs)
}

View File

@ -2,10 +2,12 @@ package simpleobfs
import (
"fmt"
"github.com/mzz2017/softwind/netproxy"
"net"
"net/url"
"strings"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/netproxy"
)
type ObfsType int
@ -25,14 +27,14 @@ type SimpleObfs struct {
}
// NewSimpleobfs returns a simpleobfs proxy.
func NewSimpleObfs(s string, d netproxy.Dialer) (*SimpleObfs, error) {
u, err := url.Parse(s)
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, fmt.Errorf("simpleobfs: %w", err)
return nil, nil, fmt.Errorf("simpleobfs: %w", err)
}
t := &SimpleObfs{
dialer: d,
dialer: nextDialer,
addr: u.Host,
}
query := u.Query()
@ -46,14 +48,19 @@ func NewSimpleObfs(s string, d netproxy.Dialer) (*SimpleObfs, error) {
case "tls":
t.obfstype = TLS
default:
return nil, fmt.Errorf("unsupported obfs type %v", obfstype)
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, nil
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) {

View File

@ -0,0 +1,8 @@
package tls
import "github.com/daeuniverse/dae/component/outbound/dialer"
func init() {
dialer.FromLinkRegister("tls", NewTls)
dialer.FromLinkRegister("utls", NewTls)
}

View File

@ -4,7 +4,10 @@ import (
"crypto/tls"
"fmt"
"net/url"
"strconv"
"strings"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/mzz2017/softwind/netproxy"
utls "github.com/refraction-networking/utls"
)
@ -22,36 +25,57 @@ type Tls struct {
}
// NewTls returns a Tls infra.
func NewTls(s string, d netproxy.Dialer) (*Tls, error) {
u, err := url.Parse(s)
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, fmt.Errorf("NewTls: %w", err)
}
t := &Tls{
dialer: d,
addr: u.Host,
tlsImplentation: u.Scheme,
return nil, nil, fmt.Errorf("NewTls: %w", err)
}
query := u.Query()
t.serverName = query.Get("sni")
t.utlsImitate = query.Get("utlsImitate")
// skipVerify
if query.Get("allowInsecure") == "true" || query.Get("allowInsecure") == "1" ||
query.Get("skipVerify") == "true" || query.Get("skipVerify") == "1" {
t.skipVerify = true
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, nil
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) {
@ -61,7 +85,7 @@ func (s *Tls) Dial(network, addr string) (c netproxy.Conn, err error) {
}
switch magicNetwork.Network {
case "tcp":
rc, err := s.dialer.Dial(network, addr)
rc, err := s.dialer.Dial(network, s.addr)
if err != nil {
return nil, fmt.Errorf("[Tls]: dial to %s: %w", s.addr, err)
}

View File

@ -0,0 +1,10 @@
package ws
import (
"github.com/daeuniverse/dae/component/outbound/dialer"
)
func init() {
dialer.FromLinkRegister("ws", NewWs)
dialer.FromLinkRegister("wss", NewWs)
}

View File

@ -7,7 +7,9 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/gorilla/websocket"
"github.com/mzz2017/softwind/netproxy"
)
@ -21,14 +23,14 @@ type Ws struct {
}
// NewWs returns a Ws infra.
func NewWs(s string, d netproxy.Dialer) (*Ws, error) {
u, err := url.Parse(s)
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, fmt.Errorf("NewWs: %w", err)
return nil, nil, fmt.Errorf("NewWs: %w", err)
}
t := &Ws{
dialer: d,
dialer: nextDialer,
}
query := u.Query()
@ -45,13 +47,31 @@ func NewWs(s string, d netproxy.Dialer) (*Ws, error) {
}
t.wsAddr = wsUrl.String() + u.Path
if u.Scheme == "wss" {
skipVerify, _ := strconv.ParseBool(query.Get("allowInsecure"))
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: skipVerify,
InsecureSkipVerify: allowInsecure || option.AllowInsecure,
}
if len(query.Get("alpn")) > 0 {
t.tlsClientConfig.NextProtos = strings.Split(query.Get("alpn"), ",")
}
}
return t, nil
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) {

View File

@ -256,15 +256,19 @@ func NewControlPlane(
sniffingTimeout = 0
}
disableKernelAliveCallback := dialMode != consts.DialMode_Ip
_direct, directProperty := dialer.NewDirectDialer(option, true)
direct := dialer.NewDialer(_direct, option, dialer.InstanceOption{CheckEnabled: false}, directProperty)
_block, blockProperty := dialer.NewBlockDialer(option, func() { /*Dialer Outbound*/ })
block := dialer.NewDialer(_block, option, dialer.InstanceOption{CheckEnabled: false}, blockProperty)
outbounds := []*outbound.DialerGroup{
outbound.NewDialerGroup(option, consts.OutboundDirect.String(),
[]*dialer.Dialer{dialer.NewDirectDialer(option, true)}, []*dialer.Annotation{{}},
[]*dialer.Dialer{direct}, []*dialer.Annotation{{}},
outbound.DialerSelectionPolicy{
Policy: consts.DialerSelectionPolicy_Fixed,
FixedIndex: 0,
}, core.outboundAliveChangeCallback(0, disableKernelAliveCallback)),
outbound.NewDialerGroup(option, consts.OutboundBlock.String(),
[]*dialer.Dialer{dialer.NewBlockDialer(option, func() { /*Dialer Outbound*/ })}, []*dialer.Annotation{{}},
[]*dialer.Dialer{block}, []*dialer.Annotation{{}},
outbound.DialerSelectionPolicy{
Policy: consts.DialerSelectionPolicy_Fixed,
FixedIndex: 0,

View File

@ -112,6 +112,7 @@ node {
mylink: 'ss://LINK'
node1: 'vmess://LINK'
node2: 'vless://LINK'
chains: 'tuic://LINK -> vmess://LINK'
}
# See https://github.com/daeuniverse/dae/blob/main/docs/en/configuration/dns.md for full examples.

2
go.mod
View File

@ -12,7 +12,7 @@ require (
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/mzz2017/softwind v0.0.0-20230722080658-973f985df174
github.com/mzz2017/softwind v0.0.0-20230723115304-666ec11098b8
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/safchain/ethtool v0.3.0
github.com/sirupsen/logrus v1.9.3

4
go.sum
View File

@ -91,8 +91,8 @@ github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B
github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI=
github.com/mzz2017/quic-go v0.0.0-20230706143320-cc858d4932b7 h1:9zmZilN02x3byMB2X3x+B4iyKHkucv70WA4hsyZkjo8=
github.com/mzz2017/quic-go v0.0.0-20230706143320-cc858d4932b7/go.mod h1:3H6d55CEofIWWr3gQThiB27+hA3WG5tATtPovzEYPAA=
github.com/mzz2017/softwind v0.0.0-20230722080658-973f985df174 h1:I4JKJSOCxUtFgiMB5VpoqdC70tncD1UkKd2Qvi5/A0o=
github.com/mzz2017/softwind v0.0.0-20230722080658-973f985df174/go.mod h1:Fz8fgR7/dbnfR6RLpeOMkUDyebq4xShdmjj+cE5jnJ4=
github.com/mzz2017/softwind v0.0.0-20230723115304-666ec11098b8 h1:T/VA4g5qkPeZFvZ+gkx4SK9wijDSOgFwumEZAxdHZ8U=
github.com/mzz2017/softwind v0.0.0-20230723115304-666ec11098b8/go.mod h1:Fz8fgR7/dbnfR6RLpeOMkUDyebq4xShdmjj+cE5jnJ4=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=