182 lines
6.1 KiB
Go
182 lines
6.1 KiB
Go
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// Extended Mihomo protocols use their documented/simple sharing forms where
|
|
// available. These links are intended for clients that understand the scheme;
|
|
// they are not silently downgraded to another protocol.
|
|
func snellURIs(name, host, port string, cfg map[string]interface{}) ([]string, error) {
|
|
psk, _ := cfg["psk"].(string)
|
|
if psk == "" {
|
|
return nil, fmt.Errorf("snell listener requires psk for URI export")
|
|
}
|
|
params := map[string]string{"psk": psk}
|
|
if v := stringValue(cfg["version"], "4"); v != "" {
|
|
params["version"] = v
|
|
}
|
|
if obfs, ok := cfg["obfs-opts"].(map[string]interface{}); ok {
|
|
if mode := stringValue(obfs["mode"], ""); mode != "" {
|
|
params["obfs"] = mode
|
|
}
|
|
if host := stringValue(obfs["host"], ""); host != "" {
|
|
params["obfs-host"] = host
|
|
}
|
|
}
|
|
if b, ok := cfg["udp"].(bool); ok && b {
|
|
params["udp"] = "1"
|
|
}
|
|
return []string{addName(query("snell://"+net.JoinHostPort(host, port), params), name)}, nil
|
|
}
|
|
|
|
func shadowQUICURIs(name, host, port string, cfg map[string]interface{}) ([]string, error) {
|
|
// ShadowQUIC uses array-form users: [{username, password}] per official docs.
|
|
rows := userRows(cfg)
|
|
if len(rows) == 0 {
|
|
return nil, fmt.Errorf("shadowquic listener requires at least one user for URI export")
|
|
}
|
|
result := make([]string, 0, len(rows))
|
|
for _, row := range rows {
|
|
username, _ := row["username"].(string)
|
|
password, _ := row["password"].(string)
|
|
if password == "" {
|
|
return nil, fmt.Errorf("shadowquic user %q has empty password", username)
|
|
}
|
|
params := map[string]string{}
|
|
sni, skip := resolveURITLS(cfg, host)
|
|
if sni != "" {
|
|
params["sni"] = sni
|
|
}
|
|
if skip {
|
|
params["insecure"] = "1"
|
|
params["allowInsecure"] = "1"
|
|
params["skip-cert-verify"] = "1"
|
|
}
|
|
for _, key := range []string{"alpn", "quic-versions", "congestion-controller", "up", "down", "cwnd", "bbr-profile", "max-datagram-frame-size", "max-open-streams", "recv-window-conn", "recv-window"} {
|
|
if v := stringValue(cfg[key], ""); v != "" {
|
|
params[key] = v
|
|
}
|
|
}
|
|
if b, ok := cfg["zero-rtt"].(bool); ok && b {
|
|
params["zero-rtt"] = "1"
|
|
}
|
|
if b, ok := cfg["udp-over-stream"].(bool); ok && b {
|
|
params["udp-over-stream"] = "1"
|
|
}
|
|
if b, ok := cfg["disable-mtu-discovery"].(bool); ok && b {
|
|
params["disable-mtu-discovery"] = "1"
|
|
}
|
|
result = append(result, addName(query("shadowquic://"+url.PathEscape(username)+":"+url.PathEscape(password)+"@"+net.JoinHostPort(host, port), params), name))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func mieruURIs(name, host, port string, cfg map[string]interface{}) ([]string, error) {
|
|
users := userMap(cfg)
|
|
if len(users) == 0 {
|
|
return nil, fmt.Errorf("mieru listener requires at least one user for URI export")
|
|
}
|
|
result := make([]string, 0, len(users))
|
|
for username, raw := range users {
|
|
password, ok := raw.(string)
|
|
if !ok || password == "" {
|
|
return nil, fmt.Errorf("mieru user %q has empty password", username)
|
|
}
|
|
params := map[string]string{}
|
|
for _, key := range []string{"transport", "multiplexing", "handshake-mode", "traffic-pattern"} {
|
|
if v := stringValue(cfg[key], ""); v != "" {
|
|
params[key] = v
|
|
}
|
|
}
|
|
if v := stringValue(cfg["port-range"], ""); v != "" {
|
|
params["portRange"] = v
|
|
}
|
|
result = append(result, addName(query("mieru://"+url.PathEscape(username)+":"+url.PathEscape(password)+"@"+net.JoinHostPort(host, port), params), name))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func sudokuURIs(name, host, port string, cfg map[string]interface{}) ([]string, error) {
|
|
key, _ := cfg["key"].(string)
|
|
if key == "" {
|
|
return nil, fmt.Errorf("sudoku listener requires key for URI export")
|
|
}
|
|
params := map[string]string{"key": key}
|
|
for _, keyName := range []string{"aead-method", "padding-min", "padding-max", "table-type", "custom-table", "fallback", "handshake-timeout"} {
|
|
if v := stringValue(cfg[keyName], ""); v != "" {
|
|
params[keyName] = v
|
|
}
|
|
}
|
|
if tables, ok := cfg["custom-tables"].([]interface{}); ok && len(tables) > 0 {
|
|
values := make([]string, 0, len(tables))
|
|
for _, raw := range tables {
|
|
if v := stringValue(raw, ""); v != "" {
|
|
values = append(values, v)
|
|
}
|
|
}
|
|
if len(values) > 0 {
|
|
params["custom-tables"] = strings.Join(values, ",")
|
|
}
|
|
}
|
|
if tables, ok := cfg["custom-tables"].([]string); ok && len(tables) > 0 {
|
|
params["custom-tables"] = strings.Join(tables, ",")
|
|
}
|
|
if b, ok := cfg["enable-pure-downlink"].(bool); ok && b {
|
|
params["enable-pure-downlink"] = "1"
|
|
}
|
|
if hm, ok := cfg["httpmask"].(map[string]interface{}); ok {
|
|
if b, ok := hm["disable"].(bool); ok && b {
|
|
params["httpmask-disable"] = "1"
|
|
}
|
|
if v := stringValue(hm["mode"], ""); v != "" {
|
|
params["httpmask-mode"] = v
|
|
}
|
|
if v := stringValue(hm["path-root"], ""); v != "" {
|
|
params["httpmask-path-root"] = v
|
|
}
|
|
}
|
|
return []string{addName(query("sudoku://"+net.JoinHostPort(host, port), params), name)}, nil
|
|
}
|
|
|
|
func trustTunnelURIs(name, host, port string, cfg map[string]interface{}) ([]string, error) {
|
|
// TrustTunnel uses array-form users: [{username, password}] per official docs.
|
|
rows := userRows(cfg)
|
|
if len(rows) == 0 {
|
|
return nil, fmt.Errorf("trusttunnel listener requires at least one user for URI export")
|
|
}
|
|
result := make([]string, 0, len(rows))
|
|
for _, row := range rows {
|
|
username, _ := row["username"].(string)
|
|
password, _ := row["password"].(string)
|
|
if password == "" {
|
|
return nil, fmt.Errorf("trusttunnel user %q has empty password", username)
|
|
}
|
|
params := map[string]string{}
|
|
sni, skip := resolveURITLS(cfg, host)
|
|
if sni != "" {
|
|
params["sni"] = sni
|
|
}
|
|
if skip {
|
|
params["skip-cert-verify"] = "1"
|
|
params["insecure"] = "1"
|
|
params["allowInsecure"] = "1"
|
|
}
|
|
for _, key := range []string{"client-fingerprint", "health-check", "alpn", "congestion-controller", "bbr-profile", "max-connections", "min-streams", "max-streams"} {
|
|
if v := stringValue(cfg[key], ""); v != "" {
|
|
params[key] = v
|
|
}
|
|
}
|
|
for _, key := range []string{"udp", "quic", "name-cert-verify"} {
|
|
if b, ok := cfg[key].(bool); ok && b {
|
|
params[key] = "1"
|
|
}
|
|
}
|
|
result = append(result, addName(query("trusttunnel://"+url.PathEscape(username)+":"+url.PathEscape(password)+"@"+net.JoinHostPort(host, port), params), name))
|
|
}
|
|
return result, nil
|
|
}
|