lpeterse / haskell-socket

A Haskell binding to the POSIX sockets API
MIT License
47 stars 10 forks source link

`connect` with Datagram sockets results in "connection refused" every other `send` to localhost #55

Closed jberryman closed 6 years ago

jberryman commented 6 years ago

My understanding is that connect can be used with UDP sockets to set the default destination for sending over that socket, but strangely I find that this seems to cause a eConnectionRefused exception to be raised on every other send, but only when there is no listener.

Test in both 0.5.something and 0.8.0.1. You can hopefully reproduce with this (so long as there is no server listening on that port). I'm on linux:

{-# LANGUAGE ScopedTypeVariables, OverloadedStrings, NamedFieldPuns #-}
import Data.Functor
import System.Socket
import System.Socket.Protocol.UDP
import System.Socket.Type.Datagram
import System.Socket.Family.Inet
import Control.Monad

-- uncomment below, and run `repro 2` to raise exception
repro :: Int -> IO ()
repro n = do
  (s :: Socket Inet Datagram UDP) <- socket

  -- Uncommenting raises eConnectionRefused...
  -- connect s (SocketAddressInet  inetLoopback  6565     :: SocketAddress Inet)
  putStrLn "Starting send"
  replicateM_ n $ 
    -- ...but only on every other iteration of this:
    void $ sendTo s "FOO" mempty (SocketAddressInet  inetLoopback  6565     :: SocketAddress Inet)
  putStrLn "Done"

It's very possible there is something funny going on on my machine as well, but difficult to triangulate; I'm still pretty hazy on the details of unix sockets, and sockets seems to be doing very much it's own complex thing as well.

jberryman commented 6 years ago

Actually it seems this is expected behavior for a UDP socket with connect called: http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

With a connected UDP socket, three things change, compared to the default unconnected UDP socket:

We can no longer specify the destination IP address and port for an output operation. That is, we do not use sendto, but write or send instead. Anything written to a connected UDP socket is automatically sent to the protocol address (e.g., IP address and port) specified by connect.

    Similar to TCP, we can call sendto for a connected UDP socket, but we cannot specify a destination address. The fifth argument to sendto (the pointer to the socket address structure) must be a null pointer, and the sixth argument (the size of the socket address structure) should be 0. The POSIX specification states that when the fifth argument is a null pointer, the sixth argument is ignored.

We do not need to use recvfrom to learn the sender of a datagram, but read, recv, or recvmsg instead. The only datagrams returned by the kernel for an input operation on a connected UDP socket are those arriving from the protocol address specified in connect. Datagrams destined to the connected UDP socket's local protocol address (e.g., IP address and port) but arriving from a protocol address other than the one to which the socket was connected are not passed to the connected socket. This limits a connected UDP socket to exchanging datagrams with one and only one peer.

    Technically, a connected UDP socket exchanges datagrams with only one IP address, because it is possible to connect to a multicast or broadcast address.

Asynchronous errors are returned to the process for connected UDP sockets.

The corollary, as we previously described, is that unconnected UDP sockets do not receive asynchronous errors.