Nginx模块开发中使用PCRE正则表达式匹配

Nginx内部对pcre库的常用操作进行了封装. 封装的源码位于nginx/src/core/ngx_regex.c, 同时将pcre内使用的内存池更变为了Nginx的内存池.

  • pcre_compile:

    Nginx封装了pcre_compile方法. 方法名为ngx_regex_compile.

    ngx_regex_compile方法的参数需要传入一个ngx_regex_compile_t来进行编译正则等操作.

    ngx_regex_compile_t结构如下:

    typedef struct {
        ngx_str_t     pattern; // 正则
        ngx_pool_t    *pool;   // 每个request分配的内存池
        ngx_int_t     options; // pcre options
    
        ngx_regex_t   *regex;  // 编译完毕后的pcre实例
        int           captures;
        int           named_captures;
        int           name_size;
        u_char       *names;
        ngx_str_t     err;     // 错误信息
    } ngx_regex_compile_t;`
    

    当编译正则成功时会返回NGX_OK并且会在内部调用pcre_study来进一步提高正则匹配性能. 失败时会返回NGX_ERROR. 同时失败的错误信息会保存在err成员变量中.

  • pcre_exec

    Nginx同样封装了pcre_exec封装后的方法名为:ngx_regex_exec 源码位于nginx/src/core/ngx_regex.h文件中:

    #define ngx_regex_exec(re, s, captures, size)                                \
        pcre_exec(re->code, re->extra, (const char *) (s)->data, (s)->len, 0, 0, \
                  captures, size)
    

    可以看到参数re要求的是ngx_regex_compile_t中的regex成员变量. 而被搜索的字符串被替换成了Nginx内部的字符串类型ngx_str_t. 同时也将Nginx内部不常用的搜索偏移以及选项设置为成0. 如果仍需要使用偏移以及选项的话可以直接使用pcre_exec来跳过Nginx的封装.

Refs:

SS通讯步骤

目前在写一个Socks5转SS的工具(某些奇怪需求需要), 在互联网上翻了几个网站并没有详细说明SS通讯步骤. 所以在这记录下.

密码初始化

本文章aes-256-cfb加密来说明, 当使用AES时需要提供KeyIV(初始化向量). 其中IV类似于给hash加盐, 不过这个盐是基于上一个数据块加密后的出来的盐. 那为啥还需要提供IV呢?因为第一次用的时候没上一块数据只能自己指定一个.

SS生成KeyIV是基于用户指定的密码来生成的, 以下为TypeScript基于密码生成KeyIV代码:

function generateKeyIVByPassword(passwordString: string, keyLength: number, ivLength: number): any {
    var password = new Buffer(passwordString, "binary");
    var hashBuffers: Array<Buffer> = [];
    for (var dataCount = 0, loopCount = 0; dataCount < keyLength + ivLength; loopCount++) {
        var data: any = password;
        if (loopCount > 0) {
            data = Buffer.concat([hashBuffers[loopCount - 1], password]);
        }
        var md5 = crypto.createHash("md5");
        var md5Buffer = md5.update(data).digest();
        hashBuffers.push(md5Buffer);
        dataCount += md5Buffer.length;
    }
    var hashBuffer: Buffer = Buffer.concat(hashBuffers);
    return {
        key: hashBuffer.slice(0, keyLength),
        iv: hashBuffer.slice(keyLength, keyLength + ivLength)
    };
}

从上述代码中可以得知SS作者先将用户指定的密码迭代MD5多次; 其结束条件是迭代多次的MD5结果长度相加, 如果长度大于加密算法所需Key长度 + IV长度则退出循环. 最后将MD5 Hash结果截断来获得KeyIV.

通讯过程(TCP过程)

当客户端连接至服务端后会直接发送 客户端IV + 已被加密的Socks5头 + TCP数据.

IV交换

Client连接后, Client发送的第一个数据前N个字节(取决于加密算法)是AESIV用于来解密来自Client的数据.
然后Server发送Client的第一个数据也是类似. 其结构图如下:
ShadowsocksIVED.png

注意!SS中这样交换IV是不安全做法.

通讯

SS省略了Socks5协商过程直接进入通讯过程. Socks5通讯需要客户端提供 目标IP或域名 + 端口. 当客户端提供完成之后服务端会连接至目标IP并返回是否连接成功; 如果连接成功就能够直接通讯了.

格式如下:
027071B4-415D-4ACE-BF39-EFABB2F7B64A.png

上述说过SS省略了协商过程, 所以这里Socks5请求也是缩水的. 其把前3个字节砍掉了, 也就是说Client只会发来ATYP, DST ADDR, DST PROT. 当连接至DST ADDRDST ADDR发送的数据加密发送给Client, Client发送的数据解密发送给DST ADDR. 这样SS就完成了通讯.

参考资料

CentOS7 LXC网络以及配置

安装EPEL源.

yum install epel-release.noarch -y

禁用Firewalld使用iptable代替.

systemctl stop firewalld
systemctl disable firewalld
yum install iptables iptables-services net-tools -y

创建iptables默认规则.

echo "# sample configuration for iptables service
# you can edit this manually or use system-config-firewall
# please do not ask us to add additional ports/services to this default configuration
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT" > /etc/sysconfig/iptables;
systemctl enable iptables.service
systemctl start iptables.service

安装Linux Container.

yum install lxc lxc-templates -y

为容器设置虚拟交换机.

echo 'DEVICE="virbr0"
ONBOOT="yes"
TYPE="Bridge"
BOOTPROTO=static
IPADDR=10.0.0.1
NETMASK=255.255.255.0' > /etc/sysconfig/network-scripts/ifcfg-lxcbr0;

启用内核转发以及虚拟内存调整.

echo "net.ipv4.ip_forward = 1
vm.swappiness = 10
net.ipv4.tcp_timestamps = 0" >> /etc/sysctl.conf;
sysctl -p

设置iptables nat.

iptables --flush POSTROUTING --table nat
iptables --flush FORWARD
iptables -t nat -A POSTROUTING -o 网卡 -j MASQUERADE
iptables-save > /etc/sysconfig/iptables

应用相关服务.

systemctl start lxc
systemctl enable lxc
systemctl restart network

Q&A

  • Q: 容器启动后一直持续占用CPU.

  • A: 编辑/var/lib/lxc/{container}/config加入:

    lxc.autodev = 1
    lxc.kmsg = 0
    
  • Q: 容器设置静态IP.

  • A:编辑/var/lib/lxc/{container}/config加入:

  • lxc.network.type = veth
    lxc.network.link = virbr0
    lxc.network.flags = up
    lxc.network.name = eth0
    lxc.network.ipv4 = 10.0.0.2/24
    lxc.network.ipv4.gateway = 10.0.0.1
    

常用命令

# 创建指定版本的容器
lxc-create -n centos -t mcentos -- --release 6

# 当目标IP为192.168.0.160且端口为2222 NAT 10.0.0.2:22 
iptables -t nat -A PREROUTING -d 192.168.0.160 -p tcp --dport 2222 -j DNAT --to 10.0.0.2:22

# 当目标网口为ens160且端口为2222时 NAT 10.0.0.2:22
iptables -t nat -A PREROUTING -i ens160 -p tcp --dport 2222 -j DNAT --to-destination 10.0.0.2:22

# 当任意网口目标端口为2222时 NAT 10.0.0.2:22
iptables -t nat -A PREROUTING -p tcp --dport 2222 -j DNAT --to-destination 10.0.0.2:22

# 当目任意网口标端口为2222~4444 NAT 10.0.0.2:2222~4444
iptables -t nat -A PREROUTING -p tcp --dport 2222:4444 -j DNAT --to-destination 10.0.0.2:2222-4444

# 为ens160网口增加一个ip
ifconfig ens160 add 192.168.0.161

# 为ens160网口删除一个ip
ifconfig ens160 del 192.168.0.161

# DMZ
iptables -t nat -A PREROUTING -d 192.168.0.161 -j DNAT --to 10.0.0.2

# 删除DMZ
iptables -t nat -D PREROUTING -d 192.168.0.161 -j DNAT --to 10.0.0.2

# 限速 1m上 2m下
wondershaper ens160 1024 2048

# 解除限速
wondershaper clear ens160

OrangePi 开启离线CPU核心

OrangePi 官方 LuUbuntu系统貌似存在BUG,启动后没开启CPU03核心。

root@orangepi:~# lscpu
Architecture:          armv7l
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-2
Off-line CPU(s) list:  3
Thread(s) per core:    1
Core(s) per socket:    3
Socket(s):             1

这时我们就需要手动开启CPU03核心了

echo 1 > /sys/devices/system/cpu/cpu3/online

再次执行lscpu看下CPU核心是不是已经全部开启了

root@orangepi:~# lscpu
Architecture:          armv7l
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    1
Core(s) per socket:    4
Socket(s):             1

2015-09-24 Update: 最后发现是CPU太热了 导致了CPU自动下线。。。