校园网&openwrt记(五)构造udp包,Lua的int32运算和bit运算

  在构造获取权限的UDP包的过程中遇到了两个麻烦的问题,就是在计算数据包的校验和的时候,需要将校验和作为int32整形来计算,需要截断数据,但是Lua是用double来存储数值的,所以需要简单写一个workaround方法来处理;并且校验和还需要进行一些bit操作,但是Lua的数值运算没有bit运算,所以这里需要导入nixio库的bit运算库。(当然,也可以写C,然后用Lua调用,但是这样不符合该应用的现状)
  我们先假设有这样一个计算校验和算法的伪代码:

function getchecksum(bytes)
    checksum = 666666
    for b in bytes
        if res > 0
            checksum ^= checksum >> 2 + checksum << 5 + b
        else
            checksum ^= (checksum >> 2) | 0xC0000000 + checksum << 5 + b
    return checksum & 0x7FFFFFFF

  其中输入的参数是需要计算校验和的二进制字符串,checksum是int32整形。

  • bit运算

      使用C语言编写的nixio的bit库。查看源码后发现: 

/* 52 bit maximum precision */
#ifdef NIXIO_DOUBLE
#define NIXIO_BIT_BMAX 52
#define NIXIO_BIT_NMAX 0xfffffffffffff
#else
#define NIXIO_BIT_BMAX 32
#define NIXIO_BIT_NMAX 0xffffffff
#endif

  如果Lua编译时使用了默认的double来保存数值的话,bit运算的最大数值为0xfffffffffffff(double的尾数位为52)。nixio在bit运算过程中,每个函数(例如bit.band(target, a1, a2 …))都将各参数从堆栈中取出来保存在类型为uint64_t 局部变量中,这样就可以通过for循环对这些整型局部变量进行bit运算,运算完毕后将结果压入堆栈作为函数返回。
  源码中以一个宏定义来定义bit运算,BIT_XOP是C语言的bit运算操作符,NIXIO_BIT_XOP其实就是luaL_checknumber(见nixio.h),从堆栈中获取第i个参数,这个参数是数值参数,lua_gettop获取堆栈元素的个数(参数个数),nixio__pushnumber其实就是lua_pushnumber(见nixio.h),将oper作为函数返回值。

#define NIXIO_BIT_XOP(BIT_XOP)                      \
    uint64_t oper = nixio__checknumber(L, 1);       \
    const int args = lua_gettop(L);                 \
                                                    \
    for (int i = 2; i <= args; i++) {               \
        uint64_t oper2 = nixio__checknumber(L, i);  \
        oper BIT_XOP oper2;                         \
    }                                               \
                                                    \
    nixio__pushnumber(L, oper);                     \
    return 1;

  然后任意的bit运算函数都以以下方式来定义(bit and为例):
  

static int nixio_bit_and(lua_State *L) {
    NIXIO_BIT_XOP(&=);
}

  这样,在Lua中只需要导入nixio的bit库后就可以进行bit运算了:
  

bit = require "nixio".bit

--[[以下operX是数值操作数,shift是移位的个数

bit.arshift (oper, shift)         --对oper算数右移shift位
bit.band (oper1, oper2, ...)      --对多个操作数进行逻辑与
bit.bnot (oper)                   --对oper取反
bit.bor (oper1, oper2, ...)       --对多个操作数进行逻辑或
bit.bxor (oper1, oper2, ...)      --对多个操作数进行逻辑异或
bit.cast (oper)                   --将oper转换为可以bit运算的数值
bit.check (bitfield, flag1, ...)  --检查数值bitfield中某一位是否置1,例如bit.check(16, 0x10) 这将返回true
bit.div (oper1, oper2, ...)       --地板除法,oper1/oper2/...
bit.lshift (oper, shift)          --oper向左移shift位
bit.rshift (oper, shift)          --oper向右移shift位
bit.set (bitfield, flag1, ...)    --将某些位置为1,等价于bor
bit.unset (bitfield, flag1, ...)  --将某些位置为0

--]]
  • 处理int32运算
      由于checksum是int32类型,所以在进行上述算法时有以下几点需要注意:
      1. 每次bit运算后,由于Lua的bit有效位一般都是52位,所以需要对结果与0xffffffff进行逻辑与,防止32位以后的位数影响数值,这样每次bit运算后的结果都是真正的int32,只是Lua计算时需要将其转化罢了。
      2. 数值计算时,需要将数值转化为int32。由于经过bit运算后,数值是32位的,但是Lua返回的是由uint64(见源码)转化而来的Lua数值类型,直接使用会等效的使用为uint32,这时可以使用bit向右移位运算获取其符号值,将其转化为正确的int32数值。
      3. 在进行数值运算(加减乘除)后需要判断数值是否溢出(-2,147,483,648 <= int32 <= 2,147,483,647),溢出则进行截断,使用加上2的32次方或减去2的32次方即可。

      于是,综上所述,这一个计算校验和的算法可以用Lua这样编写:
      

--转换无符号32位整数为int32
function u2i (uint)
    local rs=uint
    --获取符号位
    local signed = bit.rshift(uint,31)       
        if signed > 0 then  --负数
        rs = bit.band(bit.bnot(uint), 0x7fffffff) + 1
        rs = -1 * rs
    end 
    return rs   
end

--lua用double来保存数值,故进行数值运算时需要将checksum转化为int32
--不参与数值运算的数值不需要u2i,因为我们在bit运算时已经将其视为int32
function calchecksum(bytes)
    local checksum = 0x666666

    for k, v in ipairs(bytes) do
        local rb = u2i(bit.band(bit.rshift(checksum, 2), 0xffffffff))
        local lb = u2i(bit.band(bit.lshift(checksum, 5), 0xffffffff))

        if u2i(checksum) < 0 then
            rb = u2i(bit.band(bit.bor(rb, 0xc0000000), 0xffffffff))
        end

        local temp = rb + lb + v
        --溢出,钟摆原理
        if temp < 0x80000000 then
            temp = temp + 0x100000000
        elseif temp > 0x7fffffff then
            temp = temp - 0x100000000
        end
        checksum = bit.band(bit.bxor(checksum, temp), 0xffffffff)
        --print(checksum)
    end 

    return bit.band(checksum, 0x7fffffff)
end

  此时,校验和算法完成,可以进行udp包的构造了,这里我用table来构造udp包,每个索引保存一个字节数值(byte),根据udp包的结构构造出一个有56个元素的table,将其送入calchecksum函数计算出校验和后,使用string.char函数和字符串连接操作符.. 将table中的数据结合成为二进制字符串data,最终再将校验和以小端模式pack进data中,获取拨号权限的udp包构造完毕。
  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值