Open heiher opened 1 year ago
今天刚写的 qBittorrent 打洞脚本,可能不太完善,欢迎大家反馈: https://github.com/Mythologyli/qBittorrent-NAT-TCP-Hole-Punching
尝试实现了 NAT-PMP 协议给 Transmission 用 ~,但是它的支持好像有问题,给他返回映射端口后,它把 IPv6 的监听端口也改了,导致 IPv6 入站全被防火墙拦截了。。。~ 应该没问题,就是一时没有v6入站
对于wireguard方式,写了一个PowerShell脚本,能够自动修改配置文件的Endpoint
并调用wireguard.exe
进行连接。
使用方法:
C:\example
下建立wg.ps1和nat.conf,粘贴Gist内容。nat.conf
,以及wg.ps1
中$Hostname
部分。Endpoint
不必修改。PowerShell
ps1
脚本运行权限:Set-ExecutionPolicy RemoteSigned
(或Unrestricted)C:\example\wg.ps1 -up
C:\example\wg.ps1 -down
在Windows 11, Powershell 5.1.22621.963测试通过,也可以配合Windows下的sudo使用。
另外,在Android下,也可以用termux运行nm-echo.sh来获得IP地址,可以不必更换客户端。
TCP 打洞部署 Vmess TCP 代理服务 让流量回家,通过脚本生成 vmess 分享链接保持更新。
对比 wireguard 优点:
SIng-box 服务端 Vmess 配置示例:
{
"log": {
"level": "info"
},
"inbounds": [
{
"type": "vmess",
"listen": "0.0.0.0",
"listen_port": 9689,
"users": [
{
"uuid": "20a46c57-710e-4ec9-947d-2c178f037bf5",
"alterId": 0
}
],
"sniff": true,
"sniff_override_destination": false
}
]
}
配合 natmap 的 linux 脚本(需要 base64 命令,openwrt 通过 opkg install coreutils-base64 安装):
#!/bin/sh
# 服务器别名
server_alias=Home_Proxy
# 服务器地址
server_address="$1"
# 服务器端口
server_port="$2"
# 用户 UUID
user_id="20a46c57-710e-4ec9-947d-2c178f037bf5"
# 生成的 Vmess 分享链接文件位置
share_link_file="/www/ad874236-07ed-4801-99f0"
# 生成 VMess 链接
vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$server_alias\",\"add\":\"$server_address\",\"port\":$server_port,\"id\":\"$user_id\",\"aid\":\"0\",\"net\":\"tcp\",\"type\":\"none\"}" | base64 -w 0)"
echo $vmess_link > $share_link_file
生成的 分享链接 文件可以通过 内网穿透 或者 增加 curl 命令上传至 web 服务暴露出来。 然后代理客户端,将 url 地址填入订阅功能即可。
注意:
Resolve IP4P and generate config with cloudflare worker.
worker.js:
/**
* Purpose: Resolve IP4P and generate a configuration using a Cloudflare worker.
*
* Usage:
* 1. Create an online configuration file with placeholders: ${ip4p.ip}, ${ip4p.port}, ${query.xxx}.
* 2. Set your online configuration URL as the CONFIG_URL variable and publish this script as a Cloudflare worker.
* 3. Retrieve your generated configuration by accessing https://YOUR-WORKER.workers.dev/YOUR-random-PATH-12435/clash?IP4P_DOMAIN=YOUR_IP4P_DOMAIN&cipher=YOUR_CIPHER&password=YOUR_PASSWORD.
* 4. Additional keyword checks to the user-agent header can be applied, by setting ALLOW_UA_KEYWORDS.
*
* Notes:
* 1. Only IP4P_DOMAIN is mandatory; the rest of the query parameters are optional.
* 2. The configuration itself can be in any format you like (yaml, json, etc.).
* 3. Add ?_= to the CONFIG_URL to prevent caching.
* 4. Choose a random PATH to prevent URL leakage.
* 5. If the worker's domain is blocked in your region, consider binding the worker to your custom domain.
*/
// Set your online configuration URL here
const CONFIG_URL = 'https://gist.githubusercontent.com/YOUR_ONLINE_CONFIG/config-ss.yaml?_=';
// Choose a random path to prevent URL leakage
const PATH = '/YOUR-random-PATH-12435/clash';
// Keywords to allow in user-agent header
// const ALLOW_UA_KEYWORDS = 'clash,Clash,v2ray'
const ALLOW_UA_KEYWORDS = ''
// Cloudflare DNS-over-HTTPS URL
const DOH_URL = 'https://cloudflare-dns.com/dns-query?ct=application/dns-json';
const ALLOW_UA_KEYWORDS_ARR = ALLOW_UA_KEYWORDS.split(',')
.filter(keyword => keyword)
/**
* Performs an HTTP GET request.
*
* @param {string | URL} url - The URL to fetch.
* @returns {Promise<Response>} A promise that resolves to the fetch response.
*/
const get = async (url) => {
const res = await fetch(url)
if (!res.ok) {
throw new Error(`Request error: ${res.status}`)
}
return res
}
/**
* Resolves DNS records using DNS-over-HTTPS.
*
* @param {string} domain - The domain to resolve.
* @param {string} [type='AAAA'] - The DNS record type (default: 'AAAA').
* @returns {Promise<Object>} A promise that resolves to the DNS response JSON object.
*/
const resolveDNSRecord = async (domain, type = 'AAAA') => {
const url = new URL(DOH_URL)
url.searchParams.append('name', domain)
url.searchParams.append('type', type)
const res = await get(url)
return res.json()
}
/**
* @typedef {Object} IP4PInfo
* @property {string} ip - The IP address.
* @property {number} port - The port number.
*/
/**
* Resolves IP4P information from DNS records.
*
* @param {string} domain - The domain to resolve IP4P for.
* @returns {Promise<IP4PInfo>} A promise that resolves to an object containing IP and port.
* @throws {Error} If the IP4P information is invalid.
*/
const resolveIP4P = async (domain) => {
const json = await resolveDNSRecord(domain)
const answer = json?.Answer
if (!answer || !Array.isArray(answer)) {
throw new Error('Invalid dns record')
}
const data = answer.find(t => t.data)?.data || ''
const parts = data.split(':')
if (parts.length !== 5) {
throw new Error(`Invalid IP4P: ${data}`)
}
// See: https://github.com/heiher/natmap/wiki/ssh#proxycommand
const port = parseInt(parts[2], 16)
const ipab = parseInt(parts[3], 16)
const ipcd = parseInt(parts[4], 16)
if (Number.isNaN(port) || Number.isNaN(ipab) || Number.isNaN(ipcd)) {
throw new Error(`Invalid IP4P values: ${data}`)
}
const ipa = ipab >> 8
const ipb = ipab & 0xff
const ipc = ipcd >> 8
const ipd = ipcd & 0xff
const ip = `${ipa}.${ipb}.${ipc}.${ipd}`
return {
ip,
port,
}
}
/**
* Gets the configuration from the online source with placeholders replaced.
*
* @param {string} url - The URL of the configuration source.
* @param {(type: string, key: string) => string | undefined} replacer - A function that replaces placeholders based on their type and key.
* @returns {Promise<string>} A promise that resolves to the configuration with placeholders replaced.
*/
const getConfig = async (url, replacer) => {
const urlObject = new URL(url)
const {
searchParams,
} = urlObject
if (searchParams.has('_')) {
searchParams.set('_', `${Math.random()}`)
}
const res = await get(urlObject)
const configText = await res.text()
return configText.replace(/\$\{([^}]+)\}/g, (g0, g1) => {
const index = g1.indexOf('.')
if (index === -1) {
return g0
}
const type = g1.slice(0, index)
const key = g1.slice(index + 1)
const value = replacer(type, key)
if (typeof value === 'string') {
return value
}
return g0
})
}
/**
* Checks if the user-agent header is allowed.
*
* @param {string} ua - The user-agent header.
* @returns {boolean} True if user-agent is allowed, false otherwise.
*/
const allowUA = (ua) => {
if (ALLOW_UA_KEYWORDS_ARR.length === 0) {
return true
}
return ALLOW_UA_KEYWORDS_ARR.some(keyword => ua.includes(keyword))
}
/**
* Main function to handle requests.
*
* @param {Request} request - The request object.
* @returns {Promise<string>} A promise that resolves to the response.
* @throws {Error}
*/
const main = async (request) => {
const {
url,
headers,
} = request
const ua = headers.get('user-agent')
if (!allowUA(ua)) {
throw new Error('Invalid user-agent, failed to pass keyword checking')
}
const {
pathname,
searchParams,
} = new URL(url)
if (pathname !== PATH) {
throw new Error(`Unknown request: ${pathname}`)
}
const domain = searchParams.get('IP4P_DOMAIN')
if (!domain) {
throw new Error('Domain name not provided')
}
const ip4p = await resolveIP4P(domain)
const config = await getConfig(CONFIG_URL, (type, key) => {
if (type === 'ip4p' && (key === 'ip' || key === 'port')) {
return `${ip4p[key]}`
} else if (type === 'query') {
return searchParams.get(key) || ''
}
return undefined;
})
return config
}
// Cloudflare worker export
export default {
/**
* Cloudflare Worker Fetch Function.
*
* @param {Request} request - The incoming request object.
* @param {Object} env - The environment object.
* @param {Object} ctx - The context object.
* @returns {Promise<Response>} A promise that resolves to the response.
*/
async fetch(request, env, ctx) {
try {
const content = await main(request)
return new Response(content, {
status: 200,
headers: {
'cache-control': 'no-cache, no-store'
}
})
} catch (e) {
console.error(e)
return new Response('', {
status: 404,
headers: {
'cache-control': 'no-cache, no-store'
}
})
}
},
}
Config file (can be yaml, json, whatever format you like):
mixed-port: 7890
mode: rule
ipv6: true
dns:
ipv6: true
proxies:
-
name: proxy-server
type: ss
server: ${ip4p.ip}
port: ${ip4p.port}
cipher: "${query.cipher}"
password: "${query.password}"
udp: true
proxy-groups:
-
name: PROXY
type: select
proxies:
- proxy-server
- DIRECT
rule-providers:
reject:
type: http
behavior: domain
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt"
path: ./ruleset/reject.yaml
interval: 86400
private:
type: http
behavior: domain
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/private.txt"
path: ./ruleset/private.yaml
interval: 86400
gfw:
type: http
behavior: domain
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/gfw.txt"
path: ./ruleset/gfw.yaml
interval: 86400
tld-not-cn:
type: http
behavior: domain
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/tld-not-cn.txt"
path: ./ruleset/tld-not-cn.yaml
interval: 86400
telegramcidr:
type: http
behavior: ipcidr
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/telegramcidr.txt"
path: ./ruleset/telegramcidr.yaml
interval: 86400
applications:
type: http
behavior: classical
url: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/applications.txt"
path: ./ruleset/applications.yaml
interval: 86400
rules:
- RULE-SET,applications,DIRECT
- RULE-SET,private,DIRECT
- RULE-SET,reject,REJECT
- RULE-SET,tld-not-cn,PROXY
- RULE-SET,gfw,PROXY
- RULE-SET,telegramcidr,PROXY
- MATCH,DIRECT
https://github.com/sub-store-org/Sub-Store 的节点域名解析支持了 IP4P
clash.meta(mihomo) 已支持 IP4P 出站
感谢 亚托莉佬以及 mihomo 开发组接受我的建议(
用法: 开启 IPv6, 在需要使用的地方配置 IP4P 域名.
IP4P 节点和 ip4p.web.com 这个服务可以正常使用
ipv6: true
dns:
ipv6: true
experimental:
dialer-ip4p-convert: true
proxies:
- name: IP4P
server: ip4p.proxy.com
port: 1
...
rules:
- DOMAIN,ip4p.web.com,DIRECT
IP4P 请求自动重定向(以 Surge 为例)
效果为 访问 http://ip4p.com/a?v=1
时, 自动根据 IP4P 信息重定向为 http://1.1.1.1:1234/a?v=1
使用场景
使用固定的 URL 访问 STUN 打洞的内网服务
模块和脚本见 https://t.me/zhetengsha/1198
TCP 打洞部署 Vmess TCP 代理服务 让流量回家,通过脚本生成 vmess 分享链接保持更新。
可以利用自部署的pastebin服务,例如SharzyL/pastebin-worker,来提供分享链接,不必暴露自己的web服务。
配合 natmap 的 linux 脚本(也需要 base64 命令):
#!/bin/bash
ip_address="${1}"
port="${2}"
pastebin_url="https://shz.al/"
pastebin_name="<随机字符串a>"
pb_pass="<随机字符串b>"
raw_ss_url="ss://2022-blake3-aes-128-gcm:<密码>@${ip_address}:${port}#ss-home4
ss://2022-blake3-aes-128-gcm:<密码>@<其他地址>#ss-home6
"
# Apply base64 encoding
base64_encoded=$(echo -n "${raw_ss_url}" | base64 -w 0)
# Upload the result using curl
curl -Fc="${base64_encoded}" -Fe="24M" -Fs="${pb_pass}" -Fn="${pastebin_name}" "${pastebin_url}"
curl -X PUT -Fc="${base64_encoded}" -Fe="24M" "${pastebin_url}~${pastebin_name}:${pb_pass}"
然后就可以用https://shz.al/~<随机字符串a>
作为订阅地址了。
我尝试着给 WireGuard iOS 官方客户端添加了 IP4P 支持,自己用下来没有问题,这里分享出来。
用法也很简单,和 Android 基本一样,不过会判断 peer 端口填写为 0 才会开启 IP4P 解析。
https://github.com/yv-code/wireguard-apple
感谢各位开源作者。
截至 2024.06.03 , 使用NATMap在NAT-1私网IP宽带上部署Web服务 一文中的 wdns 脚本已不能正常运作,应该是 Cloudflare 改了 API,故更新脚本。脚本中的子域名,Origin 规则(名称默认 natmap) 都需要在面板中预先创建。脚本需要 jq 支持。
#!/bin/sh
TOKEN='EvkRQ-23424234234_j0X7x_Z1tVs3ngc9VUoC'
ZONE="test.tt.com"
SUB_DOMAIN="home"
ORIGIN_RULE="natmap"
DOMAIN="${SUB_DOMAIN}.${ZONE}"
ZONES_FILE="/tmp/zones.json"
DNS_RECORDS_FILE="/tmp/dns_records.json"
ADDR=${1}
PORT=${2}
#GET ZONE_ID:
[ ! -e $ZONES_FILE ] && {
curl -sX GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type:application/json" | jq .result > $ZONES_FILE
}
ZONE_NUM=$(jq '. | length' $ZONES_FILE)
ZONE_NUM_FOR=$(expr $ZONE_NUM - 1)
for i in $(seq 0 $ZONE_NUM_FOR); do
zone="$(jq -r ".[$i].name" $ZONES_FILE | sed 's/"//')"
[ $zone = "$ZONE" ] && {
ZONE_ID="$(jq -r ".[$i].id" $ZONES_FILE | sed 's/"//')"
break
}
done
#GET RECORD_ID:
[ ! -e $DNS_RECORDS_FILE ] && {
curl -sX GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type:application/json" | jq .result > $DNS_RECORDS_FILE
}
DNS_RECORDS_NUM=$(jq '. | length' $DNS_RECORDS_FILE)
DNS_RECORDS_NUM_FOR=$(expr $DNS_RECORDS_NUM - 1)
for i in $(seq 0 $DNS_RECORDS_NUM_FOR); do
name="$(jq -r ".[$i].name" $DNS_RECORDS_FILE | sed 's/"//')"
[ $name = "$DOMAIN" ] && {
RECORD_ID="$(jq -r ".[$i].id" $DNS_RECORDS_FILE | sed 's/"//')"
break
}
done
# DNS
while true; do
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type:application/json" \
--data "{\"type\":\"A\",\"name\":\"${SUB_DOMAIN}\",\"content\":\"${ADDR}\",\"ttl\":60,\"proxied\":false}" > /dev/null 2> /dev/null
if [ $? -eq 0 ]; then
break
fi
done
# Origin rule
while true; do
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets/phases/http_request_origin/entrypoint" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"rules\":[{\"expression\":\"(http.host eq \\\"${DOMAIN}\\\")\",\"description\":\"${ORIGIN_RULE}\",\"action\":\"route\",\"action_parameters\":{\"origin\":{\"port\":${PORT}}}}]}" > /dev/null 2> /dev/null
if [ $? -eq 0 ]; then
break
fi
done
cf 重定向 脚本 设置好cf 重定向 然后 api 地址浏览器F12抓包下
#!/bin/bash
IP="$1"
IPport="$2"
IPadd="http://${1}:${2}"
sleep 1
logger -t natmap "地址'$IPadd'"
curl -L -k -s "https://api.cloudflare.com/client/v4/zones/9f98e1aa7ed47633335d3570af/rulesets/defe98d8036c400b86111111111/rules/5ba6ee1b7fb4407699182222222220" \
-X 'PATCH' \
-H "Authorization: Bearer HWIucLnR7dfsJtqs-QGa7yTiKDSKHi7RN9N" \
-H "Content-Type:application/json" \
--data '{"description":"mt","expression":"(http.host eq \"m.xx.us.kg\")","action":"redirect","action_parameters":{"from_value":{"status_code":301,"preserve_query_string":true,"target_url":{"expression":"concat(\"http://'$IP':'$IPport'\", http.request.uri.path)"}}},"enabled":true}'
sleep 1
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
可否增加一个TXT解析功能呢?有些环境无法解析IPV6/IP4P
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
可否增加一个TXT解析功能呢?有些环境无法解析IPV6/IP4P
我之前的实现(使用系统DNS解析器)确实在某些系统上存在无法解析AAAA记录的情况,看这版实现可能是可以的,建议实测看看。
这个默认使用223.5.5.5的DNS解析,没有使用系统DNS解析,理论上不存在无法解析AAAA记录的情况,我实测只要能联网的环境都可以使用(PS:如果局域网劫持所有53端口的DNS解析请求到不支持解析AAAA记录的内网DNS另说。。。)。 另外https://github.com/kwxiaozhu/wireguard-android-ip4p 这个项目使用的是DOH 来解析DNS,实测可以正常解析AAAA记录。
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
可否增加一个TXT解析功能呢?有些环境无法解析IPV6/IP4P
这个默认使用223.5.5.5的DNS解析,没有使用系统DNS解析,理论上不存在无法解析AAAA记录的情况,我实测只要能联网的环境都可以使用(PS:如果局域网劫持所有53端口的DNS解析请求到不支持解析AAAA记录的内网DNS另说。。。)。 另外https://github.com/kwxiaozhu/wireguard-android-ip4p 这个项目使用的是DOH 来解析DNS,实测可以正常解析AAAA记录。
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
可否增加一个TXT解析功能呢?有些环境无法解析IPV6/IP4P
感谢,heiher 的版本一直会触发更新,显示"正在获取更新元数据",你的是否可以取消它的更新检测?
这个默认使用223.5.5.5的DNS解析,没有使用系统DNS解析,理论上不存在无法解析AAAA记录的情况,我实测只要能联网的环境都可以使用(PS:如果局域网劫持所有53端口的DNS解析请求到不支持解析AAAA记录的内网DNS另说。。。)。 另外https://github.com/kwxiaozhu/wireguard-android-ip4p 这个项目使用的是DOH 来解析DNS,实测可以正常解析AAAA记录。
wireguard windows便携版客户端,支持IP4P解析,端口随意写,域名解析到IP4P地址时会自动解析IP和端口。 https://github.com/kwxiaozhu/wireguard-windows-portable 缝合两位大佬的成果,实现便携客户端以及IP4P解析。 感谢各位开源作者。
可否增加一个TXT解析功能呢?有些环境无法解析IPV6/IP4P
感谢,heiher 的版本一直会触发更新,显示"正在获取更新元数据",你的是否可以取消它的更新检测?
没做处理,目前是基于最新版改的,没弹更新,后面有更新了我再跟进,升级逻辑在这里 ui/src/main/java/com/wireguard/android/updater/Updater.kt,可以问问chatgpt自己改改
Welcome to share the usage and demos. 欢迎分享用法、示例。