由于某些奇怪的原因,我无法正确验证TCP校验和。我有代码检查IP和UDP校验和,它工作得很好,但对于TCP,我的逻辑中的东西是不对的。
我对这些标题的结构定义很好,因为我可以完全正确地读取数据(从wireshark验证)。我遇到的唯一问题是,对于TCP校验和,我无法验证校验和是否实际正确。有什么想法,我在做错了吗?
非常感谢。
校验和功能
unsigned short in_cksum(unsigned short *addr,int len)
{
register int sum = 0;
u_short answer = 0;
register u_short *w = addr;
register int nleft = len;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
读取TCP功能(旧版,检查编辑版)
/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
struct TCP_Pseudo tcpPseudo;
char tcpcsumblock[sizeof(struct TCP_Pseudo) + sizeof(struct TCP_Header)];
/* tcp pseudo header */
memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
tcpPseudo.source_ip = ip->source_ip.s_addr;
tcpPseudo.destination_ip = ip->destination_ip.s_addr;
tcpPseudo.zero = 0;
tcpPseudo.protocol = 6;
tcpPseudo.length = htons(sizeof(struct TCP_Header));
/* grab tcp checksum and reset it */
int tcpCheckSum = htons(tcp->tcp_checksum);
tcp->tcp_checksum = 0;
/* place the data from the tcp pseudo infront of the tcp header */
memcpy(tcpcsumblock, &tcpPseudo, sizeof(TCPPseudoHeader));
memcpy(tcpcsumblock+sizeof(TCPPseudoHeader),tcp, sizeof(TCPHeader));
/* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
u_short checksum = in_cksum((unsigned short *)tcpcsumblock, sizeof(tcpcsumblock));
}
==编辑==
新的tcp功能
/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
struct TCP_Pseudo tcpPseudo;
/* tcp pseudo header */
memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
tcpPseudo.source_ip = ip->source_ip;
tcpPseudo.destination_ip = ip->destination_ip;
tcpPseudo.zero = 0;
tcpPseudo.protocol = 6;
tcpPseudo.len = htons(ip->ip_len - (ip->ip_hdr_len * 4));
int len = sizeof(struct TCP_Pseudo) + tcpPseudo.len;
u_char tcpcsumblock[len];
memcpy(tcpcsumblock, &tcpPseudo, sizeof(struct TCP_Pseudo));
memcpy(tcpcsumblock + sizeof(struct TCP_Pseudo), (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header)), tcpPseudo.len);
/* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
u_short checksum = in_cksum((unsigned short *)ps_tcp, len);
char *cs = checksum ? "Invalid Checksum!" : "Valid!";
}
ip header
typedef struct IP_Header {
#if __BYTE_ORDER__ == __LITTLE_ENDIAN__
uint8_t ip_hdr_len:4; /* header length */
uint8_t ip_version:4; /* ip version */
#else
uint8_t ip_version:4; /* ip version */
uint8_t ip_hdr_len:4; /* The IP header length */
#endif
uint8_t ip_tos; /* type of service */
uint16_t ip_len; /* total length */
uint16_t ip_id; /* identification */
uint16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
uint8_t ip_ttl; /* time to live */
uint8_t ip_p; /* protocol */
uint16_t ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
} __attribute__ ((packed));
tcp标题
typedef struct TCP_Header {
uint16_t tcp_source_port; /* source port */
uint16_t tcp_dest_port; /* destination port */
uint32_t tcp_seq; /* sequence */
uint32_t tcp_ack; /* acknowledgement number */
uint8_t tcp_offest; /* data offset */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
uint8_t tcp_flags; /* flags */
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_NS 0x100
#define TH_RS 0xE00
uint16_t tcp_window; /* window */
uint16_t tcp_sum; /* checksum */
uint16_t tcp_urp; /* urgent pointer */
} __attribute__ ((packed));
tcp伪标题
typedef struct TCP_Pseudo {
struct in_addr src_ip; /* source ip */
struct in_addr dest_ip; /* destination ip */
uint8_t zeroes; /* = 0 */
uint8_t protocol; /* = 6 */
uint16_t len; /* length of TCPHeader */
} __attribute__ ((packed));
答案 0 :(得分:10)
问题在于这一行:
tcpPseudo.length = htons(sizeof(struct TCP_Header));
根据RFC 793:
TCP长度是TCP标头长度加上数据长度 八位字节(这不是明确传输的数量,但是 计算),它不计算伪的12个八位字节 报头中。
您只设置TCP标头长度,但它应该是TCP标头长度+数据长度。
数据长度是IP报头报告的Total Length
减去IP报头长度(字段在IP报头中命名为IHL
,必须乘以4才能得到以字节为单位的长度减去TCP标头的大小。
然而,由于您要将TCP标头的长度添加到数据长度,您只需要从总数据包长度中减去IP标头长度,剩下的就是TCP标头和数据长度的总和。
tcpPseudo.length = htons(ntohs(ip->total_length) - (ip->ihl * 4));
同样根据RFC:
校验和字段是该字符串的16位补码 补充标题和文本中所有16位字的总和。
“和文本”表示TCP标头后面的所有数据也是校验和。否则TCP无法保证数据传输正确。请记住,TCP是一种可靠的协议,它将重新传输损坏的数据,但因此它必须识别数据何时被破坏。
因此必须将整个数据包减去IP标头添加到tcpcsumblock
。因此tcpcsumblock
必须足够大以使整个数据包适合(在TCP的情况下,通常足够1500字节,但理论上IP数据包可能大到64 KB并且如果需要将被分段)然后你必须将伪标头,tcp标头和所有内容添加到数据包的末尾。
我为你写了一段代码。我通过提供一些实时数据来验证这是正确的。作为奖励,此实现对IP标头执行一些健全性检查,包括验证其校验和(因为如果不匹配,所有标头字段可能包含伪造,因为标头很可能已损坏),并且它也没有需要分配任何动态内存或复制任何数据,所以它应该非常快。特别是对于最后一个功能,我不得不更改in_cksum
函数以接受第三个参数。如果此参数为零,则其行为与代码中的版本完全相同,但通过提供正确的值,您可以使用此函数更新已计算的校验和,就像您要校验和的数据直接跟随数据一样之前已经做过校验和(非常漂亮,是吗?)
uint16_t in_cksum (const void * addr, unsigned len, uint16_t init) {
uint32_t sum;
const uint16_t * word;
sum = init;
word = addr;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (len >= 2) {
sum += *(word++);
len -= 2;
}
if (len > 0) {
uint16_t tmp;
*(uint8_t *)(&tmp) = *(uint8_t *)word;
sum += tmp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return ((uint16_t)~sum);
}
void readTCP (const u_char *packets) {
uint16_t csum;
unsigned ipHdrLen;
unsigned ipPacketLen;
unsigned ipPayloadLen;
struct TCP_Pseudo pseudo;
const struct IP_Header * ip;
const struct TCP_Header * tcp;
// Verify IP header and calculate IP payload length
ip = (const struct IP_Header *)(packets + sizeof(struct Ethernet_Header));
ipHdrLen = ip->ip_hdr_len * 4;
if (ipHdrLen < sizeof(struct IP_Header)) {
// Packet is broken!
// IP packets must not be smaller than the mandatory IP header.
return;
}
if (in_cksum(ip, ipHdrLen, 0) != 0) {
// Packet is broken!
// Checksum of IP header does not verify, thus header is corrupt.
return;
}
ipPacketLen = ntohs(ip->ip_len);
if (ipPacketLen < ipHdrLen) {
// Packet is broken!
// The overall packet cannot be smaller than the header.
return;
}
ipPayloadLen = ipPacketLen - ipHdrLen;
// Verify that there really is a TCP header following the IP header
if (ip->ip_p != 6) {
// No TCP Packet!
return;
}
if (ipPayloadLen < sizeof(struct TCP_Header)) {
// Packet is broken!
// A TCP header doesn't even fit into the data that follows the IP header.
return;
}
// TCP header starts directly after IP header
tcp = (const struct TCP_Header *)((const u_char *)ip + ipHdrLen);
// Build the pseudo header and checksum it
pseudo.src_ip = ip->ip_src;
pseudo.dest_ip = ip->ip_dst;
pseudo.zeroes = 0;
pseudo.protocol = 6;
pseudo.len = htons(ipPayloadLen);
csum = in_cksum(&pseudo, (unsigned)sizeof(pseudo), 0);
// Update the checksum by checksumming the TCP header
// and data as if those had directly followed the pseudo header
csum = in_cksum(tcp, ipPayloadLen, (uint16_t)~csum);
char * cs = csum ? "Invalid Checksum!" : "Valid!";
printf("%s\n", cs);
}