最近学习了华为/CMCC的portal协议。 下面做些学习纪要:
1、概述
Portal协议提供了这样一种方式。当用户未认证时,控制用户只能访问某些特定的网络资源。当用户需要访问互联网更多资源的时候,必须进行认证。
它不需要用户安装特定的客户端,只需要通过浏览器,当用户没有认证时,通过HTTP重定向到特定的认证页面,引导用户完成认证的过程。并在此过程中开展广告、社区服务等个性化业务。
2、协议标准
《华为公司宽带产品Portal协议标准(V2.0)》和《中移动PORTAL协议规范》。这两种标准,在协议上是一脉相承的。
华为的V1.0标准和CMCC的标准基本一样。华为的V2.0标准是在V1.0的标准上稍微有一部分改动。但是引入了报文验证字之后,V2.0标准与V1.0标准完全不兼容。
CMCC的标准还是保持跟华为V1.0标准一致。
3、协议实体
Portal用户、BAS设备、Portal Server。
Portal Server负责认证页面的推送、Portal协议认证请求的发起、广告内容的维护分发管理等。
而BAS设备反而作为Portal协议认证的响应一方,扮演Portal协议认证服务器的角色。
4、承载协议
UDP协议。其中BAS设备作为Portal认证的响应方,监听UDP的2000端口。
5、认证类型
PAP、CHAP两种。
PAP方式,Portal Server直接将用户名/密码以明文的方式发给BAS设备进行认证。
CHAP方式,Portal Server通过CHAP请求挑战字符串,并据此对密码进行加密后,发给BAS设备进行认证。
6、OpenPortal
OpenPortal是一个开放的Portal Server,并不是开源的产品。它支持portal V1.0、V2.0等认证。并支持完整的广告营销、计费、运营。
它基于JAVA开发,运行于TOMCAT服务器,数据库使用MYSQL。整套JAVA代码的源码授权费用20000元。
7、最后附上wireshark的portal解析脚本
--[[ struct web_packet_header { unsigned char version; unsigned char type; unsigned char mode; unsigned char reserved; unsigned short serialno; unsigned short reqid; unsigned int userip; unsigned short userport; unsigned char errorcode; unsigned char attrnum; } -------------------------------------------------------- ]]-- do --[[ 创建一个新的协议结构 cmccportal_proto 第一个参数是协议名称会体现在过滤器中 第二个参数是协议的描述信息,无关紧要 --]] local cmccportal_proto = Proto("cmccportal", "CMCC Portal Protolcol") --[[ 下面定义字段 --]] local cmccportal_version = ProtoField.uint8("cmccportal.version", "Version", base.DEC) local cmccportal_type = ProtoField.string("cmccportal.type", "Type", base.NONE) local cmccportal_mode = ProtoField.string("cmccportal.mode", "CHAP/PAP", base.NONE) local cmccportal_reserved = ProtoField.uint8("cmccportal.reserved", "Reserved", base.HEX) local cmccportal_serialno = ProtoField.uint16("cmccportal.serialno", "SerialNo", base.DEC) local cmccportal_reqid = ProtoField.uint16("cmccportal.reqid", "ReqID", base.HEX) local cmccportal_userip = ProtoField.ipv4("cmccportal.userip", "UserIP") local cmccportal_userport = ProtoField.uint16("cmccportal.userport", "UserPort", base.DEC) local cmccportal_errorcode = ProtoField.string("cmccportal.errorcode", "ErrorCode", base.NONE) local cmccportal_attrnum = ProtoField.uint8("cmccportal.attrnum", "AttrNum", base.DEC) local cmccportal_attributes = ProtoField.string("cmccportal.attributes", "Attributes", base.NONE) local cmccportal_attribute_type = ProtoField.uint8("cmccportal.attribute._type", "Type", base.DEC) local cmccportal_attribute_length = ProtoField.uint8("cmccportal.attribute._length", "Length", base.DEC) local cmccportal_attribute_value = ProtoField.string("cmccportal.attribute._value", "Value", base.NONE) -- 将字段添加都协议中 cmccportal_proto.fields = { cmccportal_version, cmccportal_type, cmccportal_mode, cmccportal_reserved, cmccportal_serialno, cmccportal_reqid, cmccportal_userip, cmccportal_userport, cmccportal_errorcode, cmccportal_attrnum, cmccportal_attributes, cmccportal_attribute_type, cmccportal_attribute_length, cmccportal_attribute_value } --[[ 下面定义 foo 解析器的主函数,这个函数由 wireshark调用 第一个参数是 Tvb 类型,表示的是需要此解析器解析的数据 第二个参数是 Pinfo 类型,是协议解析树上的信息,包括 UI 上的显示 第三个参数是 TreeItem 类型,表示上一级解析树 --]] function cmccportal_proto.dissector(tvb, pinfo, treeitem) local offset = 0 local tvb_len = tvb:len() -- 在上一级解析树上创建 foo 的根节点 local cmccportal_tree = treeitem:add(cmccportal_proto, tvb:range(offset)) -- 下面是想该根节点上添加子节点,也就是自定义协议的各个字段 -- 注意 range 这个方法的两个参数的意义,第一个表示此时的偏移量 -- 第二个参数代表的是字段占用数据的长度 local _version = tvb:range(offset, 1) offset = offset + 1 local _type = tvb:range(offset, 1) offset = offset + 1 local _mode = tvb:range(offset, 1) offset = offset + 1 local _reserved = tvb:range(offset, 1) offset = offset + 1 local _serialno = tvb:range(offset, 2) offset = offset + 2 local _reqid = tvb:range(offset, 2) offset = offset + 2 local _userip = tvb:range(offset, 4) offset = offset + 4 local _userport = tvb:range(offset, 2) offset = offset + 2 local _errorcode = tvb:range(offset, 1) offset = offset + 1 local _attrnum = tvb:range(offset, 1) offset = offset + 1 cmccportal_tree:add(cmccportal_version, _version) cmccportal_tree:add(cmccportal_type, cmccportal_type_to_text(_type:uint())) cmccportal_tree:add(cmccportal_mode, cmccportal_mode_to_text(_mode:uint())) cmccportal_tree:add(cmccportal_reserved, _reserved) cmccportal_tree:add(cmccportal_serialno, _serialno) cmccportal_tree:add(cmccportal_reqid, _reqid) cmccportal_tree:add(cmccportal_userip, _userip) cmccportal_tree:add(cmccportal_userport, _userport) cmccportal_tree:add(cmccportal_errorcode, cmccportal_errorcode_to_text(_type:uint(), _errorcode:uint())) cmccportal_tree:add(cmccportal_attrnum, _attrnum) -- 解析attributes while (offset < tvb_len) do local _tlv_t = tvb:range(offset, 1) offset = offset + 1 local _tlv_l = tvb:range(offset, 1) offset = offset + 1 local _tlv_v_len = _tlv_l:uint() - 2 local _tlv_v = tvb:range(offset, _tlv_v_len) offset = offset + _tlv_v_len local attribute_tree = cmccportal_tree:add(cmccportal_attributes, cmccportal_attribute_to_text(_tlv_t:uint())) -- attribute_tree:add(cmccportal_attribute_type, _tlv_t) attribute_tree:add(cmccportal_attribute_length, _tlv_l) attribute_tree:add(cmccportal_attribute_value, _tlv_v) end -- 设置一些 UI 上面的信息 pinfo.cols.protocol:set("CMCC PORTAL - V" .. _version:uint()) pinfo.cols.info:set(_serialno:uint() .. " - " .. cmccportal_type_to_text(_type:uint())) -- 遍历属性内容,打印属性 end --[[ cmccportal_type_to_text --]] function cmccportal_type_to_text(_type) local switch_foo = { [1] = function() return "REQ_CHALLENGE" end, [2] = function() return "ACK_CHALLENGE" end, [3] = function() return "REQ_AUTH" end, [4] = function() return "ACK_AUTH" end, [5] = function() return "REQ_LOGOUT" end, [6] = function() return "ACK_LOGOUT" end, [7] = function() return "AFF_ACK_AUTH" end, [8] = function() return "NTF_LOGOUT" end, [9] = function() return "REQ_INFO" end, [10] = function() return "ACK_INFO" end } local _text = switch_exec( switch_foo, _type, function() return "unknown type" end ); return string.format("(%d) %s", _type, _text); end --[[ cmccportal_mode_to_text --]] function cmccportal_mode_to_text(_mode) local switch_foo = { [0] = function() return "CHAP" end, [1] = function() return "PAP" end } local _text = switch_exec( switch_foo, _mode, function() return "unknown type" end ); return string.format("(%d) %s", _mode, _text); end --[[ ack_challenge_errorcode_to_text --]] function ack_challenge_errorcode_to_text(_errorcode) local switch_foo = { [0] = function() return "REQ_CHALLENGE successful" end, [1] = function() return "REQ_CHALLENGE rejected" end, [2] = function() return "REQ_CHALLENGE link already established" end, [3] = function() return "REQ_CHALLENGE another authen in progress" end, [4] = function() return "REQ_CHALLENGE failure" end } return switch_exec( switch_foo, _errorcode, function() return "n/a" end ); end --[[ ack_authen_errorcode_to_text --]] function ack_authen_errorcode_to_text(_errorcode) local switch_foo = { [0] = function() return "REQ_AUTH successful" end, [1] = function() return "REQ_AUTH rejected" end, [2] = function() return "REQ_AUTH link already established" end, [3] = function() return "REQ_AUTH another authen in progress" end, [4] = function() return "REQ_AUTH failure" end } return switch_exec( switch_foo, _errorcode, function() return "n/a" end ); end --[[ req_logout_errorcode_to_text --]] function req_logout_errorcode_to_text(_errorcode) local switch_foo = { [0] = function() return "A REQ_LOGOUT packet" end, [1] = function() return "A retry packet after request time out" end } return switch_exec( switch_foo, _errorcode, function() return "n/a" end ); end --[[ ack_logout_errorcode_to_text --]] function ack_logout_errorcode_to_text(_errorcode) local switch_foo = { [0] = function() return "REQ_LOGOUT successful" end, [1] = function() return "REQ_LOGOUT rejected" end, [2] = function() return "REQ_LOGOUT failure" end } return switch_exec( switch_foo, _errorcode, function() return "n/a" end ); end --[[ cmccportal_errorcode_to_text --]] function cmccportal_errorcode_to_text(_type, _errorcode) local switch_foo = { [2] = function() return ack_challenge_errorcode_to_text(_errorcode) end, [4] = function() return ack_authen_errorcode_to_text(_errorcode) end, [5] = function() return req_logout_errorcode_to_text(_errorcode) end, [6] = function() return ack_logout_errorcode_to_text(_errorcode) end } local _text = switch_exec( switch_foo, _type, function() return "n/a" end ); return string.format("(%d) %s", _errorcode, _text); end --[[ cmccportal_attribute_to_text --]] function cmccportal_attribute_to_text(_attribute) local switch_foo = { [1] = function() return "UserName" end, [2] = function() return "PassWord" end, [3] = function() return "Challenge" end, [4] = function() return "ChapPassWord" end } local _text = switch_exec( switch_foo, _attribute, function() return "unknown attribute" end ); return string.format("(%d) %s", _attribute, _text); end --[[ switch_exec --]] function switch_exec(switch_foo, _case, default_foo) local case_foo = switch_foo[_case] if (case_foo) then -- for switch case return case_foo() else -- for default case return default_foo() end end -- 向 wireshark 注册协议插件被调用的条件 local tcp_port_table = DissectorTable.get("udp.port") tcp_port_table:add(2000, cmccportal_proto) end使用方法:
1)将以上脚本保存为cmccportal.lua,放到wireshark的安装根目录 2)修改wireshark根目录下的init.lua,在文件最后增加 dofile(DATA_DIR.."cmccportal.lua")
