在全局复写脚本里加入如下内容即可.这样的话,匹配到Direct的情况下会走最上方的代理组

这里可以选择是走tailscale的网卡,还是就是正常的网卡.一般来说,只有开了tailscale且打算让流量走exit node出口走,才需要选第二个.
function main(config, profileName) {
const DIRECT_GROUP = "Manual-DIRECT";
const NATIVE_DIRECT = "Native-DIRECT";
const TS_DIRECT = "Tailscale-DIRECT";
const TS_IFACE = "utun10"; // 当前 Tailscale 接口;如果变了就改这里
config.proxies ||= [];
config.proxies = config.proxies.filter((p) => {
if (!p || !p.name) return true;
return (
p.name !== NATIVE_DIRECT &&
p.name !== TS_DIRECT &&
!p.name.startsWith("Tailscale-DIRECT-utun")
);
});
config.proxies.unshift({
name: TS_DIRECT,
type: "direct",
udp: true,
"interface-name": TS_IFACE,
"ip-version": "ipv4-prefer",
});
config.proxies.unshift({
name: NATIVE_DIRECT,
type: "direct",
udp: true,
"ip-version": "ipv4-prefer",
});
config["proxy-groups"] ||= [];
config["proxy-groups"] = config["proxy-groups"].filter(
(g) => g.name !== DIRECT_GROUP && g.name !== "Smart-DIRECT"
);
config["proxy-groups"].unshift({
name: DIRECT_GROUP,
type: "select",
proxies: [
NATIVE_DIRECT, // 默认选这个
TS_DIRECT, // 需要 Tailscale exit node 时手动切
],
});
const replaceDirect = (policy) =>
policy === "DIRECT" || policy === "Smart-DIRECT" ? DIRECT_GROUP : policy;
if (Array.isArray(config.rules)) {
config.rules = config.rules.map((rule) => {
if (typeof rule !== "string") return rule;
const parts = rule.split(",");
const policyIndex =
parts[parts.length - 1] === "no-resolve"
? parts.length - 2
: parts.length - 1;
parts[policyIndex] = replaceDirect(parts[policyIndex]);
return parts.join(",");
});
}
for (const group of config["proxy-groups"] || []) {
if (group.name === DIRECT_GROUP) continue;
if (Array.isArray(group.proxies)) {
group.proxies = group.proxies.map(replaceDirect);
}
}
return config;
}
随后重启clash内核即可.
我们能不能做到不要写死utun,而是每次手动选择一下看看哪个是通的

这样我们一看就知道,utun12是tailscale的网口.全局扩展脚本如下:
function main(config, profileName) {
const DIRECT_GROUP = "Manual-DIRECT-IFACE";
const NATIVE_DIRECT = "Native-DIRECT";
const MAX_UTUN = 20;
config.proxies ||= [];
config.proxies = config.proxies.filter((p) => {
if (!p || !p.name) return true;
return (
p.name !== NATIVE_DIRECT &&
p.name !== "Tailscale-DIRECT" &&
!p.name.startsWith("Tailscale-DIRECT-utun")
);
});
config.proxies.unshift({
name: NATIVE_DIRECT,
type: "direct",
udp: true,
"ip-version": "ipv4-prefer",
});
const candidates = [];
for (let i = 0; i <= MAX_UTUN; i++) {
const name = `Tailscale-DIRECT-utun${i}`;
candidates.push(name);
config.proxies.push({
name,
type: "direct",
udp: true,
"interface-name": `utun${i}`,
"ip-version": "ipv4-prefer",
});
}
config["proxy-groups"] ||= [];
config["proxy-groups"] = config["proxy-groups"].filter(
(g) =>
g.name !== DIRECT_GROUP &&
g.name !== "Manual-DIRECT" &&
g.name !== "Smart-DIRECT"
);
config["proxy-groups"].unshift({
name: DIRECT_GROUP,
type: "select",
proxies: [
NATIVE_DIRECT, // 默认
...candidates,
],
});
const replaceDirect = (policy) =>
policy === "DIRECT" ||
policy === "Smart-DIRECT" ||
policy === "Manual-DIRECT"
? DIRECT_GROUP
: policy;
if (Array.isArray(config.rules)) {
config.rules = config.rules.map((rule) => {
if (typeof rule !== "string") return rule;
const parts = rule.split(",");
const policyIndex =
parts[parts.length - 1] === "no-resolve"
? parts.length - 2
: parts.length - 1;
parts[policyIndex] = replaceDirect(parts[policyIndex]);
return parts.join(",");
});
}
for (const group of config["proxy-groups"] || []) {
if (group.name === DIRECT_GROUP) continue;
if (Array.isArray(group.proxies)) {
group.proxies = group.proxies.map(replaceDirect);
}
}
return config;
}