mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-06-08 13:29:56 +00:00
feat: client config URI support
This commit is contained in:
parent
0dbd6af683
commit
07b7f14bef
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -193,8 +194,79 @@ func (c *clientConfig) fillFastOpen(hyConfig *client.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// URI generates a URI for sharing the config with others.
|
||||
// Note that only the bare minimum of information required to
|
||||
// connect to the server is included in the URI, specifically:
|
||||
// - server address
|
||||
// - authentication
|
||||
// - obfuscation type
|
||||
// - obfuscation password
|
||||
// - TLS SNI
|
||||
// - TLS insecure
|
||||
func (c *clientConfig) URI() string {
|
||||
q := url.Values{}
|
||||
switch strings.ToLower(c.Obfs.Type) {
|
||||
case "salamander":
|
||||
q.Set("obfs", "salamander")
|
||||
q.Set("obfs-password", c.Obfs.Salamander.Password)
|
||||
}
|
||||
if c.TLS.SNI != "" {
|
||||
q.Set("sni", c.TLS.SNI)
|
||||
}
|
||||
if c.TLS.Insecure {
|
||||
q.Set("insecure", "1")
|
||||
}
|
||||
var user *url.Userinfo
|
||||
if c.Auth != "" {
|
||||
user = url.User(c.Auth)
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: "hysteria2",
|
||||
User: user,
|
||||
Host: c.Server,
|
||||
Path: "/",
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// parseURI tries to parse the server address field as a URI,
|
||||
// and fills the config with the information contained in the URI.
|
||||
// Returns whether the server address field is a valid URI.
|
||||
// This allows a user to use put a URI as the server address and
|
||||
// omit the fields that are already contained in the URI.
|
||||
func (c *clientConfig) parseURI() bool {
|
||||
u, err := url.Parse(c.Server)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if u.Scheme != "hysteria2" && u.Scheme != "hy2" {
|
||||
return false
|
||||
}
|
||||
if u.User != nil {
|
||||
c.Auth = u.User.String()
|
||||
}
|
||||
c.Server = u.Host
|
||||
q := u.Query()
|
||||
if obfsType := q.Get("obfs"); obfsType != "" {
|
||||
c.Obfs.Type = obfsType
|
||||
switch strings.ToLower(obfsType) {
|
||||
case "salamander":
|
||||
c.Obfs.Salamander.Password = q.Get("obfs-password")
|
||||
}
|
||||
}
|
||||
if sni := q.Get("sni"); sni != "" {
|
||||
c.TLS.SNI = sni
|
||||
}
|
||||
if insecure, err := strconv.ParseBool(q.Get("insecure")); err == nil {
|
||||
c.TLS.Insecure = insecure
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Config validates the fields and returns a ready-to-use Hysteria client config
|
||||
func (c *clientConfig) Config() (*client.Config, error) {
|
||||
c.parseURI()
|
||||
hyConfig := &client.Config{}
|
||||
fillers := []func(*client.Config) error{
|
||||
c.fillConnFactory,
|
||||
@ -213,32 +285,6 @@ func (c *clientConfig) Config() (*client.Config, error) {
|
||||
return hyConfig, nil
|
||||
}
|
||||
|
||||
// ShareURI generates a URI for sharing the config with others.
|
||||
// Note that only the fields necessary for a client to connect to the server are included.
|
||||
// It doesn't include local modes, for example.
|
||||
func (c *clientConfig) ShareURI() string {
|
||||
q := url.Values{}
|
||||
switch strings.ToLower(c.Obfs.Type) {
|
||||
case "salamander":
|
||||
q.Set("obfs", "salamander")
|
||||
q.Set("obfs-password", c.Obfs.Salamander.Password)
|
||||
}
|
||||
if c.TLS.SNI != "" {
|
||||
q.Set("sni", c.TLS.SNI)
|
||||
}
|
||||
if c.TLS.Insecure {
|
||||
q.Set("insecure", "1")
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: "hysteria2",
|
||||
User: url.User(c.Auth),
|
||||
Host: c.Server,
|
||||
Path: "/",
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func runClient(cmd *cobra.Command, args []string) {
|
||||
logger.Info("client mode")
|
||||
|
||||
@ -260,7 +306,7 @@ func runClient(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
uri := config.ShareURI()
|
||||
uri := config.URI()
|
||||
logger.Info("use this URI to share your server", zap.String("uri", uri))
|
||||
if showQR {
|
||||
printQR(uri)
|
||||
|
@ -98,3 +98,80 @@ func TestClientConfig(t *testing.T) {
|
||||
t.Fatal("parsed client config is not equal to expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientConfigURI tests URI-related functions of clientConfig
|
||||
func TestClientConfigURI(t *testing.T) {
|
||||
tests := []struct {
|
||||
uri string
|
||||
uriOK bool
|
||||
config *clientConfig
|
||||
}{
|
||||
{
|
||||
uri: "hysteria2://god@zilla.jp/",
|
||||
uriOK: true,
|
||||
config: &clientConfig{
|
||||
Server: "zilla.jp",
|
||||
Auth: "god",
|
||||
},
|
||||
},
|
||||
{
|
||||
uri: "hysteria2://noauth.com/?insecure=1&obfs=salamander&obfs-password=66ccff&sni=crap.cc",
|
||||
uriOK: true,
|
||||
config: &clientConfig{
|
||||
Server: "noauth.com",
|
||||
Auth: "",
|
||||
Obfs: struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Salamander struct {
|
||||
Password string `mapstructure:"password"`
|
||||
} `mapstructure:"salamander"`
|
||||
}{
|
||||
Type: "salamander",
|
||||
Salamander: struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}{
|
||||
Password: "66ccff",
|
||||
},
|
||||
},
|
||||
TLS: struct {
|
||||
SNI string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
CA string `mapstructure:"ca"`
|
||||
}{
|
||||
SNI: "crap.cc",
|
||||
Insecure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
uri: "invalid.bs",
|
||||
uriOK: false,
|
||||
config: nil,
|
||||
},
|
||||
{
|
||||
uri: "https://www.google.com/search?q=test",
|
||||
uriOK: false,
|
||||
config: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.uri, func(t *testing.T) {
|
||||
// Test parseURI
|
||||
nc := &clientConfig{Server: test.uri}
|
||||
if ok := nc.parseURI(); ok != test.uriOK {
|
||||
t.Fatal("unexpected parseURI ok result")
|
||||
}
|
||||
if test.uriOK && !reflect.DeepEqual(nc, test.config) {
|
||||
t.Fatal("unexpected parsed client config from URI")
|
||||
}
|
||||
// Test URI generation
|
||||
if test.config == nil {
|
||||
// config is nil if parseURI is expected to fail
|
||||
return
|
||||
}
|
||||
if uri := test.config.URI(); uri != test.uri {
|
||||
t.Fatalf("generated URI mismatch: %s != %s", uri, test.uri)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user