From 097c2d22417d3a92a4eb7c0ea7273e8ca5a0e8ee Mon Sep 17 00:00:00 2001 From: zu1k Date: Thu, 20 Aug 2020 22:30:59 +0800 Subject: [PATCH] add trojan support --- pkg/getter/base.go | 2 + pkg/proxy/trojan.go | 147 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 pkg/proxy/trojan.go diff --git a/pkg/getter/base.go b/pkg/getter/base.go index eab4a094..7deaeb4c 100644 --- a/pkg/getter/base.go +++ b/pkg/getter/base.go @@ -39,6 +39,8 @@ func String2Proxy(link string) proxy.Proxy { data, err = proxy.ParseVmessLink(link) } else if strings.HasPrefix(link, "ss://") { data, err = proxy.ParseSSLink(link) + } else if strings.HasPrefix(link, "trojan://") { + data, err = proxy.ParseTrojanLink(link) } if err != nil { return nil diff --git a/pkg/proxy/trojan.go b/pkg/proxy/trojan.go new file mode 100644 index 00000000..adb279e2 --- /dev/null +++ b/pkg/proxy/trojan.go @@ -0,0 +1,147 @@ +package proxy + +import ( + "encoding/json" + "errors" + "math/rand" + "net" + "net/url" + "regexp" + "strconv" + "strings" +) + +var ( + ErrorNotTrojanink = errors.New("not a correct trojan link") +) + +type Trojan struct { + Base + Password string `yaml:"password" json:"password"` + ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"` + SNI string `yaml:"sni,omitempty" json:"sni,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty" json:"skip-cert-verify,omitempty"` + UDP bool `yaml:"udp,omitempty" json:"udp,omitempty"` +} + +/** + - name: "trojan" + type: trojan + server: server + port: 443 + password: yourpsk + # udp: true + # sni: example.com # aka server name + # alpn: + # - h2 + # - http/1.1 + # skip-cert-verify: true +*/ + +func (t Trojan) Identifier() string { + return net.JoinHostPort(t.Server, strconv.Itoa(t.Port)) + t.Password +} + +func (t Trojan) String() string { + data, err := json.Marshal(t) + if err != nil { + return "" + } + return string(data) +} + +func (t Trojan) ToClash() string { + data, err := json.Marshal(t) + if err != nil { + return "" + } + return "- " + string(data) +} + +func (t Trojan) ToSurge() string { + return "" +} + +func (t Trojan) Clone() Proxy { + return &t +} + +func ParseTrojanLink(link string) (*Trojan, error) { + if !strings.HasPrefix(link, "trojan://") || !strings.HasPrefix(link, "trojan-go://") { + return nil, ErrorNotSSRLink + } + + /** + trojan-go:// + $(trojan-password) + @ + trojan-host + : + port + /? + sni=$(tls-sni.com)& + type=$(original|ws|h2|h2+ws)& + host=$(websocket-host.com)& + path=$(/websocket/path)& + encryption=$(ss;aes-256-gcm;ss-password)& + plugin=$(...) + #$(descriptive-text) + */ + + uri, err := url.Parse(link) + if err != nil { + return nil, ErrorNotSSLink + } + + password := uri.User.Username() + password, _ = url.QueryUnescape(password) + + server := uri.Hostname() + port, _ := strconv.Atoi(uri.Port()) + + moreInfos := uri.Query() + sni := moreInfos.Get("sni") + sni, _ = url.QueryUnescape(sni) + transformType := moreInfos.Get("type") + transformType, _ = url.QueryUnescape(transformType) + host := moreInfos.Get("host") + host, _ = url.QueryUnescape(host) + path := moreInfos.Get("path") + path, _ = url.QueryUnescape(path) + + alpn := make([]string, 0) + if transformType == "h2" { + alpn = append(alpn, "h2") + } + + if port == 0 { + return nil, ErrorNotTrojanink + } + + return &Trojan{ + Base: Base{ + Name: strconv.Itoa(rand.Int()), + Server: server, + Port: port, + Type: "trojan", + }, + Password: password, + ALPN: alpn, + UDP: true, + SNI: host, + SkipCertVerify: true, + }, nil +} + +var ( + trojanPlainRe = regexp.MustCompile("trojan(-go)?://([A-Za-z0-9+/_&?=@:%.-])+") +) + +func GrepTrojanLinkFromString(text string) []string { + results := make([]string, 0) + texts := strings.Split(text, "trojan://") + for _, text := range texts { + results = append(results, ssPlainRe.FindAllString("trojan://"+text, -1)...) + } + return results +}