Skip to content

Commit

Permalink
hysteria2: Add more masquerade options
Browse files Browse the repository at this point in the history
  • Loading branch information
nekohasekai committed Nov 28, 2024
1 parent 54bbda6 commit d9d8567
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 23 deletions.
7 changes: 7 additions & 0 deletions constant/hysteria2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package constant

const (
Hysterai2MasqueradeTypeFile = "file"
Hysterai2MasqueradeTypeProxy = "proxy"
Hysterai2MasqueradeTypeString = "string"
)
50 changes: 45 additions & 5 deletions docs/configuration/inbound/hysteria2.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
{
"type": "hysteria2",
"tag": "hy2-in",
...
// Listen Fields

... // Listen Fields

"up_mbps": 100,
"down_mbps": 100,
Expand All @@ -21,7 +21,7 @@
],
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "",
"masquerade": "", // or {}
"brutal_debug": false
}
```
Expand Down Expand Up @@ -79,14 +79,54 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).

#### masquerade

HTTP3 server behavior when authentication fails.
HTTP3 server behavior (URL string configuration) when authentication fails.

| Scheme | Example | Description |
|--------------|-------------------------|--------------------|
| `file` | `file:///var/www` | As a file server |
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |

A 404 page will be returned if empty.
Conflict with `masquerade.type`.

A 404 page will be returned if masquerade is not configured.

#### masquerade.type

HTTP3 server behavior (Object configuration) when authentication fails.

| Type | Description | Fields |
|----------|-----------------------------|-------------------------------------|
| `file` | As a file server | `file` |
| `proxy` | As a reverse proxy | `url`, `rewrite_host` |
| `string` | Reply with a fixed response | `status_code`, `headers`, `content` |

Conflict with `masquerade`.

A 404 page will be returned if masquerade is not configured.

#### masquerade.file

File server root directory.

#### masquerade.url

Reverse proxy target URL.

#### masquerade.rewrite_host

Rewrite the `Host` header to the target URL.

#### masquerade.status_code

Fixed response status code.

#### masquerade.headers

Fixed response headers.

#### masquerade.content

Fixed response content.

#### brutal_debug

Expand Down
50 changes: 45 additions & 5 deletions docs/configuration/inbound/hysteria2.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
{
"type": "hysteria2",
"tag": "hy2-in",
...
// 监听字段

... // 监听字段

"up_mbps": 100,
"down_mbps": 100,
Expand All @@ -21,7 +21,7 @@
],
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "",
"masquerade": "", // 或 {}
"brutal_debug": false
}
```
Expand Down Expand Up @@ -76,14 +76,54 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

#### masquerade

HTTP3 服务器认证失败时的行为。
HTTP3 服务器认证失败时的行为 (URL 字符串配置)

| Scheme | 示例 | 描述 |
|--------------|-------------------------|---------|
| `file` | `file:///var/www` | 作为文件服务器 |
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |

如果为空,则返回 404 页。
如果 masquerade 未配置,则返回 404 页。

`masquerade.type` 冲突。

#### masquerade.type

HTTP3 服务器认证失败时的行为 (对象配置)。

| Type | 描述 | 字段 |
|----------|---------|-------------------------------------|
| `file` | 作为文件服务器 | `file` |
| `proxy` | 作为反向代理 | `url`, `rewrite_host` |
| `string` | 返回固定响应 | `status_code`, `headers`, `content` |

如果 masquerade 未配置,则返回 404 页。

`masquerade` 冲突。

#### masquerade.file

文件服务器根目录。

#### masquerade.url

反向代理目标 URL。

#### masquerade.rewrite_host

重写请求头中的 Host 字段到目标 URL。

#### masquerade.status_code

固定响应状态码。

#### masquerade.headers

固定响应头。

#### masquerade.content

固定响应内容。

#### brutal_debug

Expand Down
90 changes: 88 additions & 2 deletions option/hysteria2.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package option

import (
"net/url"

C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
)

type Hysteria2InboundOptions struct {
ListenOptions
UpMbps int `json:"up_mbps,omitempty"`
Expand All @@ -8,8 +18,8 @@ type Hysteria2InboundOptions struct {
Users []Hysteria2User `json:"users,omitempty"`
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
InboundTLSOptionsContainer
Masquerade string `json:"masquerade,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
}

type Hysteria2Obfs struct {
Expand All @@ -22,6 +32,82 @@ type Hysteria2User struct {
Password string `json:"password,omitempty"`
}

type _Hysteria2Masquerade struct {
Type string `json:"type,omitempty"`
FileOptions Hysteria2MasqueradeFile `json:"-"`
ProxyOptions Hysteria2MasqueradeProxy `json:"-"`
StringOptions Hysteria2MasqueradeString `json:"-"`
}

type Hysteria2Masquerade _Hysteria2Masquerade

func (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) {
var v any
switch m.Type {
case C.Hysterai2MasqueradeTypeFile:
v = m.FileOptions
case C.Hysterai2MasqueradeTypeProxy:
v = m.ProxyOptions
case C.Hysterai2MasqueradeTypeString:
v = m.StringOptions
default:
return nil, E.New("unknown masquerade type: ", m.Type)
}
return badjson.MarshallObjects((_Hysteria2Masquerade)(m), v)
}

func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {
var urlString string
err := json.Unmarshal(bytes, &urlString)
if err == nil {
masqueradeURL, err := url.Parse(urlString)
if err != nil {
return E.Cause(err, "invalid masquerade URL")
}
switch masqueradeURL.Scheme {
case "file":
m.Type = C.Hysterai2MasqueradeTypeFile
m.FileOptions.Directory = masqueradeURL.Path
case "http", "https":
m.Type = C.Hysterai2MasqueradeTypeProxy
m.ProxyOptions.URL = urlString
default:
return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
}
}
err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m))
if err != nil {
return err
}
var v any
switch m.Type {
case C.Hysterai2MasqueradeTypeFile:
v = &m.FileOptions
case C.Hysterai2MasqueradeTypeProxy:
v = &m.ProxyOptions
case C.Hysterai2MasqueradeTypeString:
v = &m.StringOptions
default:
return E.New("unknown masquerade type: ", m.Type)
}
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v)
}

type Hysteria2MasqueradeFile struct {
Directory string `json:"directory"`
}

type Hysteria2MasqueradeProxy struct {
URL string `json:"url"`
RewriteHost bool `json:"rewrite_host,omitempty"`
}

type Hysteria2MasqueradeString struct {
StatusCode int `json:"status_code,omitempty"`
Headers badoption.HTTPHeader `json:"headers,omitempty"`
Content string `json:"content"`
}

type Hysteria2OutboundOptions struct {
DialerOptions
ServerOptions
Expand Down
36 changes: 25 additions & 11 deletions protocol/hysteria2/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,40 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}
}
var masqueradeHandler http.Handler
if options.Masquerade != "" {
masqueradeURL, err := url.Parse(options.Masquerade)
if err != nil {
return nil, E.Cause(err, "parse masquerade URL")
}
switch masqueradeURL.Scheme {
case "file":
masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path))
case "http", "https":
if options.Masquerade != nil && options.Masquerade.Type != "" {
switch options.Masquerade.Type {
case C.Hysterai2MasqueradeTypeFile:
masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory))
case C.Hysterai2MasqueradeTypeProxy:
masqueradeURL, err := url.Parse(options.Masquerade.ProxyOptions.URL)
if err != nil {
return nil, E.Cause(err, "parse masquerade URL")
}
masqueradeHandler = &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(masqueradeURL)
r.Out.Host = r.In.Host
if !options.Masquerade.ProxyOptions.RewriteHost {
r.Out.Host = r.In.Host
}
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusBadGateway)
},
}
case C.Hysterai2MasqueradeTypeString:
masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if options.Masquerade.StringOptions.StatusCode != 0 {
w.WriteHeader(options.Masquerade.StringOptions.StatusCode)
}
for key, values := range options.Masquerade.StringOptions.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.Write([]byte(options.Masquerade.StringOptions.Content))
})
default:
return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
return nil, E.New("unknown masquerade type: ", options.Masquerade.Type)
}
}
inbound := &Inbound{
Expand Down

0 comments on commit d9d8567

Please sign in to comment.