mirror of
https://github.com/daeuniverse/dae.git
synced 2025-01-27 08:00:38 +07:00
180 lines
4.7 KiB
Go
180 lines
4.7 KiB
Go
package trojan
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/mzz2017/softwind/protocol"
|
|
"github.com/mzz2017/softwind/transport/grpc"
|
|
"github.com/v2rayA/dae/common"
|
|
"github.com/v2rayA/dae/component/outbound/dialer"
|
|
"github.com/v2rayA/dae/component/outbound/transport/tls"
|
|
"github.com/v2rayA/dae/component/outbound/transport/ws"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
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, iOption dialer.InstanceOption, link string) (*dialer.Dialer, error) {
|
|
s, err := ParseTrojanURL(link, option)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.Dialer(option, iOption)
|
|
}
|
|
|
|
func (s *Trojan) Dialer(option *dialer.GlobalOption, iOption dialer.InstanceOption) (*dialer.Dialer, error) {
|
|
d := dialer.FullconeDirect // Trojan Proxy supports full-cone.
|
|
u := url.URL{
|
|
Scheme: "tls",
|
|
Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
|
|
RawQuery: url.Values{
|
|
"sni": []string{s.Sni},
|
|
"allowInsecure": []string{common.BoolToString(s.AllowInsecure)},
|
|
}.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,
|
|
AllowInsecure: s.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, 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, option, iOption, s.Name, s.Protocol, s.ExportToURL()), nil
|
|
}
|
|
|
|
func ParseTrojanURL(u string, option *dialer.GlobalOption) (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 && option.AllowInsecure {
|
|
allowInsecure = true
|
|
}
|
|
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()
|
|
}
|