https://www.wireguardconfig.com/
Views: 1
https://www.wireguardconfig.com/
Views: 1
旧标准:
https://datatracker.ietf.org/doc/rfc3489/
RFC 3489 STUN协议设计,2003年发布
https://datatracker.ietf.org/doc/rfc5389/
RFC 5389 STUN工程实现和最佳实践,并对RFC3489进行了更新,2008年发布
新标准:
https://datatracker.ietf.org/doc/rfc5780/
RFC 5780, STUN协议设计,2010年发布
https://datatracker.ietf.org/doc/rfc8489/
RFC 8489 STUN工程实现和最佳实践,2020年发布
STUN的主要作用有两个:
1. 获取通信双方的公网IP地址和端口
2. 检测NAT类型(用于确定后续穿透方案)
内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包
Restricted Cone NAT
内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包
Port Restricted Cone NAT
内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包
Symmetric NAT
内网主机建立一个UDP socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1时,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1); 如果内网主机同时用这个socket给外部主机2发送数据,第一次发送时,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).
这九种包含了RFC 3489中的四种类型
RFC 5780中分为了映射类型和过滤类型
如图1所示,假设一个内网主机HostX的内网IP地址为X,端口号为x,经NAT映射后的外网IP地址为M,端口号为m。为方便描述,将内网的Endpoint记为Endpoint(X,x),映射后外网的Endpoint记为Endpoint(M,m)。内网Endpoint(X,x)发往外网HostD1的IP地址和端口号记为目的Endpoint(D1,d1);发往外网HostD2的IP地址和端口号记为目的Endpoint(D2,d2)。
NAT过滤类型
如图2所示,假设一个内网主机HostX的内网IP地址为X,端口号为x,经NAT映射后的外网IP地址为M,端口号为m。为方便描述,将内网的Endpoint记为Endpoint(X,x),映射后外网的Endpoint记为Endpoint(M,m)。内网Endpoint(X,x)发往外网HostD1的IP地址和端口号记为目的Endpoint(D1,d1);发往外网HostD2的IP地址和端口号记为目的Endpoint(D2,d2)。
图2 NAT过滤类型
把映射类型和过滤类型组合起来就是9种NAT类型,分别对应如下:
以上只是RFC的分类方法,不同厂家设备实现细节上可能还有不一样。
全椎型是打通速度最快的,受限锥型可能会延个几秒(防火墙需要等互相收到对方发过来的包才能建立会话),对称型虽然不能直接打通,但是猜测对方端口在一个范围内进行扫描也是可以的,大部分防火墙来说NAT端口分配都是连续的,下一个连接总是在上一个连接的端口号上加一,如果一次没猜中多猜几次就好了,再加上请求的时候 可以一次性打开多个端口,这样更容易猜中。
https://github.com/coturn/coturn
支持STUN,TURN,ICE,但是对STUN的支持并不完整,不支持探测NAT类型
https://github.com/jselbie/stunserver
仅支持STUN,但完整实现了RFC的所有特性,但是要开启所有特性需要服务器有两个公网IP
https://github.com/HMBSbige/NatTypeTester/tree/master
另外stunserver中自带的也有个客户端
如果网络是多出口的对NAT类型探测和穿透还是有很大影响的,在地址受限测试中客户端访问不同的STUN Server IP如果请求从不同的出口出去的话会导致IP和端口都发生变化,这样就成了端口地址受限NAT。在后面的穿透工作中如果访问NAT Server和实际通信用的不是一个出口(IP),就会出现通信振荡不稳定。所以对于多出口的环境最后能绑定线路,或者把有多出口的一端当成对称型NAT来用,由另一端进行穿透。这种情况在一端进行穿透反而比两端同时进行穿透要稳定。如果STUN能增加多线路的支持就更好了,还能利用多个线路同时传输数据。
https://arthurchiao.art/blog/how-nat-traversal-works-zh/#41-stun-%E5%8E%9F%E7%90%86
https://blog.csdn.net/wh445306/article/details/127831832
https://blog.csdn.net/momo0853/article/details/105387675/
https://zhaoyanbai.com/articles/RFC8489_STUN_zh_CN
https://gist.github.com/mondain/b0ec1cf5f60ae726202e
Views: 5
创建stuntest.sh脚本,内容如下
#!/bin/sh
stunclient --protocol 2 --localport 21 stun.hetao.me
echo "sleep 1"
sleep1
stunclient --protocol 2 --localport 21 stun.hetao.me
然后以不同的时间间隔多次运行这个脚本(可以并行运行),看映射的端口有没有变化,有变化就是超时了
./stuntest.sh 235 udp #第一个参数是间隔时间
test1
Binding test: success
Local address: 100.82.167.62:1233
Mapped address: 117.143.55.240:13837
sleep 238
Binding test: success
Local address: 100.82.167.62:1233
Mapped address: 117.143.55.240:13837
test2
Binding test: success
Local address: 100.82.167.62:1240
Mapped address: 117.143.55.240:13853
sleep 245
Binding test: success
Local address: 100.82.167.62:1240
Mapped address: 117.143.55.240:14115
经过了238秒,端口号没有变,但是245秒端口号就变了,说明超时时间应该不超过240秒也就是4分钟
测试用到的stun服务端和客户端是用的这个项目的代码:
https://github.com/jselbie/stunserver
也可以用coturn中的turnutils_natdiscovery中的工具来测试
turnutils_natdiscovery -m -f -t -T 500 stunserver2024.stunprotocol.org
这样会在开始的时候发一次请求,500秒后再发一次请求
openwrt修改NAT会话超时(其它系统方法类似)
vi /etc/sysctl.d/11-nf-conntrack.conf
修改
net.netfilter.nf_conntrack_udp_timeout=240
net.netfilter.nf_conntrack_udp_timeout_stream=300
生效
/etc/init.d/sysctl reload
查看当前NAT会话超时
sysctl net.netfilter.nf_conntrack_udp_timeout
Views: 1
可以用Ping命令来测试当前传输介质的MTU
Windows用法
ping -l 1464 -f 223.5.5.5
-l指定的是ping包的载荷大小,由于ping包头是28字节,所以载荷加上28就是真实MTU大小,-f指定不可拆分包。
Linux用法
ping -s 1464 -M do 223.5.5.5
使用-M do参数发送大包时是会自动提示MTU大小,不用再自己计算了
如果是IPv6则要减去48字节(IP头40字节,ICMPv6头8字节),如下MTU 1500的icmpv6载荷为1452字节。
ping -s 1452 -M do fe80::f8b3:a6ff:fece:7589
不同介质/线路下MTU的大小
– 以太网
默认都是1500,一般以太网卡也可以配置超大帧
– PPPOE拨号网络
可以是1492,也可以是1480,国内一般都是1492
– wireguard
wireguar的报头是固定40个字节,IPV4的报头是20字节,IPV6报头是40字节,PPPOE是8个字节,所以在IPv4网络下wireguard的MTU是1500-40-20-8=1432,在IPv6网络下是1500-40-40-8=1412
– IPSec
因为PSec是高度可配置的,不同的配置下MTU是不一样的,需要实际去测试
– udp2raw
官方没有说udp2raw的报头是多长的,只说MTU是13xx(等于没说),我用命令实测是88个字节的头部(即faketcp头部,应该也是变长的)所以PPPOE+Udp2raw+Wireguard的MTU就是1500-40-88-20-8=1344,如果是IPV6则是1324。
关于双方MtU不同的情况:
如果通信是一对一的,则两边设置相同的MTU即可,如果是面向多个端点的通信则需要需要设置为所有端点MTU最低的那个。设置MTU考虑的是对方及与对方通信线路的接收和传输能力。是取决于别人,面不是自己(一般自己都是1500)。
关于MSS钳制可以看这一篇:
这个网站列出了各个协义的头部长度:
https://baturin.org/tools/encapcalc/
参考:
https://zhuanlan.zhihu.com/p/532370376
Views: 10
转换为C语言模块后可以与Python解释器编译到一起作为内置模块用,也可以外部模块import后使用。
嵌入式模块可以嵌入到独立C语言程序中执行
首先编写普通的python模块代码,然后在python代码的基础上创建pyx代码,通过cdef声明可以定义原生C语言函数,而不用通过Python解释器调用,可以更好的与C语言交互。
pyx代码的写法可以参考cython官方文档
hello.pyx文件
cdef public char *say_hello_to(char* name):
return name
c-test.c文件
#include "Python.h"
#include "hello.h"
int main(int argc, char *argv[])
{
PyObject *pmodule;
char *result;
wchar_t *program;
// 读取命令行参数
program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0], got %d arguments\n", argc);
exit(1);
}
/* 把模块注册为内置模块 */
if (PyImport_AppendInittab("test", PyInit_hello) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* 把命令行参数传给Python解释器 */
Py_SetProgramName(program);
/* 初始化Python,这一步是必须的. */
Py_Initialize();
/* 调用Python中的函数. */
result = say_hello_to("hetao");
// PyErr_Print();
printf("result:%s\n", result);
/* Clean up after using CPython. */
PyMem_RawFree(program);
Py_FinalizeEx();
return 0;
/* Clean up in the error cases above. */
exit_with_error:
PyMem_RawFree(program);
Py_Finalize();
return 1;
}
cython hello.pyx --3str
执行cython后会生成hello.c和hello.h两个文件
gcc -o libhello.so -shared -fPIC -I/usr/include/python3.10 hello.c
gcc -o test-c -I/usr/include/python3.10 -I./ -L./ -ltest -lpython3.10 -Wl,-rpath . test-c.c
运行结果
$ ./test-c
result:hetao
hello.pyx文件
def say_hello_to(name):
return name
setup.py文件
from setuptools import setup
from Cython.Build import cythonize
setup(
name='Hello',
ext_modules=cythonize("hello.pyx"),
)
构建模块
python setup.py build_ext --inplace
test-hello.py文件
import hello
result = hello.say_hello_to("hetao")
print(result)
cdef、def 和 cpdef 的区别:
在 Cython 中,有三种不同的函数声明方式:cdef、def 和 cpdef。
cdef 声明的函数是纯 C 函数,只能从 Cython 代码中调用,不可从 Python 代码中访问。
def 声明的函数是 Python 函数,可以从 Python 代码中调用,但会带来一些性能开销。
cpdef 声明的函数是混合函数,既可以从 Cython 代码中调用,也可以从 Python 代码中调用,其底层其实生成了两个函数版本。
关于编译参数:
获取编译参数
python3-config –cflags
获取链接参数
python3-config –ldflags
Views: 1
#include "Python.h"
int main()
{
Py_Initialize();
PyObject *pmodule = PyImport_ImportModule("os");
//获取当前用户的id
PyObject *pfunc = PyObject_GetAttrString(pmodule, "geteuid");
PyObject *arg = Py_BuildValue("()");
PyObject *result = PyObject_Call(pfunc, arg, NULL);
long retval = PyLong_AsLong(result);
printf("result: %ld\n", retval);
Py_XDECREF(result);
Py_FinalizeEx();
return 0;
}
gcc -c -I/usr/include/python3.10 -L/usr/lib/python3.10/config-3.10-x86_64-linux-gnu -L/usr/lib/x86_64-linux-gnu -lpython3.10 -lcrypt -ldl -lm -lm test.c
gcc -o test test.o -L/usr/lib/python3.10/config-3.10-x86_64-linux-gnu -L/usr/lib/x86_64-linux-gnu -lpython3.10 -lcrypt -ldl -lm
执行后返回如下:
$ ./test
result: 1001
这种方法可以调用普通Python函数
Views: 1
C语言写的Python模块中可以有python函数,也可以有C函数。
以下代码导入Python模块并执行其中的C函数
ctest.c文件内容
#include "Python.h"
#include "test.h"
int main(int argc, char *argv[])
{
PyObject *pmodule;
int *result;
wchar_t *program;
// 读取命令行参数
program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0], got %d arguments\n", argc);
exit(1);
}
/* 把模块注册为内置模块 */
if (PyImport_AppendInittab("test", PyInit_test) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* 把命令行参数传给Python解释器 */
Py_SetProgramName(program);
/* 初始化Python,这一步是必须的. */
Py_Initialize();
/* 调用Python中的函数. */
result = hello(1, 2);
// PyErr_Print();
printf("result:%d,%d,%lu\n", result[0], result[2], sizeof(result));
/* Clean up after using CPython. */
PyMem_RawFree(program);
Py_FinalizeEx();
return 0;
/* Clean up in the error cases above. */
exit_with_error:
PyMem_RawFree(program);
Py_Finalize();
return 1;
}
编译
gcc -o ctest -I/usr/include/python3.10 -I./ -L./ -ltest -lpython3.10 -Wl,-rpath . ctest.c
如果是调用python自带的内置函数则不需要执行PyImport_AppendInittab,直接调用即可。
只有C语言写的模块才能注册为内置模块
关于Python解释器初始化的问题:
cython官方文档上说如果不调用Py_Initialize()进行初始化极有可能会发生程序崩溃,但并没有说明什么情况下会崩溃。经过我的测试如果所调用的C函数不涉及Python代码,也不会调用其它可能会执行Python代码的函数则不会崩溃,反之就会发生断错误。也就是说只能在C语言的内存空间内执行。对于一些Python内置的C语言写的函数是可以不初始化就调用的,如果是那种需要import的就不可以了。
Views: 1
nft add chain inet fw4 gfw_prerouting { type filter hook prerouting priority mangle\; policy accept\; }
nft add chain inet fw4 gfw_output { type route hook output priority mangle\; policy accept\; }
nft add rule inet fw4 gfw_prerouting ip daddr @GFWLIST counter ct mark set 0x10
nft add rule inet fw4 gfw_prerouting ip daddr @GFWLIST counter meta mark set ct mark
nft add rule inet fw4 gfw_output ip daddr @GFWLIST counter ct mark set 0x10
nft add rule inet fw4 gfw_output ip daddr @GFWLIST counter meta mark set ct mark
以上gfw_output链处理本地发现的包,gfw_prerouting处理转发的包,在foward链中处理mark是不生效的(所以无法用uci配置)。
通过配置文件配置nft
创建文件/etc/nftables.d/11-gfw.nft
文件内容:
chain gfw_forward {
type filter hook prerouting priority mangle; policy accept;
ip daddr @GFWLIST counter ct mark set 0x10
ip daddr @GFWLIST counter meta mark set ct mark
}
chain gfw_output {
type route hook output priority mangle; policy accept;
ip daddr @GFWLIST counter ct mark set 0x10
ip daddr @GFWLIST counter meta mark set ct mark
}
service firewall restart
这里要注意的是output hook只能是rotue链,不能是filter链,而prerouting则只能是filter链
Views: 1
nft中的set是属于某个table下面的,不像ipset是全局的,所以要先建立table
nft list tables
nft add table inet gfw
nft add set inet gfw GFWLIST { type ipv4_addr\; }
nft add set inet gfw GFWLIST6 { type ipv6_addr\; }
添加IP地址
nft add element inet gfw GFWLIST { 111.22.33.4 };
nft list set inet gfw GFWLIST
删除IP地址
nft delete element inet gfw GFWLIST {20.205.243.166}
openwrt中配置nftset
openwrt中默认的table是fw4,family是inet
关于family:
ip ipv4协议
ipv6 ipv6协议
inet 双栈协议
不指定family时nft默认是ipv4协议,dnsmasq中不指定family时默认也是ipv4协议
Views: 3