一、修正错误
之前在测试过程中,经常性的出现Invalid msg错误,导致连接被重置。经过调查,发现原来是数据分片时最后一个分片的长度计算有误导致。下面我们来分析一下这个错误的代码:
int check_msg(client_t* client, msg_t* msg) { size_t msg_data_len; if (msg->zone.clip) { if (msg->zone.last) msg_data_len = msg_data_length(msg) % client->max_length; else msg_data_len = client->max_length; } else msg_data_len = msg_data_length(msg); if (checksum(msg, sizeof(msg_t) + msg_data_len)) { SYSLOG(LOG_ERR, "Invalid msg"); return 0; } return 1; }
由上面的代码可以看出,当到来的数据包为最后一个分片时,根据代码中的运算方法直接与对端的max_length取模,此时如果最后一个分片的长度正好能被max_length整除时。所计算出的长度就是错的,应此会平凡的遇到Invalid msg错误。与该错误类似的问题已全部修正,已修正的函数有:
network.c: process_clip_msg和check_msg函数 msg_group.c: parse_msg_group函数 server.c: tcp_process函数 client.c: client_process函数
二、支持DHCP
- 增加配置文件中的use_dhcp字段并增加lua接口
- 修改sys_login_msg_t结构
typedef struct { unsigned short major_version; unsigned char minor_version : 4; unsigned char revision_version : 4; unsigned int ip; unsigned int gateway; unsigned char mask; unsigned short internal_mtu; unsigned char signature[31]; unsigned char dhcp; } sys_login_msg_t;
将头3个字节修改为版本号的定义,在末尾增加31字节的signature和1字节的dhcp标志位,使用31字节作为signature的长度的意义在于与下一字节的dhcp标志位组合后使整个结构对齐到46字节。由于该校验工作只发生在连接建立后,因此出于安全和拓展因素考虑,在配置文件中只记录签名文件的路径,在每次进行校验时由lua脚本动态读出并做校验。这样做的好处是:(1) 安全,文件内容永远不记录在内存中 (2) 拓展性强,当服务器端添加新的客户端签名时无需重启服务器端程序。 需要注意的是:建议为每个单独的客户端配置不同的签名,这样做的好处是,每个客户端都独立持有各自的签名,更加安全。
- 修改server_process_login函数 1). 首先对客户端传来的版本号进行检查 2). 若不是DHCP模式则检查客户端传过来的内网IP是否在当前网段内 3). 检查客户端所传签名是否在当前签名文件中 4). 根据是否启用DHCP模式决定调用server_process_login_dhcp函数或server_process_login_no_dhcp函数,其中前者根据当前网段寻找一个合适的IP地址通知客户端,后者为原逻辑
- 修改connect_server函数 1). 检查服务器端传来的版本号与本地版本号是否相同 2). 检查服务器端传来的IP地址是否为0 3). 检查服务器端传来的signature是否与本地的相同 4). 检查服务器端传来的IP地址是否与本地的相同,若不同且服务器端为DHCP模式则使用服务器端给定的IP进行绑定 5). 进行其他初始化
三、更灵活的签名校验
为了兼容老版本,因此将sys_login_msg_t结构的头3字节用作版本号的定义。同时为了增强灵活性和安全性,在qtun程序中仅保存signature文件的路径,每次做校验时都动态的将其读出。
- 定义script_signature_verify和script_load_signature接口
int script_signature_verify(lua_State* lua, const unsigned char signature[31]) { lua_getglobal(lua, "signature_verify"); lua_pushlstring(lua, (char*)signature, 31); if (lua_pcall(lua, 1, 1, 0) != 0) { SYSLOG(LOG_ERR, "%s\n", lua_tostring(lua, -1)); return 0; } return lua_toboolean(lua, -1); } int script_load_signature(lua_State* lua, unsigned char signature[31]) { const char* str; lua_getglobal(lua, "signature_load"); if (lua_pcall(lua, 0, 1, 0) != 0) { SYSLOG(LOG_ERR, "%s\n", lua_tostring(lua, -1)); return 0; } str = lua_tostring(lua, -1); memcpy(signature, str, 31); return 1; }
- 以上两个函数最终会调用qtun.lua中的signature_verify和signature_load接口
function signature_load() local file = io.open(qtun.conf.signature_file) local ret = ‘‘ assert(file) if qtun.state.is_server then ret = file:read(‘*a‘) else ret = file:read(31) end file:close() return ret end function signature_verify(signature) if qtun.state.is_server then local file = io.open(qtun.conf.signature_file) assert(file) while true do local line = file:read() if line == nil then break end if line == signature then return true end end file:close() return false else return signature == signature_load() end end
- 在connect_server函数中,创建login消息前先将签名读出来
unsigned char signature[31] = {0}; script_load_signature(qtun->lua, signature); msg = new_login_msg(qtun->localip, 0, 0, 1, 0, signature);
- 在server_process_login函数中,调用script_signature_verify检查客户端所传来的签名是否存在于签名文件中
if (!script_signature_verify(qtun->lua, login->signature)) { // 签名检查失败 msg_t* new_msg; unsigned char signature[sizeof(login->signature)]; memset(signature, 0, sizeof(signature)); SYSLOG(LOG_ERR, "unknown signature"); pool_room_free(&qtun->pool, room_id); data = NULL; new_msg = new_login_msg(0, 0, 0, 0, 0, signature); if (new_msg) { write_c(client, new_msg, sizeof(msg_t) + msg_data_length(new_msg)); pool_room_free(&qtun->pool, MSG_ROOM_IDX); } close_client(for_del, idx); goto end; }
- 在connect_server函数中,服务器返回数据时调用script_signature_verify接口进行校验
if (!script_signature_verify(qtun->lua, login->signature)) { SYSLOG(LOG_ERR, "invalid signature"); pool_room_free(&qtun->pool, room_id); goto end; }
四、完整代码
完整代码可到step15中查看
版本号:1.0.0
日期:2015-05-20
时间: 2024-10-15 01:30:39