华为/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")

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值