feat: support http and socks5 upstream node uris
This commit is contained in:
@@ -349,6 +349,18 @@ func buildNodeOutbound(tag, rawURI string, skipCertVerify bool) (option.Outbound
|
||||
return option.Outbound{}, fmt.Errorf("parse uri: %w", err)
|
||||
}
|
||||
switch strings.ToLower(parsed.Scheme) {
|
||||
case "http":
|
||||
opts, err := buildHTTPOptions(parsed, skipCertVerify)
|
||||
if err != nil {
|
||||
return option.Outbound{}, err
|
||||
}
|
||||
return option.Outbound{Type: C.TypeHTTP, Tag: tag, Options: &opts}, nil
|
||||
case "socks5":
|
||||
opts, err := buildSOCKSOptions(parsed)
|
||||
if err != nil {
|
||||
return option.Outbound{}, err
|
||||
}
|
||||
return option.Outbound{Type: C.TypeSOCKS, Tag: tag, Options: &opts}, nil
|
||||
case "vless":
|
||||
opts, err := buildVLESSOptions(parsed, skipCertVerify)
|
||||
if err != nil {
|
||||
@@ -390,6 +402,64 @@ func buildNodeOutbound(tag, rawURI string, skipCertVerify bool) (option.Outbound
|
||||
}
|
||||
}
|
||||
|
||||
func buildHTTPOptions(u *url.URL, skipCertVerify bool) (option.HTTPOutboundOptions, error) {
|
||||
query := u.Query()
|
||||
defaultPort := 80
|
||||
if strings.EqualFold(query.Get("security"), "tls") {
|
||||
defaultPort = 443
|
||||
}
|
||||
server, port, err := hostPort(u, defaultPort)
|
||||
if err != nil {
|
||||
return option.HTTPOutboundOptions{}, err
|
||||
}
|
||||
|
||||
opts := option.HTTPOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{Server: server, ServerPort: uint16(port)},
|
||||
}
|
||||
if u.User != nil {
|
||||
opts.Username = u.User.Username()
|
||||
if password, ok := u.User.Password(); ok {
|
||||
opts.Password = password
|
||||
}
|
||||
}
|
||||
if path := u.EscapedPath(); path != "" {
|
||||
opts.Path = path
|
||||
}
|
||||
if host := strings.TrimSpace(query.Get("host")); host != "" {
|
||||
opts.Headers = badoption.HTTPHeader{"Host": {host}}
|
||||
}
|
||||
if tlsOptions, err := buildTLSOptions(query, skipCertVerify); err != nil {
|
||||
return option.HTTPOutboundOptions{}, err
|
||||
} else if tlsOptions != nil {
|
||||
opts.OutboundTLSOptionsContainer = option.OutboundTLSOptionsContainer{TLS: tlsOptions}
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func buildSOCKSOptions(u *url.URL) (option.SOCKSOutboundOptions, error) {
|
||||
server, port, err := hostPort(u, 1080)
|
||||
if err != nil {
|
||||
return option.SOCKSOutboundOptions{}, err
|
||||
}
|
||||
|
||||
opts := option.SOCKSOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{Server: server, ServerPort: uint16(port)},
|
||||
Version: "5",
|
||||
}
|
||||
if u.User != nil {
|
||||
opts.Username = u.User.Username()
|
||||
if password, ok := u.User.Password(); ok {
|
||||
opts.Password = password
|
||||
}
|
||||
}
|
||||
if network := strings.ToLower(strings.TrimSpace(u.Query().Get("network"))); network != "" {
|
||||
opts.Network = option.NetworkList(network)
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func buildAnyTLSOptions(u *url.URL, skipCertVerify bool) (option.AnyTLSOutboundOptions, error) {
|
||||
password := ""
|
||||
if u.User != nil {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func TestBuildNodeOutboundSupportsSOCKS5(t *testing.T) {
|
||||
outbound, err := buildNodeOutbound("socks-node", "socks5://demo:secret@99.144.123.135:30350", false)
|
||||
if err != nil {
|
||||
t.Fatalf("buildNodeOutbound returned error: %v", err)
|
||||
}
|
||||
if outbound.Type != C.TypeSOCKS {
|
||||
t.Fatalf("outbound type = %q, want %q", outbound.Type, C.TypeSOCKS)
|
||||
}
|
||||
|
||||
opts, ok := outbound.Options.(*option.SOCKSOutboundOptions)
|
||||
if !ok {
|
||||
t.Fatalf("outbound options type = %T, want *option.SOCKSOutboundOptions", outbound.Options)
|
||||
}
|
||||
if opts.Server != "99.144.123.135" {
|
||||
t.Fatalf("server = %q, want %q", opts.Server, "99.144.123.135")
|
||||
}
|
||||
if opts.ServerPort != 30350 {
|
||||
t.Fatalf("server port = %d, want %d", opts.ServerPort, 30350)
|
||||
}
|
||||
if opts.Username != "demo" {
|
||||
t.Fatalf("username = %q, want %q", opts.Username, "demo")
|
||||
}
|
||||
if opts.Password != "secret" {
|
||||
t.Fatalf("password = %q, want %q", opts.Password, "secret")
|
||||
}
|
||||
if opts.Version != "5" {
|
||||
t.Fatalf("version = %q, want %q", opts.Version, "5")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNodeOutboundSupportsHTTP(t *testing.T) {
|
||||
outbound, err := buildNodeOutbound("http-node", "http://alice:wonderland@example.com:8080/proxy", false)
|
||||
if err != nil {
|
||||
t.Fatalf("buildNodeOutbound returned error: %v", err)
|
||||
}
|
||||
if outbound.Type != C.TypeHTTP {
|
||||
t.Fatalf("outbound type = %q, want %q", outbound.Type, C.TypeHTTP)
|
||||
}
|
||||
|
||||
opts, ok := outbound.Options.(*option.HTTPOutboundOptions)
|
||||
if !ok {
|
||||
t.Fatalf("outbound options type = %T, want *option.HTTPOutboundOptions", outbound.Options)
|
||||
}
|
||||
if opts.Server != "example.com" {
|
||||
t.Fatalf("server = %q, want %q", opts.Server, "example.com")
|
||||
}
|
||||
if opts.ServerPort != 8080 {
|
||||
t.Fatalf("server port = %d, want %d", opts.ServerPort, 8080)
|
||||
}
|
||||
if opts.Username != "alice" {
|
||||
t.Fatalf("username = %q, want %q", opts.Username, "alice")
|
||||
}
|
||||
if opts.Password != "wonderland" {
|
||||
t.Fatalf("password = %q, want %q", opts.Password, "wonderland")
|
||||
}
|
||||
if opts.Path != "/proxy" {
|
||||
t.Fatalf("path = %q, want %q", opts.Path, "/proxy")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user