最近学习了华为/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")