mirror of
https://github.com/daeuniverse/dae.git
synced 2025-01-10 23:38:14 +07:00
233 lines
6.2 KiB
Go
233 lines
6.2 KiB
Go
package trojan
|
|
|
|
import (
|
|
"fmt"
|
|
"foo/common"
|
|
"foo/component/outbound/dialer"
|
|
"foo/component/outbound/dialer/transport/tls"
|
|
"foo/component/outbound/dialer/transport/ws"
|
|
"github.com/mzz2017/softwind/protocol"
|
|
"github.com/mzz2017/softwind/transport/grpc"
|
|
"gopkg.in/yaml.v3"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func init() {
|
|
dialer.FromLinkRegister("trojan", NewTrojan)
|
|
dialer.FromLinkRegister("trojan-go", NewTrojan)
|
|
dialer.FromClashRegister("trojan", NewTrojanFromClashObj)
|
|
}
|
|
|
|
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(link string) (*dialer.Dialer, error) {
|
|
s, err := ParseTrojanURL(link)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.Dialer()
|
|
}
|
|
|
|
func NewTrojanFromClashObj(o *yaml.Node) (*dialer.Dialer, error) {
|
|
s, err := ParseClash(o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.Dialer()
|
|
}
|
|
|
|
func (s *Trojan) Dialer() (*dialer.Dialer, error) {
|
|
d := dialer.SymmetricDirect
|
|
u := url.URL{
|
|
Scheme: "tls",
|
|
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
|
RawQuery: url.Values{
|
|
"sni": []string{s.Sni},
|
|
}.Encode(),
|
|
}
|
|
var err error
|
|
if s.Type != "grpc" {
|
|
// grpc contains tls
|
|
if d, err = tls.NewTls(u.String(), d); err != nil {
|
|
return 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(u.String(), d); err != nil {
|
|
return nil, err
|
|
}
|
|
case "grpc":
|
|
serviceName := s.ServiceName
|
|
if serviceName == "" {
|
|
serviceName = "GunService"
|
|
}
|
|
d = &grpc.Dialer{
|
|
NextDialer: &protocol.DialerConverter{Dialer: d},
|
|
ServiceName: serviceName,
|
|
ServerName: s.Sni,
|
|
}
|
|
}
|
|
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, 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, err
|
|
}
|
|
return dialer.NewDialer(d, true, s.Name, s.Protocol, 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 := t.Query().Get("allowInsecure")
|
|
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 == "1" || allowInsecure == "true",
|
|
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
|
|
}
|
|
data.AllowInsecure = false
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func ParseClash(o *yaml.Node) (data *Trojan, err error) {
|
|
type WSOptions struct {
|
|
Path string `yaml:"path,omitempty"`
|
|
Headers map[string]string `yaml:"headers,omitempty"`
|
|
MaxEarlyData int `yaml:"max-early-data,omitempty"`
|
|
EarlyDataHeaderName string `yaml:"early-data-header-name,omitempty"`
|
|
}
|
|
type GrpcOptions struct {
|
|
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
|
}
|
|
type TrojanOption struct {
|
|
Name string `yaml:"name"`
|
|
Server string `yaml:"server"`
|
|
Port int `yaml:"port"`
|
|
Password string `yaml:"password"`
|
|
ALPN []string `yaml:"alpn,omitempty"`
|
|
SNI string `yaml:"sni,omitempty"`
|
|
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
|
|
UDP bool `yaml:"udp,omitempty"`
|
|
Network string `yaml:"network,omitempty"`
|
|
GrpcOpts GrpcOptions `yaml:"grpc-opts,omitempty"`
|
|
WSOpts WSOptions `yaml:"ws-opts,omitempty"`
|
|
}
|
|
var option TrojanOption
|
|
if err = o.Decode(&option); err != nil {
|
|
return nil, err
|
|
}
|
|
proto := "trojan"
|
|
if option.Network != "" && option.Network != "origin" {
|
|
proto = "trojan-go"
|
|
}
|
|
return &Trojan{
|
|
Name: option.Name,
|
|
Server: option.Server,
|
|
Port: option.Port,
|
|
Password: option.Password,
|
|
Sni: option.SNI,
|
|
Type: option.Network,
|
|
Encryption: "",
|
|
Host: option.WSOpts.Headers["Host"],
|
|
Path: option.WSOpts.Path,
|
|
AllowInsecure: option.SkipCertVerify,
|
|
ServiceName: option.GrpcOpts.GrpcServiceName,
|
|
Protocol: proto,
|
|
}, 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()
|
|
}
|