mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
[hysteria2] Masquerade inbound option style refactor. Mainly for supporting rewriteHost
This commit is contained in:
parent
4d1c52eeb1
commit
000f720947
@ -21,7 +21,15 @@
|
|||||||
],
|
],
|
||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "",
|
"masquerade": {
|
||||||
|
"type": "proxy",
|
||||||
|
"proxy": {
|
||||||
|
"url": "",
|
||||||
|
"rewriteHost": true
|
||||||
|
},
|
||||||
|
"file": "/var/www",
|
||||||
|
"string": "Some-Stuffs"
|
||||||
|
},
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -81,10 +89,13 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|||||||
|
|
||||||
HTTP3 server behavior when authentication fails.
|
HTTP3 server behavior when authentication fails.
|
||||||
|
|
||||||
| Scheme | Example | Description |
|
| Key | Example | Description |
|
||||||
|--------------|-------------------------|--------------------|
|
|--------------|--------------------------------|----------------------|
|
||||||
| `file` | `file:///var/www` | As a file server |
|
| `type` | `file \| proxy \| string` | masquerade modes |
|
||||||
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |
|
| `file` | `/var/www` | As a file server |
|
||||||
|
| `proxy.url` | `http://127.0.0.1:8080` | As a reverse proxy |
|
||||||
|
| `proxy.rewriteHost` | `true \| false` | Rewrite the Host header to match the proxied website |
|
||||||
|
| `string` | `Some-Stuffs` | as a constant string server |
|
||||||
|
|
||||||
A 404 page will be returned if empty.
|
A 404 page will be returned if empty.
|
||||||
|
|
||||||
|
@ -21,7 +21,15 @@
|
|||||||
],
|
],
|
||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "",
|
"masquerade": {
|
||||||
|
"type": "proxy",
|
||||||
|
"proxy": {
|
||||||
|
"url": "",
|
||||||
|
"rewriteHost": true
|
||||||
|
},
|
||||||
|
"file": "/var/www",
|
||||||
|
"string": "Some-Stuffs"
|
||||||
|
},
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -83,6 +91,14 @@ HTTP3 服务器认证失败时的行为。
|
|||||||
| `file` | `file:///var/www` | 作为文件服务器 |
|
| `file` | `file:///var/www` | 作为文件服务器 |
|
||||||
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
|
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
|
||||||
|
|
||||||
|
| Key | 示例 | 描述 |
|
||||||
|
|--------------|--------------------------------|----------------------|
|
||||||
|
| `type` | `file \| proxy \| string` | 模式 |
|
||||||
|
| `file` | `/var/www` | 作为文件服务器 |
|
||||||
|
| `proxy.url` | `http://127.0.0.1:8080` | 作为反向代理 |
|
||||||
|
| `proxy.rewriteHost` | `true \| false` | 重写 `Host` 头以匹配被代理的网站 |
|
||||||
|
| `string` | `Some-Stuffs` | 作为常量字符服务器 |
|
||||||
|
|
||||||
如果为空,则返回 404 页。
|
如果为空,则返回 404 页。
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
)
|
||||||
|
|
||||||
type Hysteria2InboundOptions struct {
|
type Hysteria2InboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
UpMbps int `json:"up_mbps,omitempty"`
|
UpMbps int `json:"up_mbps,omitempty"`
|
||||||
@ -8,8 +14,8 @@ type Hysteria2InboundOptions struct {
|
|||||||
Users []Hysteria2User `json:"users,omitempty"`
|
Users []Hysteria2User `json:"users,omitempty"`
|
||||||
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
|
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
Masquerade string `json:"masquerade,omitempty"`
|
Masquerade Hysteria2Masquerade `json:"masquerade,omitempty"`
|
||||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hysteria2Obfs struct {
|
type Hysteria2Obfs struct {
|
||||||
@ -33,3 +39,49 @@ type Hysteria2OutboundOptions struct {
|
|||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Hysteria2Masquerade struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
File string `json:"file,omitempty"`
|
||||||
|
Proxy Hysteria2MasqueradeProxy `json:"proxy,omitempty"`
|
||||||
|
String string `json:"string,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hysteria2MasqueradeProxy struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
RewriteHost bool `json:"rewriteHost,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Hysteria2Masquerade) UnmarshalJSON(data []byte) error {
|
||||||
|
// Attempt to unmarshal data as a string
|
||||||
|
var str string
|
||||||
|
if err := json.Unmarshal(data, &str); err == nil {
|
||||||
|
masqueradeURL, err := url.Parse(str)
|
||||||
|
if err != nil || masqueradeURL.Scheme == "" {
|
||||||
|
m.String = str
|
||||||
|
m.Type = "string"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch masqueradeURL.Scheme {
|
||||||
|
case "file":
|
||||||
|
m.File = masqueradeURL.Path
|
||||||
|
m.Type = "file"
|
||||||
|
case "http", "https":
|
||||||
|
m.Proxy.URL = str
|
||||||
|
m.Type = "proxy"
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If not a string, attempt to unmarshal into the struct
|
||||||
|
type Alias Hysteria2Masquerade
|
||||||
|
aux := &struct {
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(m),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -60,25 +60,37 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var masqueradeHandler http.Handler
|
var masqueradeHandler http.Handler
|
||||||
if options.Masquerade != "" {
|
if options.Masquerade != (option.Hysteria2Masquerade{}) {
|
||||||
masqueradeURL, err := url.Parse(options.Masquerade)
|
switch options.Masquerade.Type {
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse masquerade URL")
|
|
||||||
}
|
|
||||||
switch masqueradeURL.Scheme {
|
|
||||||
case "file":
|
case "file":
|
||||||
masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path))
|
masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.File))
|
||||||
case "http", "https":
|
case "proxy":
|
||||||
|
masqueradeURL, err := url.Parse(options.Masquerade.Proxy.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse masquerade URL")
|
||||||
|
}
|
||||||
masqueradeHandler = &httputil.ReverseProxy{
|
masqueradeHandler = &httputil.ReverseProxy{
|
||||||
Rewrite: func(r *httputil.ProxyRequest) {
|
Rewrite: func(r *httputil.ProxyRequest) {
|
||||||
r.SetURL(masqueradeURL)
|
r.SetURL(masqueradeURL)
|
||||||
|
// SetURL rewrites the Host header,
|
||||||
|
// but we don't want that if rewriteHost is false
|
||||||
|
if !options.Masquerade.Proxy.RewriteHost {
|
||||||
|
r.Out.Host = r.In.Host
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
w.WriteHeader(http.StatusBadGateway)
|
w.WriteHeader(http.StatusBadGateway)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
case "string":
|
||||||
|
if options.Masquerade.String != "" {
|
||||||
|
masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK) // Use 200 OK by default
|
||||||
|
_, _ = w.Write([]byte(options.Masquerade.String))
|
||||||
|
})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
|
return nil, E.New("unknown masquerade type: ", options.Masquerade.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inbound := &Inbound{
|
inbound := &Inbound{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -11,6 +12,35 @@ import (
|
|||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestHysteria2InboundOptionsMasqueradeUnmarshalJSON(t *testing.T) {
|
||||||
|
t.Run("schema-file", func(t *testing.T) {
|
||||||
|
m := testMasqueradeUnmarshalJSON(t, []byte(`"file:///var/www"`))
|
||||||
|
if m.Type != "file" || m.File != "/var/www" {
|
||||||
|
t.Errorf("Unexpected values: %+v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("schema-https", func(t *testing.T) {
|
||||||
|
m := testMasqueradeUnmarshalJSON(t, []byte(`"https://example.org:443"`))
|
||||||
|
if m.Type != "proxy" || m.Proxy.URL != "https://example.org:443" || m.Proxy.RewriteHost != false {
|
||||||
|
t.Errorf("Unexpected values: %+v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("schema-string", func(t *testing.T) {
|
||||||
|
m := testMasqueradeUnmarshalJSON(t, []byte(`"Some-Stuffs"`))
|
||||||
|
if m.Type != "string" || m.String != "Some-Stuffs" {
|
||||||
|
t.Errorf("Unexpected values: %+v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMasqueradeUnmarshalJSON(t *testing.T, jsonData []byte) option.Hysteria2Masquerade {
|
||||||
|
var m option.Hysteria2Masquerade
|
||||||
|
if err := json.Unmarshal(jsonData, &m); err != nil {
|
||||||
|
t.Fatalf("Unmarshal failed: %v", err)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func TestHysteria2Self(t *testing.T) {
|
func TestHysteria2Self(t *testing.T) {
|
||||||
t.Run("self", func(t *testing.T) {
|
t.Run("self", func(t *testing.T) {
|
||||||
testHysteria2Self(t, "")
|
testHysteria2Self(t, "")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user