From 68269fd499a1bddb75e74e98041f5a6e5142cad0 Mon Sep 17 00:00:00 2001 From: nite07 Date: Sat, 9 Mar 2024 17:01:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Hysteria=20?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/proxy.go | 6 ++++ model/proxy_hysteria.go | 53 +++++++++++++++++++++++++++ parser/hysteria.go | 79 +++++++++++++++++++++++++++++++++++++++++ parser/hysteria2.go | 7 ++-- utils/proxy.go | 5 ++- 5 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 model/proxy_hysteria.go create mode 100644 parser/hysteria.go diff --git a/model/proxy.go b/model/proxy.go index 6c4f9e0..c1e6e8d 100644 --- a/model/proxy.go +++ b/model/proxy.go @@ -50,6 +50,12 @@ type Proxy struct { CustomCA string `yaml:"ca,omitempty"` CustomCAString string `yaml:"ca-str,omitempty"` CWND int `yaml:"cwnd,omitempty"` + Auth string `yaml:"auth,omitempty"` + ReceiveWindowConn int `yaml:"recv-window-conn,omitempty"` + ReceiveWindow int `yaml:"recv-window,omitempty"` + DisableMTUDiscovery bool `yaml:"disable-mtu-discovery,omitempty"` + FastOpen bool `yaml:"fast-open,omitempty"` + HopInterval int `yaml:"hop-interval,omitempty"` } func (p Proxy) MarshalYAML() (interface{}, error) { diff --git a/model/proxy_hysteria.go b/model/proxy_hysteria.go new file mode 100644 index 0000000..21e1e1e --- /dev/null +++ b/model/proxy_hysteria.go @@ -0,0 +1,53 @@ +package model + +type Hysteria struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + Server string `yaml:"server"` + Port int `yaml:"port,omitempty"` + Ports string `yaml:"ports,omitempty"` + Protocol string `yaml:"protocol,omitempty"` + ObfsProtocol string `yaml:"obfs-protocol,omitempty"` // compatible with Stash + Up string `yaml:"up"` + UpSpeed int `yaml:"up-speed,omitempty"` // compatible with Stash + Down string `yaml:"down"` + DownSpeed int `yaml:"down-speed,omitempty"` // compatible with Stash + Auth string `yaml:"auth,omitempty"` + AuthString string `yaml:"auth-str,omitempty"` + Obfs string `yaml:"obfs,omitempty"` + SNI string `yaml:"sni,omitempty"` + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"` + Fingerprint string `yaml:"fingerprint,omitempty"` + ALPN []string `yaml:"alpn,omitempty"` + CustomCA string `yaml:"ca,omitempty"` + CustomCAString string `yaml:"ca-str,omitempty"` + ReceiveWindowConn int `yaml:"recv-window-conn,omitempty"` + ReceiveWindow int `yaml:"recv-window,omitempty"` + DisableMTUDiscovery bool `yaml:"disable-mtu-discovery,omitempty"` + FastOpen bool `yaml:"fast-open,omitempty"` + HopInterval int `yaml:"hop-interval,omitempty"` +} + +func ProxyToHysteria(p Proxy) Hysteria { + return Hysteria{ + Type: "hysteria", + Name: p.Name, + Server: p.Server, + Port: p.Port, + Up: p.Up, + Down: p.Down, + Auth: p.Auth, + Obfs: p.Obfs, + SNI: p.Sni, + SkipCertVerify: p.SkipCertVerify, + Fingerprint: p.Fingerprint, + ALPN: p.Alpn, + CustomCA: p.CustomCA, + CustomCAString: p.CustomCAString, + ReceiveWindowConn: p.ReceiveWindowConn, + ReceiveWindow: p.ReceiveWindow, + DisableMTUDiscovery: p.DisableMTUDiscovery, + FastOpen: p.FastOpen, + HopInterval: p.HopInterval, + } +} diff --git a/parser/hysteria.go b/parser/hysteria.go new file mode 100644 index 0000000..c7c9942 --- /dev/null +++ b/parser/hysteria.go @@ -0,0 +1,79 @@ +package parser + +import ( + "errors" + "net/url" + "strconv" + "strings" + "sub2clash/model" +) + +//hysteria://host:port?protocol=udp&auth=123456&peer=sni.domain&insecure=1&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks +// +//- host: hostname or IP address of the server to connect to (required) +//- port: port of the server to connect to (required) +//- protocol: protocol to use ("udp", "wechat-video", "faketcp") (optional, default: "udp") +//- auth: authentication payload (string) (optional) +//- peer: SNI for TLS (optional) +//- insecure: ignore certificate errors (optional) +//- upmbps: upstream bandwidth in Mbps (required) +//- downmbps: downstream bandwidth in Mbps (required) +//- alpn: QUIC ALPN (optional) +//- obfs: Obfuscation mode (optional, empty or "xplus") +//- obfsParam: Obfuscation password (optional) +//- remarks: remarks (optional) + +func ParseHysteria(proxy string) (model.Proxy, error) { + // 判断是否以 hysteria:// 开头 + if !strings.HasPrefix(proxy, "hysteria://") { + return model.Proxy{}, errors.New("invalid hysteria Url") + } + // 分割 + parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria://"), "?", 2) + serverInfo := strings.SplitN(parts[0], ":", 2) + if len(serverInfo) != 2 { + return model.Proxy{}, errors.New("invalid hysteria Url") + } + params, err := url.ParseQuery(parts[1]) + if err != nil { + return model.Proxy{}, errors.New("invalid hysteria Url") + } + host := serverInfo[0] + port, err := strconv.Atoi(serverInfo[1]) + if err != nil { + return model.Proxy{}, errors.New("invalid hysteria Url") + } + protocol := params.Get("protocol") + auth := params.Get("auth") + peer := params.Get("peer") + insecure := params.Get("insecure") + upmbps := params.Get("upmbps") + downmbps := params.Get("downmbps") + alpn := params.Get("alpn") + obfs := params.Get("obfs") + obfsParam := params.Get("obfsParam") + remarks := "" + if strings.Contains(parts[1], "#") { + r := strings.Split(parts[1], "#") + remarks = r[len(r)-1] + } else { + remarks = serverInfo[0] + ":" + serverInfo[1] + } + // 返回结果 + result := model.Proxy{ + Type: "hysteria", + Name: remarks, + Server: host, + Port: port, + Up: upmbps, + Down: downmbps, + Auth: auth, + Obfs: obfs, + Sni: peer, + SkipCertVerify: insecure == "1", + Alpn: strings.Split(alpn, ","), + ObfsParam: obfsParam, + Protocol: protocol, + } + return result, nil +} diff --git a/parser/hysteria2.go b/parser/hysteria2.go index e627b1d..89814e2 100644 --- a/parser/hysteria2.go +++ b/parser/hysteria2.go @@ -8,16 +8,15 @@ import ( "sub2clash/model" ) +// hysteria2://letmein@example.com/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com + func ParseHysteria2(proxy string) (model.Proxy, error) { // 判断是否以 hysteria2:// 开头 - if !strings.HasPrefix(proxy, "hysteria2://") { + if !strings.HasPrefix(proxy, "hysteria2://") && !strings.HasPrefix(proxy, "hy2://") { return model.Proxy{}, errors.New("invalid hysteria2 Url") } // 分割 parts := strings.SplitN(strings.TrimPrefix(proxy, "hysteria2://"), "@", 2) - if len(parts) != 2 { - return model.Proxy{}, errors.New("invalid hysteria2 Url") - } // 分割 serverInfo := strings.SplitN(parts[1], "/?", 2) serverAndPort := strings.SplitN(serverInfo[0], ":", 2) diff --git a/utils/proxy.go b/utils/proxy.go index 7766ec8..16ed5ff 100644 --- a/utils/proxy.go +++ b/utils/proxy.go @@ -127,9 +127,12 @@ func ParseProxy(proxies ...string) []model.Proxy { if strings.HasPrefix(proxy, "ssr://") { proxyItem, err = parser.ParseShadowsocksR(proxy) } - if strings.HasPrefix(proxy, "hysteria2://") { + if strings.HasPrefix(proxy, "hysteria2://") || strings.HasPrefix(proxy, "hy2://") { proxyItem, err = parser.ParseHysteria2(proxy) } + if strings.HasPrefix(proxy, "hysteria://") { + proxyItem, err = parser.ParseHysteria(proxy) + } if err == nil { result = append(result, proxyItem) } else {