Browse Source

clash-api: Add PNA support

世界 1 year ago
parent
commit
6362b778c9

+ 78 - 18
docs/configuration/experimental/clash-api.md

@@ -1,3 +1,12 @@
+---
+icon: material/new-box
+---
+
+!!! quote "Changes in sing-box 1.10.0"
+
+    :material-plus: [access_control_allow_origin](#access_control_allow_origin)  
+    :material-plus: [access_control_allow_private_network](#access_control_allow_private_network)
+
 !!! quote "Changes in sing-box 1.8.0"
 
     :material-delete-alert: [store_mode](#store_mode)  
@@ -8,24 +17,59 @@
 
 ### Structure
 
-```json
-{
-  "external_controller": "127.0.0.1:9090",
-  "external_ui": "",
-  "external_ui_download_url": "",
-  "external_ui_download_detour": "",
-  "secret": "",
-  "default_mode": "",
-  
-  // Deprecated
-  
-  "store_mode": false,
-  "store_selected": false,
-  "store_fakeip": false,
-  "cache_file": "",
-  "cache_id": ""
-}
-```
+=== "Structure"
+
+    ```json
+    {
+      "external_controller": "127.0.0.1:9090",
+      "external_ui": "",
+      "external_ui_download_url": "",
+      "external_ui_download_detour": "",
+      "secret": "",
+      "default_mode": "",
+      "access_control_allow_origin": [],
+      "access_control_allow_private_network": false,
+      
+      // Deprecated
+      
+      "store_mode": false,
+      "store_selected": false,
+      "store_fakeip": false,
+      "cache_file": "",
+      "cache_id": ""
+    }
+    ```
+
+=== "Example (online)"
+
+    !!! question "Since sing-box 1.10.0"
+
+    ```json
+    {
+      "external_controller": "127.0.0.1:9090",
+      "access_control_allow_origin": [
+        "http://127.0.0.1",
+        "http://yacd.haishan.me"
+      ],
+      "access_control_allow_private_network": true
+    }
+    ```
+
+=== "Example (download)"
+
+    !!! question "Since sing-box 1.10.0"
+
+    ```json
+    {
+      "external_controller": "0.0.0.0:9090",
+      "external_ui": "dashboard"
+      // external_ui_download_detour: "direct"
+    }
+    ```
+
+!!! note ""
+
+    You can ignore the JSON Array [] tag when the content is only one item
 
 ### Fields
 
@@ -63,6 +107,22 @@ Default mode in clash, `Rule` will be used if empty.
 
 This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
 
+#### access_control_allow_origin
+
+!!! question "Since sing-box 1.10.0"
+
+CORS allowed origins, `*` will be used if empty.
+
+To access the Clash API on a private network from a public website, you must explicitly specify it in `access_control_allow_origin` instead of using `*`.
+
+#### access_control_allow_private_network
+
+!!! question "Since sing-box 1.10.0"
+
+Allow access from private network.
+
+To access the Clash API on a private network from a public website, `access_control_allow_private_network` must be enabled.
+
 #### store_mode
 
 !!! failure "Deprecated in sing-box 1.8.0"

+ 78 - 18
docs/configuration/experimental/clash-api.zh.md

@@ -1,3 +1,12 @@
+---
+icon: material/new-box
+---
+
+!!! quote "sing-box 1.10.0 中的更改"
+
+    :material-plus: [access_control_allow_origin](#access_control_allow_origin)  
+    :material-plus: [access_control_allow_private_network](#access_control_allow_private_network)
+
 !!! quote "sing-box 1.8.0 中的更改"
 
     :material-delete-alert: [store_mode](#store_mode)  
@@ -8,24 +17,59 @@
 
 ### 结构
 
-```json
-{
-  "external_controller": "127.0.0.1:9090",
-  "external_ui": "",
-  "external_ui_download_url": "",
-  "external_ui_download_detour": "",
-  "secret": "",
-  "default_mode": "",
-  
-  // Deprecated
-  
-  "store_mode": false,
-  "store_selected": false,
-  "store_fakeip": false,
-  "cache_file": "",
-  "cache_id": ""
-}
-```
+=== "结构"
+
+    ```json
+    {
+      "external_controller": "127.0.0.1:9090",
+      "external_ui": "",
+      "external_ui_download_url": "",
+      "external_ui_download_detour": "",
+      "secret": "",
+      "default_mode": "",
+      "access_control_allow_origin": [],
+      "access_control_allow_private_network": false,
+      
+      // Deprecated
+      
+      "store_mode": false,
+      "store_selected": false,
+      "store_fakeip": false,
+      "cache_file": "",
+      "cache_id": ""
+    }
+    ```
+
+=== "示例 (在线)"
+
+    !!! question "自 sing-box 1.10.0 起"
+
+    ```json
+    {
+      "external_controller": "127.0.0.1:9090",
+      "access_control_allow_origin": [
+        "http://127.0.0.1",
+        "http://yacd.haishan.me"
+      ],
+      "access_control_allow_private_network": true
+    }
+    ```
+
+=== "示例 (下载)"
+
+    !!! question "自 sing-box 1.10.0 起"
+
+    ```json
+    {
+      "external_controller": "0.0.0.0:9090",
+      "external_ui": "dashboard"
+      // external_ui_download_detour: "direct"
+    }
+    ```
+
+!!! note ""
+
+    当内容只有一项时,可以忽略 JSON 数组 [] 标签
 
 ### Fields
 
@@ -61,6 +105,22 @@ Clash 中的默认模式,默认使用 `Rule`。
 
 此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
 
+#### access_control_allow_origin
+
+!!! question "自 sing-box 1.10.0 起"
+
+允许的 CORS 来源,默认使用 `*`。
+
+要从公共网站访问私有网络上的 Clash API,必须在 `access_control_allow_origin` 中明确指定它而不是使用 `*`。
+
+#### access_control_allow_private_network
+
+!!! question "自 sing-box 1.10.0 起"
+
+允许从私有网络访问。
+
+要从公共网站访问私有网络上的 Clash API,必须启用 `access_control_allow_private_network`。
+
 #### store_mode
 
 !!! failure "已在 sing-box 1.8.0 废弃"

+ 10 - 5
experimental/clashapi/server.go

@@ -12,6 +12,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/sagernet/cors"
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/common/urltest"
 	C "github.com/sagernet/sing-box/constant"
@@ -29,7 +30,6 @@ import (
 	"github.com/sagernet/ws/wsutil"
 
 	"github.com/go-chi/chi/v5"
-	"github.com/go-chi/cors"
 	"github.com/go-chi/render"
 )
 
@@ -90,11 +90,16 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
 	if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
 		return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.")
 	}
+	allowedOrigins := options.AccessControlAllowOrigin
+	if len(allowedOrigins) == 0 {
+		allowedOrigins = []string{"*"}
+	}
 	cors := cors.New(cors.Options{
-		AllowedOrigins: []string{"*"},
-		AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
-		AllowedHeaders: []string{"Content-Type", "Authorization"},
-		MaxAge:         300,
+		AllowedOrigins:      allowedOrigins,
+		AllowedMethods:      []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
+		AllowedHeaders:      []string{"Content-Type", "Authorization"},
+		AllowPrivateNetwork: options.AccessControlAllowPrivateNetwork,
+		MaxAge:              300,
 	})
 	chiRouter.Use(cors.Handler)
 	chiRouter.Group(func(r chi.Router) {

+ 1 - 1
go.mod

@@ -8,7 +8,6 @@ require (
 	github.com/cloudflare/circl v1.3.7
 	github.com/cretz/bine v0.2.0
 	github.com/go-chi/chi/v5 v5.0.12
-	github.com/go-chi/cors v1.2.1
 	github.com/go-chi/render v1.0.3
 	github.com/gofrs/uuid/v5 v5.2.0
 	github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
@@ -22,6 +21,7 @@ require (
 	github.com/oschwald/maxminddb-golang v1.12.0
 	github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
 	github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
+	github.com/sagernet/cors v1.2.1
 	github.com/sagernet/fswatch v0.1.1
 	github.com/sagernet/gomobile v0.1.4
 	github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f

+ 2 - 2
go.sum

@@ -21,8 +21,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
 github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
 github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
-github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
 github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
 github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
 github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -102,6 +100,8 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
+github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
+github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
 github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
 github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
 github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY=

+ 9 - 7
option/experimental.go

@@ -17,13 +17,15 @@ type CacheFileOptions struct {
 }
 
 type ClashAPIOptions struct {
-	ExternalController       string   `json:"external_controller,omitempty"`
-	ExternalUI               string   `json:"external_ui,omitempty"`
-	ExternalUIDownloadURL    string   `json:"external_ui_download_url,omitempty"`
-	ExternalUIDownloadDetour string   `json:"external_ui_download_detour,omitempty"`
-	Secret                   string   `json:"secret,omitempty"`
-	DefaultMode              string   `json:"default_mode,omitempty"`
-	ModeList                 []string `json:"-"`
+	ExternalController               string           `json:"external_controller,omitempty"`
+	ExternalUI                       string           `json:"external_ui,omitempty"`
+	ExternalUIDownloadURL            string           `json:"external_ui_download_url,omitempty"`
+	ExternalUIDownloadDetour         string           `json:"external_ui_download_detour,omitempty"`
+	Secret                           string           `json:"secret,omitempty"`
+	DefaultMode                      string           `json:"default_mode,omitempty"`
+	ModeList                         []string         `json:"-"`
+	AccessControlAllowOrigin         Listable[string] `json:"access_control_allow_origin,omitempty"`
+	AccessControlAllowPrivateNetwork bool             `json:"access_control_allow_private_network,omitempty"`
 
 	// Deprecated: migrated to global cache file
 	CacheFile string `json:"cache_file,omitempty"`