Comments (13)
今天刚写的 qBittorrent 打洞脚本,可能不太完善,欢迎大家反馈:
https://github.com/Mythologyli/qBittorrent-NAT-TCP-Hole-Punching
from natmap.
尝试实现了 NAT-PMP 协议给 Transmission 用 ,但是它的支持好像有问题,给他返回映射端口后,它把 IPv6 的监听端口也改了,导致 IPv6 入站全被防火墙拦截了。。。
应该没问题,就是一时没有v6入站
https://github.com/OpportunityLiu/nat-mapmp
from natmap.
TCP 打洞部署 Vmess TCP 代理服务 让流量回家,通过脚本生成 vmess 分享链接保持更新。
对比 wireguard 优点:
- 几乎所有的翻墙代理客户端都可以支持,无需安装专用改版客户端。
- Vmess 服务端部署更简单更轻量,只需要运行代理服务端程序即可,无需任何系统特权修改系统设置。
- TCP 协议传输,可以解决某些网络环境下 UDP 被限制的问题。
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 地址填入订阅功能即可。
注意:
- 生成的订阅文件一定要使用复杂不规律文件名/路径,而且订阅 url 一定要用 https 加密保证安全。
- 代理客户端可能默认局域网地址段直连,这时需要在代理客户端路由功能中把家里局域网 ip 段设置为走代理。
- 多人使用的情况下,可以通过代理服务端的路由功能,限制对内网地址段的访问。
from natmap.
使用NATMap在NAT-1私网IP宽带上部署 Trojan 服务,并通过其访问内网服务(回家)
from natmap.
对于wireguard方式,写了一个PowerShell脚本,能够自动修改配置文件的Endpoint
并调用wireguard.exe
进行连接。
使用方法:
- 安装wireguard-windows,用客户端连接测试成功。
- 在文件夹
C:\example
下建立wg.ps1和nat.conf,粘贴Gist内容。 - 按照实际情况修改
nat.conf
,以及wg.ps1
中$Hostname
部分。Endpoint
不必修改。 - 以管理员身份运行
PowerShell
- 设置
ps1
脚本运行权限:Set-ExecutionPolicy RemoteSigned
(或Unrestricted) - 启动Wireguard:
C:\example\wg.ps1 -up
- 停止Wireguard:
C:\example\wg.ps1 -down
在Windows 11, Powershell 5.1.22621.963测试通过,也可以配合Windows下的sudo使用。
另外,在Android下,也可以用termux运行nm-echo.sh来获得IP地址,可以不必更换客户端。
from natmap.
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
from natmap.
https://github.com/sub-store-org/Sub-Store 的节点域名解析支持了 IP4P
from natmap.
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>
作为订阅地址了。
from natmap.
TCP
UDP
from natmap.
uTorrent / qBittorrent / Transmisson 自动更新端口脚本
from natmap.
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
from natmap.
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
from natmap.
我尝试着给 WireGuard iOS 官方客户端添加了 IP4P 支持,自己用下来没有问题,这里分享出来。
用法也很简单,和 Android 基本一样,不过会判断 peer 端口填写为 0 才会开启 IP4P 解析。
https://github.com/yv-code/wireguard-apple
感谢各位开源作者。
from natmap.
Related Issues (20)
- UDP不能绑定服务端口 HOT 8
- IP4P版的FRPC无法正常解析IP4P地址 HOT 7
- 咨询一下启动以后有没有日志记录外网ip和端口? HOT 5
- Operation in progress什么鬼报错
- 不知道怎么给wiki提pr,大佬看下能不能更新下wiki上关于ssh的说明 HOT 2
- How about creating the docker image for NATMap?
- 问一个非常弱智的问题 HOT 5
- 在Android上执行时发生了错误 HOT 7
- Origin rule无法完成 HOT 4
- [Feature Request] iptables / nftables forwarding HOT 6
- How does stun - http work? HOT 1
- tcp和udp能否共存? HOT 7
- 添加docker支持 HOT 12
- ssh断开连接问题 HOT 4
- 网络变动后故障 HOT 3
- ddns via cloudflare HOT 1
- 希望能增加参数,不要错误日志,或者把日志写入文件。 HOT 3
- notification scripts can be run manually, but natmap cannot
- 在开启clash的服务器上,无法打洞 HOT 7
- 请求在wireguard安卓客户端增加TXT记录解析 HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from natmap.