华为/CMCC的Portal协议研究纪要

华为/CMCC的Portal协议研究纪要

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

相关推荐

长虹与海信电视机深度对比:哪个更值得购买?
365服务平台

长虹与海信电视机深度对比:哪个更值得购买?

⌛ 2025-12-02 👁️ 2828
5d3拍摄视频如何跟焦
beta365体育

5d3拍摄视频如何跟焦

⌛ 2025-07-02 👁️ 768
北京机场随身WiFi租赁地点推荐及信号覆盖指南
365服务平台

北京机场随身WiFi租赁地点推荐及信号覆盖指南

⌛ 2025-10-26 👁️ 2733