-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace uint16_t* with uint8_t* in in_cksum #370
Conversation
What compiler, kernel and CPU are you seeing this on? |
I build dhcpcd on Linux and deploy it to Chrome OS. I've observed this checksum error on both x86_64 and aarch64 CPUs. The compilers I use are x86_64-cros-linux-gnu-clang and aarch64-cros-linux-gnu-clang, respectively. |
ec4c86e
to
6f98368
Compare
Thanks, I have been able to reproduce this now and your change does indeed fix the issue. |
Currently if we compile dhcpcd with -Os, we will get a checksum error upon receiving packets from the DHCP server. This prevents the client from binding a DHCP lease. This checksum error comes from the checksums_valid() function volating the strict aliasing. In this function, |pip| is a union representing the pseudo IP header. It consists of |ip| of struct ip and |w| of uint16_t[]. |ip| is assigned with the IP header data, but |w| is actually used to calculate the checksum. As struct ip and uint16_t[] are incompatible types, according to the strict aliasing, the compiler thinks the assignment to |ip| is unused, so it simply ignores the assignment. This leads to the incorrect checksum calculation. According to the strict aliasing rule, a char* alias with any object pointer, so we can choose to use uint8_t* (unsigned char*) to calculate the checksum. We replace uint16_t* with uint8_t* in in_cksum, and also remove the usage of |w| in checksums_valid().
That's indeed very interesting. Another interesting thing is that this can't be reproduced with |
@acst1223 Thanks for the excellent analysis and patch for the problem. I'm going to go out on a limb and say this looks like a compiler issue as the code compiles with zero warnings and by simply passing &pip to hwaddr_ntoa and dumping the result to stdout fixes the issue. We didn't modify the content at all, just iterated over it. That is pretty odd eh? Looking at other sources for reference, no-one uses htons in the main loop and the casting that requires or does the csum byte by byte - it's always uint16_t. As you say that aliasing is the root cause. We have the union there just so we can reference the structure as uint16_t.
Luckily for us, the reverse is also true! Please review 8a840af and let me know your thoughts. |
I've tried your patch on both x86_64 and aarch64 Chrome OS but unfortunately both give me a checksum error again. Regarding the aliasing rule, casting any other type pointer to char* is valid but not the opposite. I read this doc and in the Casting to char* section it writes
This can explain why your latest patch sometimes won't work. I agree with you that using bytes to calculate cksum is indeed inefficient and sounds weird. I've also been struggling on this, since I cannot think of a way to calculate it in words without breaking the (so strict) aliasing rule. The patch initially in the thread is the best I could think of so far. |
OK, I've redone the patch in commit f1ff96f. Let me know! |
I tried it but the checksum error remained ... All fields in What about making all fields in the pseudo header uint16_t (or just building an uint16_t array) to make in_cksum work? |
I think building the uint16_t array is the better approach. I also added some commentary around checksum results to make things more clear for future readers. Please test 9f8ae57 |
Thanks so much for your effort! The latest patch works fine! I think the reason why direct writing via a pointer doesn't work is the still the aliasing (casting uint16_t* to some other types). The overhead with memcpy is small so I vote for it, too. Thanks again for your work! The checksum issue is finally settled. |
That's great news! So please give 175fc13 a test, let me know and I'll push to master. |
a7e642a just has an updated comment about the buffer now being uint8_t. |
Just curious, what do you mean by "uint16_t buffers just bug you"? |
Excellent, thanks!
Every other buffer in dhcpcd or most code I look at is uint8_t. Anything else is an array. Thanks for helping me test this! Whilst I could replicate your initial failure, I was surprised my fixes still failed for you as they then worked for me. I used valgrind to test with for the first time yesterday (or for day before, I forget) and surprisingly picked up un-initialised memory errors in the in_cksum loop only when the pseudo header was involved. Which is complete nonsense as the memory has to be initialised - I even memset it all to zero at one point just to say "boo, i can do more to initialise" but no, valgrind still complained. memcpy was the only escape for it I could find, so glad it all works now. And has resulted in smaller and hopefully better code. |
This bug's very strange indeed. Thanks to memcpy anyway! This cksum bug was a really long struggle. I'm so happy to see we can finally fix this now! |
Merged to master as dcef5d9 Thanks again for your efforts. |
Currently if we compile dhcpcd with -Os, we will get a checksum error upon receiving packets from the DHCP server. This prevents the client from binding a DHCP lease.
This checksum error comes from the
checksums_valid()
function volating the strict aliasing. In this function,pip
is a union representing the pseudo IP header. It consists ofip
of struct ip andw
of uint16_t[].ip
is assigned with the IP header data, butw
is actually used to calculate the checksum. As struct ip and uint16_t[] are incompatible types, according to the strict aliasing, the compiler thinks the assignment toip
is unused, so it simply ignores the assignment. This leads to the incorrect checksum calculation.According to the strict aliasing rule, a char* alias with any object pointer, so we can choose to use uint8_t* (unsigned char*) to calculate the checksum. We can replace uint16_t* with uint8_t* in in_cksum, and also remove the usage of
w
inchecksums_valid()
.