This question already has answers here:
C Programming TCP Checksum
(4 answers)
Closed 5 years ago.
I've been trying to calculate the checksum of a TCP header in C. For my purposes, the TCP header is 20 bytes long and has no extra data or options. Also, according to my knowledge, the TCP checksum is calculated as the one's complement of the one's complement sum of the 16 bit words in the header. So, I wrote a function to calculate the TCP checksum, considering the conditions I stated above. However, when I examine the packet being sent out after I calculate the checksum, I get this:
1 0.000000 10.0.2.15 -> 127.0.0.1 TCP 74 24131->8000 [SYN] Seq=0 Win=13273 [TCP CHECKSUM INCORRECT] Len=20
I examined the packet using tshark. However, I also examined the packets with Wireshark, and it also said the same thing. Except, that in parenthesis, it said, "maybe caused by TCP checksum offload?" Which, I don't fully understand.
Here's the code I used to calculate the checksum:
unsigned short tcp_checksum(struct tcphdr* tcph,struct iphdr* iph) {
/* Calculate TCP checksum */
struct pseudo_hdr* pseudo_hdr;
u_char* buffer;
u_char* segment;
u_char* pseudo_segment;
unsigned int count = 32;
unsigned long sum = 0;
unsigned short mask = 0xffff;
unsigned long long* hdr;
pseudo_hdr = malloc(sizeof(struct pseudo_hdr)); // allocate memory
buffer = malloc(32); // allocate for 32 bytes of information
if (pseudo_hdr == NULL || buffer == NULL) { // if memory wasn't allocated properly
err();
if (pseudo_hdr != NULL) free(pseudo_hdr);
if (buffer != NULL) free(buffer);
return 0;
}
pseudo_hdr->saddr = (unsigned long)iph->saddr; // we add the cast because the fields if of type u_int_32
pseudo_hdr->daddr = (unsigned long)iph->daddr; // same reason for adding the cast as above
bzero(&pseudo_hdr->reserved,sizeof(pseudo_hdr->reserved)); // zero header
pseudo_hdr->proto = IPPROTO_TCP; // this will always be 6
pseudo_hdr->len = htons(tcph->doff*4); // length of tcp header
/* Place both headers into a buffer */
segment = (u_char*)tcph;
pseudo_segment = (u_char*)pseudo_hdr;
/* Concactenate */
memcpy((char*)buffer,(char*)pseudo_segment,sizeof(struct pseudo_hdr)); // first the pseudo header
memcpy((char*)buffer+12,(char*)segment,sizeof(struct tcphdr)); // then the TCP segment
/* Calculate checksum just like IP checksum (RFC C implementation) */
hdr = (unsigned long long*)buffer;
while (count > 1) {
sum += *(unsigned short*)hdr++;
count -= 2;
}
// Add left over bytes, if any
if (count > 0) sum += * (u_char*) hdr;
// Fold 32-bit sum to 16 bits
while (sum>>16) sum = (sum & mask) + (sum >> 16);
sum = ~sum; // bitwise NOT operation
/* Discard headers and buffer */
free(buffer);
free(pseudo_hdr);
return sum;
}
struct iphdr and struct tcphdr are defined in <netinet/ip.h> and <netinet/tcp.h>. Here's my definition of struct pseudo_hdr:
struct pseudo_hdr {
unsigned long saddr; // 4 bytes
unsigned long daddr; // 4 bytes
unsigned char reserved; // 1 byte
unsigned char proto; // 1 byte
unsigned short len; // 2 bytes
};
/* Total = 4+4+1+1+2 = 12 bytes */
As I said, this function, for my purposes, calculates the checksum for a TCP header that is 20 bytes long.
Thank you for any help or recommendations.
#RonMaupin's suggestions about checking out the RFC's and the contribution of everyone else to further my understanding of TCP (and C in general!) helped me to solve my problem. Thank you!
Related
Today I was investing a little more time to learn about ARP packets. To understand it's structure I tried to build one on myself in C using libpcap. I structured a simple ARP request packet and used pcap_inject function to send the packet. This function returns the number of bytes that are sent.
When I debug my code I saw that my packet was 42 bytes long. I search the Internet a bit and couldn't find a answer that tells me if this is the appropriate size for an ARP request or not. Even the wikipedia entry confused me a little. And the I discovered this post. From the answer provided by the user:
If the ARP message is to be sent in an untagged frame then the frame overhead itself is 18 bytes. That would result in a frame of
28+18=46 bytes without padding. Additional 18 bytes of padding are
necessary in this case to bloat the frame to the 64 byte length.
If the ARP message is to be sent in an 802.1Q-tagged frame then the frame overhead is 22 bytes, resulting in the total frame size of
28+22=50 bytes. In this case, the padding needs to be 14 bytes long.
If the ARP message is to be sent in a double-tagged frame then the frame overhead is 26 bytes, resulting in the total frame size of 54
bytes. In this case, the padding needs to be 10 bytes long.
My question is what do I have to do in this situation. Do I have to use padding or not?
Bellow I post the structure of my packet.
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ARP_HTYPE_ETHER 1 /* Ethernet ARP type */
#define ARP_PTYPE_IPv4 0x0800 /* Internet Protocol packet */
/* Ethernet frame header */
typedef struct {
uint8_t dest_addr[ETH_ALEN]; /* Destination hardware address */
uint8_t src_addr[ETH_ALEN]; /* Source hardware address */
uint16_t frame_type; /* Ethernet frame type */
} ether_hdr;
/* Ethernet ARP packet from RFC 826 */
typedef struct {
uint16_t htype; /* Format of hardware address */
uint16_t ptype; /* Format of protocol address */
uint8_t hlen; /* Length of hardware address */
uint8_t plen; /* Length of protocol address */
uint16_t op; /* ARP opcode (command) */
uint8_t sha[ETH_ALEN]; /* Sender hardware address */
uint32_t spa; /* Sender IP address */
uint8_t tha[ETH_ALEN]; /* Target hardware address */
uint32_t tpa; /* Target IP address */
} arp_ether_ipv4;
In the end I just copy each structure member in the bellow order and send the packet:
void packageARP(unsigned char *buffer, ether_hdr *frameHeader, arp_ether_ipv4 *arp_packet, size_t *bufferSize) {
unsigned char *cp;
size_t packet_size;
cp = buffer;
packet_size = sizeof(frameHeader->dest_addr)
+ sizeof(frameHeader->src_addr)
+ sizeof(frameHeader->frame_type)
+ sizeof(arp_packet->htype)
+ sizeof(arp_packet->ptype)
+ sizeof(arp_packet->hlen)
+ sizeof(arp_packet->plen)
+ sizeof(arp_packet->op)
+ sizeof(arp_packet->sha)
+ sizeof(arp_packet->spa)
+ sizeof(arp_packet->tha)
+ sizeof(arp_packet->tpa);
/*
* Copy the Ethernet frame header to the buffer.
*/
memcpy(cp, &(frameHeader->dest_addr), sizeof(frameHeader->dest_addr));
cp += sizeof(frameHeader->dest_addr);
memcpy(cp, &(frameHeader->src_addr), sizeof(frameHeader->src_addr));
cp += sizeof(frameHeader->src_addr);
/* Normal Ethernet-II framing */
memcpy(cp, &(frameHeader->frame_type), sizeof(frameHeader->frame_type));
cp += sizeof(frameHeader->frame_type);
/*
* Add the ARP data.
*/
memcpy(cp, &(arp_packet->htype), sizeof(arp_packet->htype));
cp += sizeof(arp_packet->htype);
memcpy(cp, &(arp_packet->ptype), sizeof(arp_packet->ptype));
cp += sizeof(arp_packet->ptype);
memcpy(cp, &(arp_packet->hlen), sizeof(arp_packet->hlen));
cp += sizeof(arp_packet->hlen);
memcpy(cp, &(arp_packet->plen), sizeof(arp_packet->plen));
cp += sizeof(arp_packet->plen);
memcpy(cp, &(arp_packet->op), sizeof(arp_packet->op));
cp += sizeof(arp_packet->op);
memcpy(cp, &(arp_packet->sha), sizeof(arp_packet->sha));
cp += sizeof(arp_packet->sha);
memcpy(cp, &(arp_packet->spa), sizeof(arp_packet->spa));
cp += sizeof(arp_packet->spa);
memcpy(cp, &(arp_packet->tha), sizeof(arp_packet->tha));
cp += sizeof(arp_packet->tha);
memcpy(cp, &(arp_packet->tpa), sizeof(arp_packet->tpa));
cp += sizeof(arp_packet->tpa);
*bufferSize = packet_size;
}
Is this the correct way of structuring an ARP request packet?
That's the correct structure -- except that the C compiler is free to insert padding in order to ensure structure members are placed at the most efficient boundaries. In particular, spa and tpa are not at natural 32-bit boundaries (due to the preceding 6-byte MAC address fields) and so the compiler might want to insert two bytes of padding before each.
If you are using gcc, you can ensure that doesn't happen with __attribute__((packed)):
struct {
[fields]
} __attribute__((packed)) arp_ether_ipv4;
Other compilers might have a different but equivalent mechanism (a #pragma directive for example).
The ARP payload should be 28 bytes. Adding the 14-byte ethernet header, that gives 42 total bytes. As your cite said, an 802.1Q (VLAN) header inserts an additional 4 bytes and a "double-tagged" frame (not common outside of Internet service providers) will add 2 X 4 = 8 bytes. If you're on an ordinary endpoint machine, you wouldn't typically add these headers anyway. The IT department will have configured your switches to automatically insert/remove these headers as needed.
The 42 bytes will get padded automatically to 64 bytes (Ethernet minimum packet size) by your network driver. 64 is actually 60 + the 4-byte Ethernet FCS [frame checksum]. (The post you cited is apparently including the 4-byte FCS in their calculations, which is why their numbers seem whack.)
Also, don't forget to use network byte order for all uint16_t and uint32_t fields: (ntohs and ntohl)
I am reading a buffer from a socket (AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP)) with the following code. I am using an arp_frame struct to access the component parts of the contained ARP reply. The inet_ntoa() returns the correct first octet of the IP but the other octets are 0 producing 172.0.0.0.
Question 1 is why might this happen?
Question 2 is how can I print r bytes of the msg buffer as hex in host byte order to debug the packet?
unsigned char msg[65535];
struct ether_arp *arp_frame = (struct ether_arp *)msg;
while ((r = recv(sock, msg, sizeof(msg), 0))) {
// skip it's not an ARP REPLY
if (ntohs(arp_frame->arp_op) != ARPOP_REPLY)
continue;
for (i = 0; i < SONOS_PREFIX_NUM; i++) {
if (!memcmp(sonos_prefixes[i], arp_frame->arp_sha, 3)) {
struct in_addr addr;
addr.s_addr = *arp_frame->arp_spa;
printf("Blah: %lu\n", ntohl(*arp_frame->arp_spa));
printf("Sonos found at %s\n", inet_ntoa(addr));
}
}
}
struct ether_arp looks like this:
struct ether_arp {
struct arphdr ea_hdr; /* fixed-size header */
u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
u_int8_t arp_spa[4]; /* sender protocol address */
u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
u_int8_t arp_tpa[4]; /* target protocol address */
};
With that in mind, I think that your addr.s_addr = *arp_frame->arp_spa; looks a little fishy. arp_frame->arp_spa yields a u_int8_t[4], which you then dereference as a pointer. I think memcpy() might be more appropriate there.
If you think the API is broken, print out the bytes.
"Methinks, Brutus, the fault is not in the stars."
i trying to parse a packet. till the ip header everything is fine(i'm able to retrieve all the values correctly). but for the udp header( checked if the protocol is 17) , the values are coming out to be wrong( all the 4 fields).
I'm trying to do this:
struct udp_header{
uint16_t sport;
uint16_t dport;
uint16_t len;
uint16_t chksum;
};
struct udp_header* udp= (struct udp_header*)(packet + 14 + ip_hdr->ip_hl*4);
Packet is the pointer pointing to the beginning of the packet. 14 is for ethernet header.The header length ip when checked is giving out the correct value. But after performing this operation i'm getting all the fields wrongly. when tried with uint8_t as data type( i know its wrong! ) the destintion port somehow is coming out correct.
You have run into endianness. IP packets have all fields in network byte order (aka "big-endian"), and your host system probably runs little-endian. Look into ntohs() and friends for one approach.
The proper approach is to not copy the structure as-is from the network data, but instead extract each field manually and byte-swap it if necessary. This also works around any issues with padding and alignment, there's no guarantee that your struct is mapped into your computer's memory in exactly the same way as the packet is serialized.
So you would do e.g.:
udp_header.sport = ntohs(*(unsigned short*) (packet + 14 + 4 * ip_hdr->ip_hl));
This is also a bit iffy, since it assumes the resulting address can validly be cast into a pointer to unsigned short. On x86 that will work, but it's not epic.
Even better, in my opinion, is to drop the use of pointers and instead write a function called e.g. unsigned short read_u16(void *packet, size_t offset) that extracts the value byte-by-byte and returns it. Then you'd just do:
udp_header.sport = read_u16(packet, 14 + 4 * ip_hdr->ip_hl);
I always use this struct for IP header:
struct sniff_ip {
u_char ip_vhl; /* version << 4 | header length >> 2 */
u_char ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
And to get the UDP struct pointer:
udp = (struct sniff_udp*)(packet + SIZE_ETHERNET + (IP_HL(ip)*4));
As another answer remarked, you have to deal with endianness of your data.
The other thing you need to deal with is byte alignment. For speed, when you define a structure in C like this:
struct udp_header{
uint16_t sport;
uint16_t dport;
uint16_t len;
uint16_t chksum;
};
The C compiler may leave padding bytes between these fields so that member accesses can be done with faster single-instruction memory access assembly instructions. You can check if your c compiler is doing this by printf("struct size is: %u\n", sizeof(struct udp_header));
Assuming you are using GCC, you must disable padding bytes by adding #pragma pack(1) before the structure definition. To re-enable padding for speed you should then use #pragma pack().
I am having a weird issue when i do a pwrite a struct to a file. It adds a single byte with next to a char entry in the struct. When i tried to write the char alone to a file it correctly wrote a single byte. Could some one tell me why the single byte got added??
int main(){
typedef struct pcap_hdr_s {
guint32 magic_number; /* magic number */
guint16 version_major; /* major version number */
guint16 version_minor; /* minor version number */
gint32 thiszone; /* GMT to local correction */
guint32 sigfigs; /* accuracy of timestamps */
guint32 snaplen; /* max length of captured packets, in octets */
guint32 network; /* data link type */
guint32 ts_sec; /* timestamp seconds */
guint32 ts_usec; /* timestamp microseconds */
guint32 incl_len; /* number of octets of packet saved in file */
guint32 orig_len; /* actual length of packet */
guint16 fcf;
char seqno;
guint16 dpan;
guint16 daddr;
guint16 saddr;
gint16 payload_data;
} pcaprec_hdr_t;
pcaprec_hdr_t packet_header;
packet_header.magic_number = PCAP_MAGIC;
packet_header.version_major = 2;
packet_header.version_minor = 4;
packet_header.thiszone = 0;
packet_header.sigfigs = 0;
packet_header.snaplen = 65535;
packet_header.network = 195;
struct timeval tv;
gettimeofday(&tv, NULL);
packet_header.ts_sec = tv.tv_sec;
packet_header.ts_usec = tv.tv_usec;
packet_header.incl_len = 11;
packet_header.orig_len = 13;
packet_header.seqno = 255;
packet_header.dpan = 65535;
packet_header.daddr = 65535;
packet_header.saddr = 65535;
packet_header.payload_data = 8;
int fd = open("sample.cap", O_CREAT | O_WRONLY);
printf("Bytes written: %d \n",pwrite(fd, &packet_header, sizeof(packet_header),0));
}
The struct has a char var "seq" and next to the seq no value a single byte of random value gets added in the file.
Your structure contains a char data member and you probably have word-alignment on. See if you can find a "pack" option in the compiler.
You can probably use a #pragma pack(1) or similar pragma (pragma's are implementation specific) to change the alignment for a particular set of classes.
Be careful though, the compiler word-aligns for performance reasons. The memory bus typically works on word boundaries, so you could end up requiring two fetchs for each word that straddles the word boundary thereby slowing memory access down a bit.
You might want to stream the structure members out individually if you're concerned about the added bytes in the file.
I'm working on a Linux userspace program that receives IPv6 router advertisement packets. As part of RFC4861 I need to verify the ICMPv6 checksum. Based on my research, most of which refers to the refers to the IP checksum in general if you compute the ones compliment checksum of the IPv6 pseudo header and the packet contents the result should be 0xffff. But I keep getting a checksum of 0x3fff.
Is there something wrong with my checksum implementation? does the Linux kernel verify the ICMPv6 checksum before passing the packets to userspace? is there a good reference source for known good ICMPv6 packets to test with?
uint16_t
checksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) {
uint32_t checksum = 0;
union {
uint32_t dword;
uint16_t word[2];
uint8_t byte[4];
} temp;
// IPv6 Pseudo header source address, destination address, length, zeros, next header
checksum += src->s6_addr16[0];
checksum += src->s6_addr16[1];
checksum += src->s6_addr16[2];
checksum += src->s6_addr16[3];
checksum += src->s6_addr16[4];
checksum += src->s6_addr16[5];
checksum += src->s6_addr16[6];
checksum += src->s6_addr16[7];
checksum += dst->s6_addr16[0];
checksum += dst->s6_addr16[1];
checksum += dst->s6_addr16[2];
checksum += dst->s6_addr16[3];
checksum += dst->s6_addr16[4];
checksum += dst->s6_addr16[5];
checksum += dst->s6_addr16[6];
checksum += dst->s6_addr16[7];
temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];
temp.byte[0] = 0;
temp.byte[1] = 0;
temp.byte[2] = 0;
temp.byte[3] = 58; // ICMPv6
checksum += temp.word[0];
checksum += temp.word[1];
while (len > 1) {
checksum += *((const uint16_t *)data);
data = (const uint16_t *)data + 1;
len -= 2;
}
if (len > 0)
checksum += *((const uint8_t *)data);
printf("Checksum %x\n", checksum);
while (checksum >> 16 != 0)
checksum = (checksum & 0xffff) + (checksum >> 16);
checksum = ~checksum;
return (uint16_t)checksum;
}
The while loop is overkill. The body will only happen once.
while (checksum >> 16 != 0)
checksum = (checksum & 0xffff) + (checksum >> 16);
checksum = ~checksum;
return (uint16_t)checksum;
Instead
checksum += checksum >> 16;
return (uint16_t)~checksum;
This is unnecessary. len is always 16-bit
temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];
This is unnecessary. The constant is always 00 00 00 58, so just add 58.
temp.byte[0] = 0;
temp.byte[1] = 0;
temp.byte[2] = 0;
temp.byte[3] = 58; // ICMPv6
checksum += temp.word[0];
checksum += temp.word[1];
Your algorithm looks generally right otherwise, except for the way you handle the endianness of the integers and the last byte odd-numbered byte. From how I read the protocol, the bytes are to be summed in big-endian order, i.e., the bytes 0xAB 0xCD are to be interpreted as the 16-bit 0xABCD. Your code depends on the ordering of your machine.
The order that the integers are built will affect the number of carries, which you are adding correctly into the checksum. But if your code matches your target machine, then the last odd-numbered byte is wrong. 0xAB would result in 0xAB00, not 0x00AB as written.
If this is running on a little-endian machine then I think you need (much) more byte swapping while accumulating the checksum.
For example on a little endian machine the s6_addr16[0] element of a typical IPv6 address starting 2001: will contain 0x0120, and not 0x2001. This will put your carry bits in the wrong place.
The length code appears OK since you are using htonl() there, but the 0x00 0x00 0x00 0x58 and subsequent message accumulation logic does not. I think any left over bits should end up in the high byte too, not the low byte as happens in your code.
Also, using 0x0000 for the pseudo header checksum bytes is what you should do when generating the checksum. To validate the checksum use the actual checksum bytes received in the IPv6 RA, and then you should get 0xffff as the eventual value.
I found my bug: I had a 256 byte input buffer and assumed the iov_len element of msg_iov on recvmsg() was modified to return length of data received. Since the length of my router advertisements where a constant 64 bytes, the difference between this lengths resulted in a constant error in the checksum. I did not need to change the byte order to verify the checksum (although I have not had an odd length ICMPv6 packet to verify my handling of the final byte in the odd length case.
Also, the final NOT of the checksum is only necessary to calculate the checksum, not to verify it. With the above code checksum() will return 0 if the checksum is valid.