Closed rafael-santiago closed 10 years ago
In additional, I noticed that in some cases the recalculation of checksum is required even with non-changed packets otherwise the packets will be dropped...
In theory WinDivertRecv should (by default) return a packet with correct checksums. However, looking at the driver code, it appears the driver will only calculate checksums for IPv4 packets (windivert_update_checksums() immediately returns if not IPv4). I'm not sure why, as this code was written a long time ago. This may explain why unchanged IPv6 packets are dropped.
I will add a fix in the next release.
Ok, thank you for the clarifications.
Hi,
I think that I found a way to "solve" this issue but it isn't the best solution yet:
At first I implemented the IPv6 pseudo-header composition besides the usage of it at windivert_update_checksums() function.
After, I changed windivert_queue_packet() a little because I noticed that "packet->udp_checksum" is always "TRUE" to TCP packets and "packet->tcp_checksum" is always "FALSE"... I don't know the reason... I suspect that it is due to "NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO" be composed by unions (and unions are....)... then now I'm setting it by myself.
Something like:
// proto = ipv4 or ipv6 protocol field
switch (proto) {
case IPPROTO_TCP:
packet->tcp_checksum = TRUE;
break;
case IPPROTO_UDP:
packet->udp_checksum = TRUE;
break;
default:
packet->tcp_checksum = FALSE;
packet->udp_checksum = FALSE;
break;
}
Maybe it can cause overhead but using it, at least, I can access the local address (eg.: IPv4 loopback, IPv6 loopback, real IPv4 and real IPv6).
The windivert_update_checksums() function which I used in my tests:
static void windivert_update_checksums(void *header, size_t len, BOOL update_ip, BOOL update_tcp, BOOL update_udp)
{
struct
{
UINT32 SrcAddr;
UINT32 DstAddr;
UINT8 Zero;
UINT8 Protocol;
UINT16 TransLength;
} pseudo_header;
struct
{
UINT32 SrcAddr[4];
UINT32 DstAddr[4];
UINT8 Protocol;
UINT8 Zero;
UINT16 TransLength;
} pseudo_header6;
struct iphdr *ip_header = NULL;
struct ipv6hdr *ip6_header = NULL;
unsigned char version = (*((unsigned char *)header)) >> 4;
unsigned char proto = 0;
size_t ip_header_len, trans_len, pseudo_header_len;
void *trans_header;
void *pseudo_header_p;
struct tcphdr *tcp_header;
struct udphdr *udp_header;
UINT16 *trans_check_ptr;
UINT sum;
if (!update_ip && !update_tcp && !update_udp)
{
return;
}
if (len < sizeof(struct iphdr))
{
return;
}
switch (version)
{
case 4:
ip_header = (struct iphdr *)header;
break;
case 6:
ip6_header = (struct ipv6hdr *)header;
break;
default:
return;
break;
}
if (version == 4) {
ip_header_len = ip_header->HdrLength*sizeof(UINT32);
if (len < ip_header_len)
{
return;
}
if (update_ip)
{
ip_header->Checksum = 0;
ip_header->Checksum = windivert_checksum(NULL, 0, ip_header,
ip_header_len);
}
trans_len = RtlUshortByteSwap(ip_header->Length) - ip_header_len;
trans_header = (UINT8 *)ip_header + ip_header_len;
proto = ip_header->Protocol;
} else {
trans_len = RtlUshortByteSwap(ip6_header->Length);
trans_header = (UINT8 *)ip6_header + 40;
proto = ip6_header->NextHdr;
}
switch (proto)
{
case IPPROTO_TCP:
if (!update_tcp)
{
return;
}
tcp_header = (struct tcphdr *)trans_header;
if (trans_len < sizeof(struct tcphdr))
{
return;
}
trans_check_ptr = &tcp_header->Checksum;
break;
case IPPROTO_UDP:
if (!update_udp)
{
return;
}
udp_header = (struct udphdr *)trans_header;
if (trans_len < sizeof(struct udphdr))
{
return;
}
trans_check_ptr = &udp_header->Checksum;
break;
default:
return;
}
if (version == 4) {
pseudo_header.SrcAddr = ip_header->SrcAddr;
pseudo_header.DstAddr = ip_header->DstAddr;
pseudo_header.Zero = 0x0;
pseudo_header.Protocol = proto;
pseudo_header.TransLength = RtlUshortByteSwap((UINT16)trans_len);
pseudo_header_p = (UINT8 *)&pseudo_header;
pseudo_header_len = sizeof(pseudo_header);
} else {
memcpy(pseudo_header6.SrcAddr, ip6_header->SrcAddr, sizeof(ip6_header->SrcAddr));
memcpy(pseudo_header6.DstAddr, ip6_header->DstAddr, sizeof(ip6_header->DstAddr));
pseudo_header6.Zero = 0x0;
pseudo_header6.Protocol = proto;
pseudo_header6.TransLength = RtlUshortByteSwap((UINT16)trans_len);
pseudo_header_p = (UINT8 *)&pseudo_header6;
pseudo_header_len = sizeof(pseudo_header6);
}
*trans_check_ptr = 0x0;
sum = windivert_checksum(pseudo_header_p, pseudo_header_len,
trans_header, trans_len);
if (sum == 0 && proto == IPPROTO_UDP)
{
*trans_check_ptr = 0xFFFF;
}
else
{
*trans_check_ptr = (UINT16)sum;
}
}
So the problem is confirmed. The driver does not calculate checksums for ipv6 packets. If the packet is re-injected unchanged, then it is rejected. I am working on a patch.
Longer term: the checksum calculation should probably be moved out of the driver and into the user-mode DLL. Also, I think checksum calculation should be disabled by default, since most users modify packets anyway. This is for WinDivert1.2.
Yes, I agree about the execution context of checksum calculation. Anyway, I think is rather weird the ".Transmit.TcpChecksum" field be always 0 and ".Transmit.UdpChecksum" be 1 even when the transport protocol is in fact 6. At least in my test environment I noticed it.
I can confirm for IPv6 loopback packets. I think there is mix up between Transmit & Receive somewhere, so Transmit.UdpChecksum = Receive.TcpChecksumSucceeded.
For non-loopback both flags are zero. This is what I except since my test set-up does not seem to support IPv6 offloading.
The driver now calculates checksums for IPv6 packets. But still need to figure out what is going on with the checksum flags for loopback packets.
Changed the driver so that it ignores the checksum info for loopback packets. This should fix the issue for all packets.
Hi!
I was testing the webfilter sample changing the filter from "outbound && ip && tcp.DstPort == 80 && tcp.PayloadLength > 0" to "outbound && ipv6 && tcp.DstPort == 80 && tcp.PayloadLength > 0" and IPv6 packets were dropped in some specific cases.
Steps to reproduce this behaviour:
In additional, I noticed that in some cases the recalculation of checksum is required even with non-changed packets otherwise the packets will be dropped... I don't know the reason.
My impression is that there is some problem inside the driver code. Can you try to reproduce it inside your environment and give me some feedback?
Thanks in advance!