blob: e6fef6e723b98631f7a654c4336942dd3a3521dc [file] [log] [blame]
/**
* Calculate big endian 16-bit sum of a buffer (max 128kB),
* then fold and negate it, producing a 16-bit result in [0..FFFE].
*/
u16 calc_csum(u32 sum, const u8* const buf, const s32 len) {
s32 i;
for (i = 0; i < len; ++i) sum += buf[i] * ((i & 1) ? 1 : 256);
sum = (sum & 0xFFFF) + (sum >> 16); /* max after this is 1FFFE */
u16 csum = sum + (sum >> 16);
return ~csum; /* assuming sum > 0 on input, this is in [0..FFFE] */
}
static u16 fix_udp_csum(u16 csum) {
return csum ? csum : 0xFFFF;
}
/**
* Calculate the ipv4 header and tcp/udp layer 4 checksums.
* (assumes IPv4 checksum field is set to partial sum of ipv4 options [likely 0])
* (assumes L4 checksum field is set to L4 payload length on input)
* Warning: TCP/UDP L4 checksum corrupts packet iff ipv4 options are present.
* Warning: first IPV4_HLEN + TCP_HLEN == 40 bytes of ip4_pkt must be writable!
* Returns 6-bit DSCP value [0..63], garbage on parse error.
*/
static int calc_ipv4_csum(u8* const ip4_pkt, const s32 len) {
store_be16(ip4_pkt + 10, calc_csum(0xFFFF, ip4_pkt, IPV4_HLEN));
u8 proto = ip4_pkt[9];
u16 csum = calc_csum(proto, ip4_pkt + 12, len - 12);
switch (proto) {
case IPPROTO_ICMP:
/* Note: for this to work, the icmpv4 checksum field must be prefilled
* with non-zero negative sum of proto (1) and src/dst ips, ie:
* 5 * 0xFFFF - 1 - (src >> 16) - (src & 0xFFFF) - (dst >> 16) - (dst & 0xFFFF)
*/
store_be16(ip4_pkt + IPV4_HLEN + 2, csum);
break;
case IPPROTO_TCP:
store_be16(ip4_pkt + IPV4_HLEN + 16, csum);
break;
case IPPROTO_UDP:
store_be16(ip4_pkt + IPV4_HLEN + 6, fix_udp_csum(csum));
break;
}
return ip4_pkt[1] >> 2; /* DSCP */
}
/**
* Calculate the ipv6 icmp6/tcp/udp layer 4 checksums.
* (assumes L4 checksum field is set to L4 payload length on input)
* Warning: first IPV6_HLEN + TCP_HLEN == 60 bytes of ip6_pkt must be writable!
* Returns 6-bit DSCP value [0..63], garbage on parse error.
*/
static int calc_ipv6_csum(u8* const ip6_pkt, const s32 len) {
u8 proto = ip6_pkt[6];
u16 csum = calc_csum(proto, ip6_pkt + 8, len - 8);
switch (proto) {
case IPPROTO_ICMPV6:
store_be16(ip6_pkt + IPV6_HLEN + 2, csum);
break;
case IPPROTO_TCP:
store_be16(ip6_pkt + IPV6_HLEN + 16, csum);
break;
case IPPROTO_UDP:
store_be16(ip6_pkt + IPV6_HLEN + 6, fix_udp_csum(csum));
break;
}
return (read_be16(ip6_pkt) >> 6) & 0x3F; /* DSCP */
}
/**
* Calculate and store packet checksums and return dscp.
*
* @param pkt - pointer to the start of the ethernet header of the packet.
* WARNING: first ETHER_HLEN + max(IPV{4,6}_HLEN) + TCP_HLEN = 74 bytes
* of buffer pointed to my 'pkt' pointer *MUST* be writable.
* @param len - length of the packet.
*
* @return 6-bit DSCP value [0..63], garbage on parse error.
*/
int calculate_checksum_and_return_dscp(u8* const pkt, const s32 len) {
switch (read_be16(pkt + 12)) { /* ethertype */
case ETH_P_IP: return calc_ipv4_csum(pkt + ETH_HLEN, len - ETH_HLEN);
case ETH_P_IPV6: return calc_ipv6_csum(pkt + ETH_HLEN, len - ETH_HLEN);
default: return 0;
}
}