4

In the network-multicast Haskell documentation, I see a function

setInterface :: Socket -> HostName -> IO ()
Set the outgoing interface address of the multicast.

How can I use this to specify a source-address in a multicast join? The following code, when ran, produces the given output.. (IP Addresses masked for privacy)

Code

import Network.BSD
import Network.Socket hiding (send, sendTo, recv, recvFrom)
import Network.Socket.ByteString
import Network.Multicast hiding (multicastReceiver)
import Text.Printf
import Data.ByteString as B hiding (putStrLn)

sourceIP = "192.168.MMM.NNN"
mcastIP = "224.0.XXX.YYY"
mcastPort = 32101

mcastLoop :: Socket -> IO ()
mcastLoop sock = do
    (msg, addr) <- recvFrom sock 1024
    printf "%s-> %s\n" (show addr) (show . B.unpack $ msg)
    mcastLoop sock 

multicastReceiver :: HostName -> PortNumber -> IO Socket
multicastReceiver host port = do
    proto <- getProtocolNumber "udp"
    sock  <- socket AF_INET Datagram proto
    setInterface sock sourceIP

{-# LINE 81 "src/Network/Multicast.hsc" #-}
    setInterface sock sourceIP
    bindSocket sock $ SockAddrInet port 0
    setInterface sock sourceIP
{-# LINE 82 "src/Network/Multicast.hsc" #-}
    setInterface sock sourceIP
    addMembership sock host
    return sock

main :: IO ()
main = withSocketsDo $ do
    sock <- multicastReceiver mcastIP mcastPort
    dropMembership sock mcastIP
    setInterface sock sourceIP
    addMembership sock mcastIP
    mcastLoop sock

Output

~ - sudo tcpdump -n -nn -i any "host 224.0.XXX.YYY"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
08:00:44.463153 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:00:52.498722 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:00:58.301711 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:01:08.408710 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:02:04.104707 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:03:08.545718 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:04:02.634709 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:05:03.757718 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:06:10.600716 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:07:07.248707 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:08:10.202708 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY
08:09:02.390708 IP 10.52.ZZZ.WWW > 224.0.XXX.YYY: igmp v2 report 224.0.XXX.YYY

This is the comparison of C++/Haskell applications running (as requested by EJP)

~/sandbox - sudo tcpdump -i any "host 192.168.MMM.NNN"        
[sudo] password for gresko: 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes

# C++ application started
16:15:55.490156 IP 192.168.MMM.NNN > 224.0.62.108: igmp v2 report 224.0.62.108
# C++ application killed
16:16:04.689342 IP 192.168.MMM.NNN > all-routers.mcast.net: igmp leave 224.0.62.108

# Haskell application started
# Haskell application killed
gsk
  • 558
  • 5
  • 12
  • This question is similar, but does not answer my question: http://stackoverflow.com/questions/6111204/how-to-specify-a-local-bond-interface-to-multicast-socket-in-haskell – gsk Sep 30 '14 at 18:29
  • Can you elaborate on "doesn't work"? What behavior do you expect versus what you're seeing? – bheklilr Sep 30 '14 at 18:31
  • Elaborated the expected behavior – gsk Sep 30 '14 at 18:40
  • You need to call setInterface *before* joining the group. How this is done in Haskell is another question. Maybe dropMembership/addInterface/addMembership. Looks like they have misdesigned their constructor to me. – user207421 Sep 30 '14 at 20:25
  • @EJP: that's what I figured. I also tried copying the source of multicastReceiver and inserting the setInterface call right after the socket is created. That didn't work either.. – gsk Sep 30 '14 at 21:11
  • That should work. Are you sure you ran that modified code? – user207421 Sep 30 '14 at 21:45
  • See the updated description for the code I ran. – gsk Sep 30 '14 at 22:19
  • Try the setInterface after the bind and before the addMembership. – user207421 Sep 30 '14 at 22:53
  • That doesn't work. I also tried interspersing the `setInterface` in between every line after the socket is created. – gsk Oct 01 '14 at 15:18
  • This all suggests that their `setInterface` method doesn't work at all. Does their `addMembership` method have an overload with an interface address in it? – user207421 Oct 03 '14 at 01:45
  • As far as I can tell, there's no alternative version of `setInterface`. – gsk Oct 03 '14 at 02:20
  • OK Can we now define 'doesn't work'? You definitely see no IGMP message going out that interface when you join? – user207421 Oct 03 '14 at 03:16
  • If I don't specify an interface, the IGMP message goes out with a default source IP, call it A. After setting `setInterface` with an argument of an IP, call it B, I still see A being the source address. If I run an equivalent C++ application, it can send a join with source address B, without issue. (All confirmed with tcpdump). The requested data only comes in when sending a join with source B. – gsk Oct 03 '14 at 12:51

1 Answers1

1

The Haskell authors haven't allowed for this in their constructor. It shouldn't take a group parameter at all, as it doesn't allow you to set the multicast interface before joining the group. Or, the group should be optional.

Also I would say that they should provide an addMembership() override or alternative that takes an NIC address as well as a group address. The underlying Sockets system call supports it after all.

However, unless Haskell's setInterface() method doesn't work at all, in which case there isn't much you can do, the sequence of

dropMembership()
setInterface()
addMembership()

should work.

I would want to confirm that you genuinely don't see any IGMP messages going out that interface before I concluded that setInterface() doesn't work.

I suspect that what is really happening is that the sender isn't sending properly.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Good idea. I've combined the culmination of tips into one code/output sample in the original post. Take a look and let me know what you think. EDIT: Note, it's interesting, because setting an invalid source ip (aka an IP not possessed by any interface in the computer) in `setInterface` yields an error. So it seems as though it is actually doing *something*. – gsk Oct 03 '14 at 13:29
  • `-i any` means sniff on all interfaces, `"host x.y.z.w"` means capture traffic to/from the specified host. The equivalent capture from C++ shows that the outbound packets have the "192.168.MMM.NNN" as their source IP. Further, if a "membership report" isn't a join, what is? – gsk Oct 04 '14 at 00:48