usrsctp之cookie
cookie是sctp协议四次握手过程的重要组成部分,其主要是为了防止出现TCP协议中的SYN-flood攻击而产生的。先来回忆下一sctp的四次握手过程。
1 SCTP四次握手过程概述
SCTP连接建立过程包含以下四个步骤:
1. 发起方发送INIT消息
2. 接收方回复INIT-ACK消息,其中包含Cookie数据
3. 发起方发送COOKIE-ECHO消息,其中包含从步骤2中收到的Cookie数据
4. 接收方验证Cookie并回复COOKIE-ACK消息
这个过程的关键在于Cookie数据的使用,它使得接收方无需在连接完全建立之前为每个连接分配资源。
2 Cookie的作用
2.1 防止资源耗尽攻击
在传统的TCP三次握手过程中,服务器在收到客户端的SYN消息后就需要分配TCB(Transmission Control Block)资源。攻击者可以利用这一点发送大量伪造的SYN消息,导致服务器资源耗尽,这就是著名的SYN洪泛攻击。
SCTP通过使用Cookie机制解决了这个问题。在四次握手过程中,服务器在发送INIT-ACK消息时并不分配TCB资源,而是将连接状态信息编码到Cookie中发送给客户端。只有当服务器收到并验证了COOKIE-ECHO消息后,才会分配TCB资源并建立连接。
2.2 状态信息的传递
Cookie不仅用于防止资源耗尽攻击,还承载了连接建立所需的所有状态信息。这些信息包括但不限于:
- 发起方和接收方的初始TSN(Transmission Sequence Number)
- 发起方和接收方支持的地址列表
- 发起方和接收方支持的流数量
- 其他连接参数
通过将这些状态信息编码到Cookie中,接收方可以在验证Cookie时恢复这些信息,而无需在内存中保存它们。
3 Cookie数据的构成
usrsctp的cookie数据和dcsctp的cookie数据的组成结构是完全不一样的。这就说明了cookie数据仅仅是服务端用来校验客户端的有效性的,防止任意的客户端过来连接,仅仅是协议层的保护,并没有做到tls证书那般严格。说实在的,我感觉这个cookie并没有太大的作用。
服务端按照自己的私有协议设计一套cookie数据的生成方法即可,完全不必考虑对端的cookie生成机制,因为对端也是原封不动地将cookie数据回传给服务器的。服务器根据这个cookie数据来校验一下内容:
1. cookie数据是不是自己生成的,如果不是那就握手失败;
2. cookie数据包含了本次握手的四元组:源ip和目标ip以及源端口和目标端口 ;
3. cookie数据包含了本次握手的时间戳,避免服务器长时间运行导致的旧数握手问题;
4. cookie数据包含了服务端的签名信息,防止他人伪造cookie数据。
但这些都是标准建议的,并没有一套严格的机制,比如usrsctp就是这样做的,但是dcsctp也是这样做的。但是二者的实现完全不同。
| 实现 | usrsctp | dcsctp | newsctp |
| 签名算法 | hmac | sha1 | none |
| 构成 | initChk+initAckChk+stateCookie+signature | stateCookie+signature | stateCookie |
在usrsctp实现中,Cookie的主要数据结构是`sctp_state_cookie`,定义如下:
struct sctp_state_cookie {uint32_t time_entered; /* Time when cookie was created */uint32_t cookie_life; /* Cookie lifetime */uint32_t tie_tag_my_vtag; /* Tie tag for my verification tag */uint32_t tie_tag_peer_vtag; /* Tie tag for peer's verification tag */ uint32_t peers_vtag; /* Peer's verification tag */ uint32_t my_vtag; /* My verification tag */ uint32_t assoc_id; /* Association ID */ uint32_t secret_key[SCTP_NUMBER_OF_SECRETS]; /* Secret key for HMAC */ uint32_t key_id; /* Key ID */ uint32_t key_time; /* Key generation time */ uint16_t addr_type; /* Address type */ uint16_t laddr_type; /* Local address type */ uint16_t port; /* Port number */ uint16_t net_size; /* Network address size */ /* 本地地址和对端地址存储在结构体后面 */ /* struct sockaddr_conn address; */ /* struct sockaddr_conn laddress; */
};
4 Cookie的生成过程
当服务器收到INIT消息并准备发送INIT-ACK时,会执行以下步骤生成Cookie:
1. 收集连接建立所需的所有状态信息
2. 将这些信息编码到`sctp_state_cookie`结构体中
3. 使用密钥对Cookie内容进行HMAC计算,确保其完整性和真实性
4. 将Cookie附加到INIT-ACK消息中发送给客户端
具体的生成位置在`void sctp_send_initiate_ack()`函数中:
void sctp_send_initiate_ack(struct sctp_inpcb* inp,struct sctp_tcb* stcb,struct sctp_nets* src_net,struct mbuf* init_pkt,int iphlen,int offset,struct sockaddr* src,struct sockaddr* dst,struct sctphdr* sh,struct sctp_init_chunk* init_chk,uint16_t port) {struct sctp_state_cookie stc;// step 1:构造state cookiememset(&stc, 0, sizeof(struct sctp_state_cookie));/* the time I built cookie */(void)SCTP_GETTIME_TIMEVAL(&now);stc.time_entered.tv_sec = now.tv_sec;stc.time_entered.tv_usec = now.tv_usec;stc.tie_tag_my_vtag = 0;stc.tie_tag_peer_vtag = 0;/* life I will award this cookie */stc.cookie_life = inp->sctp_ep.def_cookie_life;/* copy in the ports for later check */stc.myport = sh->dest_port;stc.peerport = sh->src_port;switch (dst->sa_family) {case AF_CONN: memcpy(&stc.address, &srcconn->sconn_addr, sizeof(void*));stc.addr_type = SCTP_CONN_ADDRESS;memcpy(&stc.laddress, &dstconn->sconn_addr, sizeof(void*));stc.laddr_type = SCTP_CONN_ADDRESS;break;default:break;}strncpy(stc.identification,SCTP_VERSION_STRING, sizeof(stc.identification)));// step 2:构造cookie数据m_cookie=initChk+initAckChk+state cookie+signatureinitack = mtod(m, struct sctp_init_ack_chunk*);/* Now we must build a cookie */m_cookie = sctp_add_cookie(init_pkt, offset, m, 0, &stc, &signature);// step 3:签名cookie数据(void)sctp_hmac_m(SCTP_HMAC,(uint8_t*)inp->sctp_ep.secret_key[(int)(inp->sctp_ep.current_secret_number)],SCTP_SECRET_SIZE,m_cookie,sizeof(struct sctp_paramhdr),(uint8_t*)signature,SCTP_SIGNATURE_SIZE);// step 4:cookie追加到initAckChkSCTP_BUF_NEXT(m_last) = m_cookie;// step 5:发送init ack chkif ((error = sctp_lowlevel_chunk_output(inp,NULL,NULL,to,m,0,NULL,0,0,0,inp->sctp_lport,sh->src_port,init_chk->init.initiate_tag))) {}}
5 Cookie的验证过程
当服务器收到COOKIE-ECHO消息时,会执行以下步骤验证Cookie:
1. 使用相应的密钥验证Cookie的HMAC,确保其未被篡改
2. 检查Cookie的时间戳和生命周期,确保其未过期
3. 从Cookie中恢复连接状态信息
4. 使用恢复的状态信息建立连接
具体的验证位置在`struct mbuf* sctp_handle_cookie_echo()`函数中:
static struct mbuf* sctp_handle_cookie_echo(struct mbuf* m,int iphlen,int offset,struct sockaddr* src,struct sockaddr* dst,struct sctphdr* sh,struct sctp_cookie_echo_chunk* cp,struct sctp_inpcb** inp_p,struct sctp_tcb** stcb,struct sctp_nets** netp,int auth_skipped,uint32_t auth_offset,uint32_t auth_len,struct sctp_tcb** locked_tcb,uint32_t vrf_id,uint16_t port) {struct sctp_state_cookie* cookie; unsigned int cookie_len;cookie = &cp->cookie;cookie_len = ntohs(cp->ch.chunk_length);//!< 校验长度if (cookie_len < sizeof(struct sctp_cookie_echo_chunk) + sizeof(struct sctp_init_chunk) +sizeof(struct sctp_init_ack_chunk) + SCTP_SIGNATURE_SIZE) {/* cookie too small */return (NULL);}//!< 校验端口和tagif ((cookie->peerport != sh->src_port) || (cookie->myport != sh->dest_port) ||(cookie->my_vtag != sh->v_tag)) {/** invalid ports or bad tag. Note that we always leave the* v_tag in the header in network order and when we stored* it in the my_vtag slot we also left it in network order.* This maintains the match even though it may be in the* opposite byte order of the machine :->*/return (NULL);}//!< 校验签名sig_offset = offset + cookie_len - SCTP_SIGNATURE_SIZE;m_sig = m_split(m, sig_offset);(void)sctp_hmac_m(SCTP_HMAC,(uint8_t*)ep->secret_key[(int)ep->last_secret_number],SCTP_SECRET_SIZE,m,cookie_offset,calc_sig,0);/* compare the received digest with the computed digest */if (timingsafe_bcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) != 0) {...cookie_ok = 0;... } else {cookie_ok = 1;}