博客

  • 在NAT网络中测试UDP会话的超时时间

    创建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

  • MTU测试

    可以用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钳制可以看这一篇:

    TCP MSS钳制解决Wireguard访问github超时问题

    这个网站列出了各个协义的头部长度:

    https://baturin.org/tools/encapcalc/

    参考:

    https://zhuanlan.zhihu.com/p/532370376

    Views: 17

  • windows创建每天每分钟执行的任务

    这里开始时间和持续时间共同确定一天内任务生效的时间段,哪果选选从0点开始持续时间12个小时,则每天的0点到12点任务生效。对于每天执行的任务最长持续时间不能超过一天,也可以选择无限期,效果跟一天是一样的。

    Views: 4

  • 用Cython把Python模块转换为纯C语言模块

    转换为C语言模块后可以与Python解释器编译到一起作为内置模块用,也可以外部模块import后使用。

    1. 创建嵌入式模块

    嵌入式模块可以嵌入到独立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

    2. 创建外部模块

    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

  • C语言调用Python模块中的Python函数

    #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模块中的C函数

    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给符合条件的包打mark

    • 通过命令配置nft
      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链

    • 添加策略路由匹配mark
      ip rule add fwmark 0x10 table 50
      这条策略路由是可以从界面上添加的



      界面配置略繁锁一些。

    Views: 3

  • nftset使用

    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: 6

  • openwrt编译时集成wireguard功能

    如果只内置luci-proto-wireguard,会出现cannot find dependency kmod-crypto-kpp for kmod-crypto-lib-curve25519错误,需要把kmod-crypto-kpp模块也内置

    这个编译错误还是个老问题,参考这里:
    https://forum.openwrt.org/t/build-fails-with-multiple-errors/124382

    Views: 3

  • openwrt升级后重新安装软件包

    openwrt升级后原来安装的软件包就没有了,用这条命令可以重新安装所有丢失的软件包(前提是升级前对软件列表进行了备份)
    opkg update && opkg list-installed | cut -f 1 -d ' '| sort -u > /tmp/currentpkg && cat /etc/backup/installed_packages.txt | cut -f 1 | sort -u > /tmp/oldpkg && grep -v -F -x -f /tmp/currentpkg /tmp/oldpkg > /tmp/inst && opkg install $(cat /tmp/inst | sort -u) && rm /tmp/currentpkg /tmp/oldpkg /tmp/inst

    Views: 3