导读:本期聚焦于闲进程创作的《如何用Ruby开发RADIUS计费开始和停止功能实现会话时长与流量统计》,敬请观看详情。RADIUS协议的计费功能是企业网络接入管控的核心模块,其中计费开始和停止报文的规范处理直接决定会话时长与流量统计的准确性。如果报文构造不符合RFC 2866标准,会导致NAS设备上报的流量数据无法被计费服务器正确解析,最终出现计费偏差。本文将从RADIUS计费报文的结构规范入手,讲解用Ruby实现计费开始与停止报文的构造逻辑,详细说明会话时长计算的时间戳处理方法,以及上下行流量统计的字段映射规则,同时给出完整的可运行代码示例,帮助开发者快速完成符合生产要求的计费功能开发。

RADIUS计费协议基础与报文结构

RADIUS计费功能由RFC 2866规范定义,核心作用是让网络接入服务器(NAS)向计费服务器上报用户会话的相关信息,包括会话开始、会话进行中、会话结束三个阶段的事件。其中计费开始(Accounting-Start)报文用于告知服务器用户会话已建立,计费停止(Accounting-Stop)报文用于告知服务器用户会话已终止,这两个报文是完成会话时长与流量统计的基础。

每个RADIUS计费报文都包含固定的头部和若干属性字段。头部由编码(Code)、标识符(Identifier)、长度(Length)、认证字(Authenticator)、属性(Attributes)五部分组成,其中Code字段用于区分报文类型:计费开始的Code值为4,计费停止的Code值为5。属性字段采用TLV(Type-Length-Value)结构,每个属性包含类型、长度、值三个部分,常用的计费相关属性包括用户名称(User-Name,类型1)、纳斯IP地址(NAS-IP-Address,类型4)、会话ID(Acct-Session-Id,类型44)、会话开始时间(Acct-Start-Time,类型40)、会话停止时间(Acct-Stop-Time,类型41)、输入流量(Acct-Input-Octets,类型42)、输出流量(Acct-Output-Octets,类型43)等。

在构造报文时需要注意认证字的计算规则,计费报文的Authenticator字段是16字节的随机值,用于后续报文校验时的哈希计算。如果是计费请求报文,Authenticator需要由请求端随机生成;如果是计费响应报文,Authenticator是请求报文的Authenticator和响应报文内容拼接后,用共享密钥计算MD5哈希得到的结果。这个规则在Ruby实现时需要严格遵循,否则计费服务器会认为报文不合法直接丢弃。

Ruby实现RADIUS计费开始报文构造

要构造符合规范的计费开始报文,首先需要封装基础的工具方法,处理TLV属性的编码和报文头部的组装。Ruby中没有原生的RADIUS协议支持库,我们可以手动实现属性的编码逻辑,核心是将属性的类型、长度、值按照规范拼接成二进制数据。对于字符串类型的属性值,直接转换为二进制即可;对于IP地址类型的属性值,需要先将点分十进制的IP转换为4字节的二进制;对于整数类型的属性值,需要按照网络字节序转换为4字节二进制。

以下是一个基础的工具模块实现,包含属性编码和报文头部构造的方法:

require 'socket'
require 'openssl'
require 'time'

module RadiusAccounting
  # RADIUS属性类型常量
  ATTR_USER_NAME = 1
  ATTR_NAS_IP_ADDRESS = 4
  ATTR_ACCT_STATUS_TYPE = 40
  ATTR_ACCT_DELAY_TIME = 41
  ATTR_ACCT_INPUT_OCTETS = 42
  ATTR_ACCT_OUTPUT_OCTETS = 43
  ATTR_ACCT_SESSION_ID = 44
  ATTR_ACCT_SESSION_TIME = 46
  ATTR_ACCT_START_TIME = 47
  ATTR_ACCT_STOP_TIME = 48

  # 计费状态类型常量
  ACCT_STATUS_START = 1
  ACCT_STATUS_STOP = 2

  # 编码单个RADIUS属性
  def self.encode_attribute(type, value)
    # 根据值类型处理编码逻辑
    case type
    when ATTR_NAS_IP_ADDRESS
      # IP地址转换为4字节二进制
      ip_parts = value.split('.').map(&:to_i)
      value_bin = ip_parts.pack('C4')
    when ATTR_ACCT_INPUT_OCTETS, ATTR_ACCT_OUTPUT_OCTETS, ATTR_ACCT_SESSION_TIME, ATTR_ACCT_DELAY_TIME
      # 整数按网络字节序转为4字节
      value_bin = [value].pack('N')
    else
      # 字符串类型直接转二进制
      value_bin = value.to_s
    end
    # 拼接类型、长度、值
    type_bin = [type].pack('C')
    length_bin = [value_bin.bytesize + 2].pack('C')
    type_bin + length_bin + value_bin
  end

  # 构造RADIUS计费报文
  def self.build_accounting_packet(code, secret, attributes)
    identifier = rand(0..255) # 随机生成标识符
    authenticator = OpenSSL::Random.random_bytes(16) # 随机生成16字节认证字
    # 拼接属性部分
    attrs_bin = attributes.map { |k, v| encode_attribute(k, v) }.join
    # 计算总长度:头部8字节 + 属性长度
    length = 8 + attrs_bin.bytesize
    # 拼接报文头部
    header = [code, identifier, length].pack('C*')
    # 拼接认证字和属性
    packet = header + authenticator + attrs_bin
    # 计算响应认证字(如果是请求报文不需要,这里先预留)
    packet
  end
end

有了基础工具方法后,构造计费开始报文只需要传入对应的属性即可。计费开始报文需要包含用户名称、NAS IP地址、会话ID、计费状态类型(值为1表示开始)、会话开始时间等必要属性。会话开始时间通常使用Unix时间戳,格式为字符串类型的RFC 3164时间格式,或者直接用整数时间戳表示,具体需要和计费服务器的要求匹配。

以下是构造计费开始报文的示例代码:

# 构造计费开始报文
secret = 'radius_secret' # 与计费服务器约定的共享密钥
attrs = {
  RadiusAccounting::ATTR_USER_NAME => 'test_user_001',
  RadiusAccounting::ATTR_NAS_IP_ADDRESS => '192.168.1.1',
  RadiusAccounting::ATTR_ACCT_SESSION_ID => 'session_20240501_001',
  RadiusAccounting::ATTR_ACCT_STATUS_TYPE => RadiusAccounting::ACCT_STATUS_START,
  RadiusAccounting::ATTR_ACCT_START_TIME => Time.now.to_i.to_s,
  RadiusAccounting::ATTR_ACCT_DELAY_TIME => 0
}
start_packet = RadiusAccounting.build_accounting_packet(4, secret, attrs)
puts "计费开始报文长度:#{start_packet.bytesize} 字节"
puts "报文十六进制表示:#{start_packet.unpack('H*').first}"

Ruby实现RADIUS计费停止报文与会话统计

计费停止报文的构造逻辑和开始报文类似,只是Code值改为5,计费状态类型改为2,同时需要新增会话时长、上下行流量、会话停止时间等属性。会话时长的计算逻辑是会话停止时间减去会话开始时间,单位为秒,存储在Acct-Session-Time(类型46)属性中。上下行流量分别用Acct-Input-Octets(类型42)和Acct-Output-Octets(类型43)表示,单位是字节,统计的是用户会话期间NAS接收到的上行流量和发送给用户设备的下行流量。

为了保证统计的准确性,会话ID需要和计费开始报文中的会话ID完全一致,这样计费服务器才能将两个报文关联到同一个用户会话。以下是一个构造计费停止报文的示例,假设我们已经记录了会话开始时间、上下行流量等信息:

# 假设会话相关信息
session_start_time = Time.now - 3600 # 会话开始于1小时前
session_end_time = Time.now
session_duration = session_end_time.to_i - session_start_time.to_i # 会话时长3600秒
input_octets = 1024 * 1024 * 50 # 上行流量50MB
output_octets = 1024 * 1024 * 200 # 下行流量200MB

# 构造计费停止报文属性
stop_attrs = {
  RadiusAccounting::ATTR_USER_NAME => 'test_user_001',
  RadiusAccounting::ATTR_NAS_IP_ADDRESS => '192.168.1.1',
  RadiusAccounting::ATTR_ACCT_SESSION_ID => 'session_20240501_001',
  RadiusAccounting::ATTR_ACCT_STATUS_TYPE => RadiusAccounting::ACCT_STATUS_STOP,
  RadiusAccounting::ATTR_ACCT_SESSION_TIME => session_duration,
  RadiusAccounting::ATTR_ACCT_START_TIME => session_start_time.to_i.to_s,
  RadiusAccounting::ATTR_ACCT_STOP_TIME => session_end_time.to_i.to_s,
  RadiusAccounting::ATTR_ACCT_INPUT_OCTETS => input_octets,
  RadiusAccounting::ATTR_ACCT_OUTPUT_OCTETS => output_octets,
  RadiusAccounting::ATTR_ACCT_DELAY_TIME => 0
}
stop_packet = RadiusAccounting.build_accounting_packet(5, secret, stop_attrs)
puts "计费停止报文长度:#{stop_packet.bytesize} 字节"

报文构造完成后,需要将其发送到计费服务器。RADIUS协议使用UDP传输,默认端口是1813,所以我们需要用Ruby的UDPSocket发送报文,并等待服务器的响应。发送逻辑需要注意设置超时时间,避免无响应时一直阻塞。以下是发送报文并处理响应的完整代码示例:

def send_radius_packet(packet, secret, server_ip='127.0.0.1', server_port=1813)
  socket = UDPSocket.new
  socket.send(packet, 0, server_ip, server_port)
  # 等待响应,超时时间3秒
  begin
    socket.settimeout(3)
    response, addr = socket.recvfrom(1024)
    puts "收到计费服务器响应,长度:#{response.bytesize} 字节"
    # 简单校验响应:响应的认证字需要符合规范
    resp_code = response.unpack('C').first
    if resp_code == 5 # 计费响应Code为5
      puts "计费报文处理成功"
    else
      puts "计费报文处理失败,响应Code:#{resp_code}"
    end
  rescue Timeout::Error
    puts "等待计费服务器响应超时"
  ensure
    socket.close
  end
end

# 发送计费开始报文
send_radius_packet(start_packet, secret)
# 发送计费停止报文
send_radius_packet(stop_packet, secret)

在实际生产环境中,还需要处理更多的边界情况,比如报文重传机制、大流量场景下的流量计数溢出问题(RADIUS的Acct-Input-Octets等属性是32位整数,最大表示4GB流量,超过后需要配合Acct-Input-Gigawords等属性扩展计数)、不同时区的时间处理问题等。此外,如果需要支持计费更新报文(Code值为5,计费状态类型为3),只需要在会话过程中定期发送包含最新流量和时长的报文即可,构造逻辑和停止报文类似,只是不需要包含会话停止时间属性。

常见问题与调试方法

开发过程中最常见的问题是计费服务器无法识别报文,通常原因是属性编码不符合规范。比如IP地址属性没有转换为4字节二进制,而是直接传入字符串,或者整数属性的长度计算错误,导致属性TLV结构不符合要求。调试时可以将构造的报文转换为十六进制字符串,对照RFC 2866的报文结构逐字节校验,也可以用Wireshark抓包查看发送的报文内容,确认每个字段的数值是否正确。

另一个常见问题是会话时长统计不准确,通常是因为会话开始时间和停止时间的时间戳来源不一致,比如开始时间用的是NAS本地时间,停止时间用的是计费服务器本地时间,两者存在时区差或者时钟偏差。解决方法是在所有涉及时间的地方统一使用Unix时间戳(UTC时间),避免时区转换带来的误差,同时可以在NAS和计费服务器之间部署NTP服务,保证两者的时钟同步。

流量统计偏差的问题通常是因为上下行流量的统计口径和计费服务器不一致,比如有些设备将输入流量统计为NAS接收的流量,有些则统计为用户发送的流量,需要在开发前和计费服务器的运维人员确认属性的定义。另外,需要注意流量统计的单位是字节,不要误写成比特,否则统计结果会相差8倍。如果计费服务器要求流量以千字节为单位,需要在构造报文前将字节数除以1024后再传入属性值。

如何用Ruby开发RADIUS计费开始和停止功能实现会话时长与流量统计

RubyRADIUS计费会话统计修改时间:2026-08-28 18:50:15

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。