April 24, 2008
Just because I am feeling lazy wrt any real task, I decided to post about the sillyness that is inet_ntoa. Yes, this is ancient/known stuff to rehash, but you can hit the browser back button at any time.
As most of you probably know, the function inet_ntoa converts an IPv4 address to ascii, storing the result in a static buffer. It is this last part that periodically causes people fun when they forget. This mutable memory issue is revealed easily enough in goofed up ‘C’ statements such as:
struct in_addr a,b; inet_aton("18.104.22.168", &a); inet_aton("22.214.171.124", &b); printf("addr a: %s\taddr b: %s\n", inet_ntoa(a), inet_ntoa(b));
which returns “addr a: 126.96.36.199 addr b: 188.8.131.52”. Sometimes more complex systems have a race condition (ex: exception handlers calling inet_ntoa), but it isn’t a larger issue in multi-threaded C programs thanks to thread local storage…
unless you cram many logical threads into a single OS thread like in Haskell. Zao in #haskell asked why inet_ntoa was of type IO (meaning, it isn’t a pure / deterministic function) and I correctly guessed it was a wrapper for the ‘C’ call.
Not to rip at any of the libraries folk, who made a faithful foreign function interface for the sockets/networking functions, but – this was a bad idea. Foremost, the use of IO means this can’t be called from any deterministic function even though the desired operation of converting an address to a string IS deterministic. Secondly, some Haskell programmers (myself included) use Haskells threads liberally (perhaps another, positive, blog post on that). So if someone is being brain-dead then they are going to have a bug – likely non-fatal and obvious due to how string representation of addresses are used.
And if you desire to see the race, I have some code… hope it runs… yep:
import Network.Socket (inet_ntoa) import Control.Concurrent (forkIO, threadDelay) import Control.Monad (when, forever) main = do let zero = (0,"0.0.0.0") one = (1,"184.108.40.206") two = (2,"220.127.116.11") assert = confirm "assert" race = \x -> forever (confirm "FAILURE: " x) assert zero assert one assert two forkIO $ race zero forkIO $ race one forkIO $ race two threadDelay maxBound test n s = forever $ confirm "" (n,s) confirm prefix (n,str) = do s <- inet_ntoa n when (s /= str) (error (prefix ++ s ++ " does not equal " ++ str))
Yes, I know this non-deterministic behavior is being screamed by that ‘IO’ type.
Yes, I know I should write a Haskellized network library.