Radius 协议(Remote Authentication Dial-In User Service Protocol)是一种由 IETF(Internet Engineering Task Force)定义的网络协议,最初设计用于拨号网络的认证、授权和计费。它的工作流程为,由客户端发起拨号请求,网络设备将认证请求转发至 Radius 服务器,服务器根据用户信息进行认证和授权,之后将认证结果返回给网络设备,网络设备接到响应后决定用户是否有权访问网络。
Radius 协议支持多种加密机制,可以为认证过程提供数据的安全保证,同时该协议还具备良好的扩展性,可以通过(Attribute-Value Pairs, AVPs)形式传递和夹带额外的信息,Radius 服务器是集中式的管理用户认证和授权信息,极大的简化了网络管理,总之在网络安全领域 Radius 服务扮演着机器重要的角色,确保只有授权用户才能访问其网络资源。
Radius 协议分为两种模式:认证模式和计费模式。认证(Authentication)模式用于用户身份认证,由客户端发送一个包含用户凭证(用户名和密码)的认证请求(Access-Request)到 Radius 服务器,服务器根据认证结果返回认证接受(Access-Accept)消息或认证拒绝(Access-Reject)消息给用户。计费(Accounting)模式则用于记录用户对网络资源的使用情况,用于生成账单或进行网络使用审计,它通常在网络认证完成之后,网络设备定期向 Radius 服务器发送计费记录(Accounting-Request),包含用户登录时间、使用的字节数、断开连接的时间等,服务器收到计费消息后,会将记录保存起来,用于后续的计费和审计工作。
下面是 Radius 协议数据格式摘要,Code 字段是一个八位字节,4 代表计费请求,5 代表计费应答;Identifier 字段是一个八位字节,用于匹配请求与回复;Length 字段占两个八位字节表示数据长度,取值介于 20 至 4096 之间;Authenticator 字段占十六个八位字节,根据请求或应答报文取值和意义各不相同;Attributes 为协议属性值,长度可变;
消息结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code | Identifier | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Authenticator |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attributes ...
+-+-+-+-+-+-+-+-+-+-+-+-+-
Code 取值
4 Accounting-Request
5 Accounting-Response
Attributes 结构
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Value ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Type 取值
1-39 (refer to RADIUS document [2])
40 Acct-Status-Type
41 Acct-Delay-Time
42 Acct-Input-Octets
43 Acct-Output-Octets
44 Acct-Session-Id
45 Acct-Authentic
46 Acct-Session-Time
47 Acct-Input-Packets
48 Acct-Output-Packets
49 Acct-Terminate-Cause
50 Acct-Multi-Session-Id
51 Acct-Link-Count
60+ (refer to RADIUS document [2])
Radius 协议数据格式大致了解了,接下来描述一下 Radius 计费协议的解析过程,编程语言为 C 语言。假设我们已经通过分光器或镜像方式采集到了用户 Radius 计费报文,准备从中提取业务需要的字段属性,下面是数据结构定义。
/* packet type define */
#define ACCESS_ACCEPT 2 /* accounting request code */
#define ACCT_REQUEST 4 /* accounting request code */
typedef struct tagRadiusPacket
{
UCHAR m_ucCode;
UCHAR m_ucIdentifier;
USHORT m_usLength;
char m_szAuthenticator[16];
}RADIUSPACKET_S;
/* packet attribute define */
#define USER_NAME 1 /* User-Name attribute */
#define NAS_IP_ADDR 4 /* NAS IP ADDRESS ATTRUBUTE */
#define ACCT_RESPONSE 5 /* accounting response code */
#define FRAMED_IP_ADDRESS 8 /* Framed-IP-Address attribute */
#define FILTER_ID 11 /* Filter-ID */
#define CALLED_PHONE 31 /* Calling-Station-Id attribute */
typedef struct tagRadiusAttribute
{
UCHAR m_ucType;
UCHAR m_ucLength;
}RADIUSATTRIBUTE_S;
/* accept attribute define */
#define ACCT_STATUS_TYPE 40 /* Account-Status-Type attribute */
#define ACCT_STATUS_START 1 /* Account-Status is start */
#define ACCT_STATUS_STOP 2 /* Account-Status is stop */
#define ACCT_STATUS_UPDATE 3 /* specially for cisco's update status */
#define RADACCT_DGRAM_MAX 1024 /* largerest dgram packet size */
下面是解析过程,解析操作只获取了用户名称、用户地址、电话号码和计费状态,其余的信息可以根据业务需要添加。
int parse(const u_char *pszPacket,int iPacketLength)
{
ULONG ulStatus = 0;
UINT uiPacketOffset = 0;
UINT uiPacketLength = 0;
UINT uiHeaderLength = 0;
UINT uiAttributeNumber = 0;
const struct ip *pstIP = NULL;
const struct udphdr *pstUdp = NULL;
const struct ether_header *pstEther = NULL;
RADIUSPACKET_S *pstRadiusPacket = NULL;
RADIUSATTRIBUTE_S *pstRadiusAttribute = NULL;
RADIUSUSERINFORMATION_S stRadiusUserInformation;
const int iRadiusAttributeSize = sizeof(RADIUSATTRIBUTE_S);
memmove(szPacket,pszPacket,iPacketLength);
memset(&stRadiusUserInformation,'\0',sizeof(RADIUSUSERINFORMATION_S));
/* ether header */
pstEther = (const struct ether_header *)szPacket;
memcpy(stRadiusUserInformation.m_ucMac,pstEther->ether_shost,ETH_ALEN);
/* ip header */
pstIP = (struct ip *)(szPacket + sizeof(struct ether_header));
if( pstIP->ip_p != IPPROTO_UDP ) return FAILURE;
/* udp header */
pstUdp = (const struct udphdr *)(szPacket + sizeof(struct ether_header) + pstIP->ip_hl * 4);
if( ntohs(pstUdp->uh_dport) != g_pstIzpNdmsConfig->m_uiRadiusPort ) return FAILURE;
/* radius packet */
uiHeaderLength = (UINT)(sizeof(struct ether_header) + pstIP->ip_hl * 4 + sizeof(struct udphdr));
pstRadiusPacket = (RADIUSPACKET_S *)(szPacket + uiHeaderLength);
if( pstRadiusPacket->m_ucCode != ACCT_REQUEST ) return FAILURE;
uiPacketLength = ntohs(pstRadiusPacket->m_usLength);
if( uiPacketLength < sizeof(RADIUSPACKET_S) ) return FAILURE;
/* RADIUS报文解析 */
uiPacketOffset = sizeof(RADIUSATTRIBUTE_S);
while( uiPacketLength - uiPacketOffset > sizeof(RADIUSATTRIBUTE_S) ) {
if( ++ uiAttributeNumber > 70 ) break;
pstRadiusAttribute = (RADIUSATTRIBUTE_S *)(szPacket + uiHeaderLength + uiPacketOffset);
if( pstRadiusAttribute->m_ucLength < iRadiusAttributeSize ) continue;
switch( pstRadiusAttribute->m_ucType ) {
case USER_NAME: memcpy(stRadiusUserInformation.m_szUserName,pstRadiusAttribute + iRadiusAttributeSize,pstRadiusAttribute->m_ucLength - iRadiusAttributeSize);
break;
case NAS_IP_ADDR: memcpy((struct in_addr *)&stRadiusUserInformation.m_stNasIP,pstRadiusAttribute + iRadiusAttributeSize,pstRadiusAttribute->m_ucLength - iRadiusAttributeSize);
break;
case CALLED_PHONE: memcpy(stRadiusUserInformation.m_szPhone,pstRadiusAttribute + iRadiusAttributeSize,pstRadiusAttribute->m_ucLength - iRadiusAttributeSize);
break;
case ACCT_STATUS_TYPE: memcpy((ULONG *)&ulStatus,pstRadiusAttribute + iRadiusAttributeSize,sizeof(ULONG));
stRadiusUserInformation.m_ulVerb = ntohl(ulStatus);
break;
case FRAMED_IP_ADDRESS: memcpy((struct in_addr *)&stRadiusUserInformation.m_stUserIP,pstRadiusAttribute + iRadiusAttributeSize,pstRadiusAttribute->m_ucLength - iRadiusAttributeSize);
break;
default: break;
}
uiPacketOffset += pstRadiusAttribute->m_ucLength;
}
return SUCCESS;
}
以上就是 Radius 计费请求协议的解析实现,仅供参考。