跳到主要内容

sing-box

因为 sing-box 的配置文件为 JSON 格式,所以我们引入了一种新的方式来维护 sing-box 规则,或是其它 JSON 格式的规则。

准备​

首先我们找到一份基础的规则文件,它可能是这样的:

{
"inbounds": [],
"outbounds": [
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns"
}
],
"route": {},
"experimental": {
"cache_file": {
"enabled": true
},
"clash_api": {
"external_controller": "127.0.0.1:9090"
}
}
}

我们看到此时 outbounds 已经包含了一些内容,我们要做的就是把节点信息填充到 outbounds 中。

把这个文件保存在 template 目录下,命名为 singbox.json。

编写 Artifact​

const { extendOutbounds } = require('surgio')

module.exports = {
artifacts: [
{
name: 'singbox.json',
template: 'singbox',
templateType: 'json',
extendTemplate: extendOutbounds(
({ getSingboxNodes, getSingboxNodeNames, nodeList }) => [
{
type: 'direct',
tag: 'direct',
tcp_fast_open: false,
tcp_multi_path: true,
},
{
type: 'selector',
tag: 'proxy',
outbounds: ['auto', ...getSingboxNodeNames(nodeList)],
// outbounds: getSingboxNodeNames(nodeList), // 如果你不需要 auto 节点
interrupt_exist_connections: false,
},
...getSingboxNodes(nodeList),
],
),
provider: 'ss',
},
],
}

这个配置的含义是:

  • template 为 singbox,即我们刚刚创建的模板文件
  • extendTemplate 为 extendOutbounds,这个函数会把节点信息填充到 outbounds 中

第 10 行的 getSingboxNodes, getSingboxNodeNames 属于「模板方法」,具体有哪些可用的模板方法可以看 这里。

extendOutbounds 函数​

extendOutbounds 支持两种写法,一种是直接输入一个不可变的变量,另一种是输入一个函数。变量即确定的不会变化的内容,函数则是相对动态的内容。上面的例子中我们使用了函数的写法。

直接输入变量​

const { extendOutbounds } = require('surgio')

module.exports = {
artifacts: [
{
name: 'singbox.json',
template: 'singbox',
templateType: 'json',
extendTemplate: extendOutbounds([
{
type: 'direct',
tag: 'direct',
tcp_fast_open: false,
tcp_multi_path: true,
},
]),
provider: 'ss',
},
],
}

你可以在 这里 查看这篇文章中提到的所有模板方法的文档。

Tailscale 等 endpoint 节点​

新版 sing-box 将 WireGuard 和 Tailscale 视为 endpoint,需要放入配置文件顶层的 endpoints 字段,而不是 outbounds。使用 getSingboxEndpoints 生成 endpoint 节点,并配合 extendEndpoints 与 combineExtendFunctions 一起填充:

const {
combineExtendFunctions,
extendOutbounds,
extendEndpoints,
} = require('surgio')

module.exports = {
artifacts: [
{
name: 'singbox.json',
template: 'singbox',
templateType: 'json',
extendTemplate: combineExtendFunctions(
extendOutbounds(
({ getSingboxNodes, getSingboxNodeNames, nodeList }) => [
{
type: 'selector',
tag: 'proxy',
// getSingboxNodeNames 同时包含 outbound 和 endpoint 的 tag
outbounds: getSingboxNodeNames(nodeList),
},
...getSingboxNodes(nodeList),
],
),
extendEndpoints(({ getSingboxEndpoints, nodeList }) =>
getSingboxEndpoints(nodeList),
),
),
provider: 'ss',
},
],
}

维护规则​

前面介绍了如何把节点写入 outbounds,而规则则可以复用现有的 Surge 格式规则片段。Surgio 提供了两个新的模板方法:

  • getSingboxRules(ruleText, outbound?) —— 把 Surge 格式的规则文本转换成 route.rules 需要的规则对象数组。每行末尾的策略列会被转换成 outbound(REJECT 系列策略会转换成 action: 'reject'),也可以通过第二个参数强制指定策略;
  • getSingboxHeadlessRules(ruleText) —— 转换成不含策略的 headless 规则,用于生成 rule-set 文件(见下文)。

配合 extendRoute 就可以把规则片段直接写入 sing-box 配置:

const {
combineExtendFunctions,
extendOutbounds,
extendRoute,
} = require('surgio')

module.exports = {
artifacts: [
{
name: 'singbox.json',
template: 'singbox',
templateType: 'json',
extendTemplate: combineExtendFunctions(
extendOutbounds(/* ... */),
extendRoute(({ getSingboxRules, remoteSnippets, snippet }) => ({
rules: [
// 远程片段,策略为 REJECT
...getSingboxRules(remoteSnippets.reject.main('REJECT')),
// 本地片段 template/snippet/direct.tpl,策略为 DIRECT
...getSingboxRules(snippet('snippet/direct.tpl').main('DIRECT')),
// 远程片段,策略为 proxy
...getSingboxRules(remoteSnippets.proxy.main('proxy')),
],
// 对应 Surge 规则中的 FINAL
final: 'proxy',
})),
),
provider: 'ss',
},
],
}

extendRoute 会把 rules 追加到模板已有的 route.rules 之后,并覆盖 route.final 等标量字段。

生成 rule-set 文件​

sing-box 支持把规则放到独立的 rule-set 文件中,主配置通过引用的方式使用它们。以 Netflix 规则集为例:

  1. 新建模板 template/singbox-ruleset.json:
{
"version": 3,
"rules": []
}
  1. 新建 Artifact,用 extendRuleSet 填充 rules 字段。headless 规则不包含策略,所以直接读取片段原文(.text):
const { extendRuleSet } = require('surgio')

module.exports = {
artifacts: [
{
name: 'ruleset/netflix.json',
template: 'singbox-ruleset',
templateType: 'json',
extendTemplate: extendRuleSet(
({ getSingboxHeadlessRules, remoteSnippets }) =>
getSingboxHeadlessRules(remoteSnippets.netflix.text),
),
provider: 'ss',
},
],
}
  1. 在主配置的 route 中声明并引用这个 rule-set:
extendRoute(({ getUrl }) => ({
rule_set: [
{
type: 'remote',
tag: 'netflix',
format: 'source',
url: getUrl('ruleset/netflix.json'),
},
],
rules: [{ rule_set: ['netflix'], outbound: 'proxy' }],
}))

规则类型对照​

Surge 规则类型sing-box 字段备注
DOMAINdomain
DOMAIN-SUFFIXdomain_suffix
DOMAIN-KEYWORDdomain_keyword
DOMAIN-WILDCARDdomain_regex*、? 会被转换为正则表达式
IP-CIDR / IP-CIDR6ip_cidrno-resolve 会被忽略
GEOIPrule_set生成 geoip-<代码> 引用
RULE-SETrule_set原样引用第二列的值
PROCESS-NAMEprocess_name
DEST-PORTport
SRC-PORTsource_port
SRC-IPsource_ip_cidr
PROTOCOLnetwork仅支持 TCP 和 UDP
AND / OR / NOT逻辑规则转换为 sing-box 的 logical rule
注意
  • GEOIP 和 RULE-SET 只会生成对 rule-set 的引用(例如 geoip-cn),你需要像上面的例子一样在 route.rule_set 中自行声明这些 rule-set;
  • sing-box 无法表达的规则(例如 USER-AGENT、URL-REGEX、IP-ASN、SCRIPT)会被跳过,并在生成时输出警告日志;
  • FINAL 规则请使用 route.final,如上面的 final: 'proxy'。