agnat / node_mdns

mdns/zeroconf/bonjour service discovery add-on for node.js
http://agnat.github.com/node_mdns
MIT License
870 stars 146 forks source link

IPv4 addresses not reliably returned #108

Closed jcwren closed 8 years ago

jcwren commented 9 years ago

Running into an issue on OSX 10.10 with node-mdns 2.2.2 under NodeJS 0.10.33 where the IPv4 address isn't always returned in the addresses array. I've included wireshark captures with the IPv4 address both being returned and not returned. An iPad running iOS 8.1.1 is the device advertising, although I get the same behavior with an iPad running iOS 7.x. This used to work reliably, but I'm not sure if it was an upgrade to OSX 10.10 or node-mdns 2.2.2 that broke it.

The address is present in the mDNS packet, but it appears that node-mdns isn't parsing it out properly. The Bonjour Browser sees both addresses all the time.

I would have attached this as a file, but it seems only image files can be attached.

--- Works ---
#
#  Display of '.on ('serviceUp', function (service) {
#
{ interfaceIndex: 4,
  type: 
   { name: 'practiscoresync',
     protocol: 'tcp',
     subtypes: [],
     fullyQualified: true },
  replyDomain: 'local.',
  flags: 2,
  name: 'JCW\'s iPad Mini White:315c54cc',
  networkInterface: 'en0',
  fullname: 'JCW\'s\\032iPad\\032Mini\\032White:315c54cc._practiscoresync._tcp.local.',
  host: 'JCWs-iPad-Mini-White.local.',
  port: 59613,
  addresses: [ 'fe80::86a:dfa9:6f2f:2303', '172.16.1.21' ] }
2014-11-25 22:57:33.659: 172.16.1.21: Practiscore device 'JCW's iPad Mini White:315c54cc' now on-line

#
#  Wireshark record
#
Frame 1: 261 bytes on wire (2088 bits), 261 bytes captured (2088 bits) on interface 0
    Interface id: 0 (en0)
    Encapsulation type: Ethernet (1)
    Arrival Time: Nov 25, 2014 22:56:33.204828000 EST
    [Time shift for this packet: 0.000000000 seconds]
    Epoch Time: 1416974193.204828000 seconds
    [Time delta from previous captured frame: 0.000000000 seconds]
    [Time delta from previous displayed frame: 0.000000000 seconds]
    [Time since reference or first frame: 0.000000000 seconds]
    Frame Number: 1
    Frame Length: 261 bytes (2088 bits)
    Capture Length: 261 bytes (2088 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:udp:dns]
    [Coloring Rule Name: UDP]
    [Coloring Rule String: udp]
Ethernet II, Src: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1), Dst: IPv4mcast_fb (01:00:5e:00:00:fb)
    Destination: IPv4mcast_fb (01:00:5e:00:00:fb)
        Address: IPv4mcast_fb (01:00:5e:00:00:fb)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...1 .... .... .... .... = IG bit: Group address (multicast/broadcast)
    Source: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1)
        Address: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IP (0x0800)
Internet Protocol Version 4, Src: 172.16.1.21 (172.16.1.21), Dst: 224.0.0.251 (224.0.0.251)
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes
    Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00: Not-ECT (Not ECN-Capable Transport))
        0000 00.. = Differentiated Services Codepoint: Default (0x00)
        .... ..00 = Explicit Congestion Notification: Not-ECT (Not ECN-Capable Transport) (0x00)
    Total Length: 247
    Identification: 0x2f07 (12039)
    Flags: 0x00
        0... .... = Reserved bit: Not set
        .0.. .... = Don't fragment: Not set
        ..0. .... = More fragments: Not set
    Fragment offset: 0
    Time to live: 255
    Protocol: UDP (17)
    Header checksum: 0xfdcd [validation disabled]
        [Good: False]
        [Bad: False]
    Source: 172.16.1.21 (172.16.1.21)
    Destination: 224.0.0.251 (224.0.0.251)
    [Source GeoIP: Unknown]
    [Destination GeoIP: Unknown]
User Datagram Protocol, Src Port: 5353 (5353), Dst Port: 5353 (5353)
    Source Port: 5353 (5353)
    Destination Port: 5353 (5353)
    Length: 227
    Checksum: 0x3fc9 [validation disabled]
        [Good Checksum: False]
        [Bad Checksum: False]
    [Stream index: 0]
Domain Name System (response)
    Transaction ID: 0x0000
    Flags: 0x8400 Standard query response, No error
        1... .... .... .... = Response: Message is a response
        .000 0... .... .... = Opcode: Standard query (0)
        .... .1.. .... .... = Authoritative: Server is an authority for domain
        .... ..0. .... .... = Truncated: Message is not truncated
        .... ...0 .... .... = Recursion desired: Don't do query recursively
        .... .... 0... .... = Recursion available: Server can't do recursive queries
        .... .... .0.. .... = Z: reserved (0)
        .... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
        .... .... ...0 .... = Non-authenticated data: Unacceptable
        .... .... .... 0000 = Reply code: No error (0)
    Questions: 0
    Answer RRs: 4
    Authority RRs: 0
    Additional RRs: 2
    Answers
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type SRV, class IN, cache flush, priority 0, weight 0, port 59613, target JCWs-iPad-Mini-White.local
            Service: JCW's iPad Mini White:315c54cc
            Protocol: _practiscoresync
            Name: _tcp.local
            Type: SRV (Server Selection) (33)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 4500
            Data length: 29
            Priority: 0
            Weight: 0
            Port: 59613
            Target: JCWs-iPad-Mini-White.local
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type TXT, class IN, cache flush
            Name: JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
            Type: TXT (Text strings) (16)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 4500
            Data length: 1
            TXT Length: 0
            TXT:
        _practiscoresync._tcp.local: type PTR, class IN, JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
            Name: _practiscoresync._tcp.local
            Type: PTR (domain name PoinTeR) (12)
            .000 0000 0000 0001 = Class: IN (0x0001)
            0... .... .... .... = Cache flush: False
            Time to live: 4500
            Data length: 2
            Domain Name: JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
        _services._dns-sd._udp.local: type PTR, class IN, _practiscoresync._tcp.local
            Name: _services._dns-sd._udp.local
            Type: PTR (domain name PoinTeR) (12)
            .000 0000 0000 0001 = Class: IN (0x0001)
            0... .... .... .... = Cache flush: False
            Time to live: 4500
            Data length: 2
            Domain Name: _practiscoresync._tcp.local
    Additional records
        JCWs-iPad-Mini-White.local: type A, class IN, cache flush, addr 172.16.1.21
            Name: JCWs-iPad-Mini-White.local
            Type: A (Host Address) (1)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 120
            Data length: 4
            Address: 172.16.1.21 (172.16.1.21)
        JCWs-iPad-Mini-White.local: type AAAA, class IN, cache flush, addr fe80::86a:dfa9:6f2f:2303
            Name: JCWs-iPad-Mini-White.local
            Type: AAAA (IPv6 Address) (28)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 120
            Data length: 16
            AAAA Address: fe80::86a:dfa9:6f2f:2303 (fe80::86a:dfa9:6f2f:2303)

0000  01 00 5e 00 00 fb 1c e6  2b 8d c2 c1 08 00 45 00   ..^..... +.....E.
0010  00 f7 2f 07 00 00 ff 11  fd cd ac 10 01 15 e0 00   ../..... ........
0020  00 fb 14 e9 14 e9 00 e3  3f c9 00 00 84 00 00 00   ........ ?.......
0030  00 04 00 00 00 02 1e 4a  43 57 27 73 20 69 50 61   .......J CW's iPa
0040  64 20 4d 69 6e 69 20 57  68 69 74 65 3a 33 31 35   d Mini W hite:315
0050  63 35 34 63 63 10 5f 70  72 61 63 74 69 73 63 6f   c54cc._p ractisco
0060  72 65 73 79 6e 63 04 5f  74 63 70 05 6c 6f 63 61   resync._ tcp.loca
0070  6c 00 00 21 80 01 00 00  11 94 00 1d 00 00 00 00   l..!.... ........
0080  e8 dd 14 4a 43 57 73 2d  69 50 61 64 2d 4d 69 6e   ...JCWs- iPad-Min
0090  69 2d 57 68 69 74 65 c0  41 c0 0c 00 10 80 01 00   i-White. A.......
00a0  00 11 94 00 01 00 c0 2b  00 0c 00 01 00 00 11 94   .......+ ........
00b0  00 02 c0 0c 09 5f 73 65  72 76 69 63 65 73 07 5f   ....._se rvices._
00c0  64 6e 73 2d 73 64 04 5f  75 64 70 c0 41 00 0c 00   dns-sd._ udp.A...
00d0  01 00 00 11 94 00 02 c0  2b c0 58 00 01 80 01 00   ........ +.X.....
00e0  00 00 78 00 04 ac 10 01  15 c0 58 00 1c 80 01 00   ..x..... ..X.....
00f0  00 00 78 00 10 fe 80 00  00 00 00 00 00 08 6a df   ..x..... ......j.
0100  a9 6f 2f 23 03                                     .o/#.            

--- Fails ---
#
#  Display of '.on ('serviceUp', function (service) {
#
{ interfaceIndex: 4,
  type: 
   { name: 'practiscoresync',
     protocol: 'tcp',
     subtypes: [],
     fullyQualified: true },
  replyDomain: 'local.',
  flags: 2,
  name: 'JCW\'s iPad Mini White:315c54cc',
  networkInterface: 'en0',
  fullname: 'JCW\'s\\032iPad\\032Mini\\032White:315c54cc._practiscoresync._tcp.local.',
  host: 'JCWs-iPad-Mini-White.local.',
  port: 59613,
  addresses: [ 'fe80::86a:dfa9:6f2f:2303' ] }
2014-11-25 22:57:43.943: fe80::86a:dfa9:6f2f:2303: Practiscore device 'JCW's iPad Mini White:315c54cc' now on-line

#
#  Wireshark record
#
Frame 2: 257 bytes on wire (2056 bits), 257 bytes captured (2056 bits) on interface 0
    Interface id: 0 (en0)
    Encapsulation type: Ethernet (1)
    Arrival Time: Nov 25, 2014 22:57:43.938904000 EST
    [Time shift for this packet: 0.000000000 seconds]
    Epoch Time: 1416974263.938904000 seconds
    [Time delta from previous captured frame: 70.734076000 seconds]
    [Time delta from previous displayed frame: 70.734076000 seconds]
    [Time since reference or first frame: 70.734076000 seconds]
    Frame Number: 2
    Frame Length: 257 bytes (2056 bits)
    Capture Length: 257 bytes (2056 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ip:udp:dns]
    [Coloring Rule Name: UDP]
    [Coloring Rule String: udp]
Ethernet II, Src: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1), Dst: IPv4mcast_fb (01:00:5e:00:00:fb)
    Destination: IPv4mcast_fb (01:00:5e:00:00:fb)
        Address: IPv4mcast_fb (01:00:5e:00:00:fb)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...1 .... .... .... .... = IG bit: Group address (multicast/broadcast)
    Source: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1)
        Address: Apple_8d:c2:c1 (1c:e6:2b:8d:c2:c1)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IP (0x0800)
Internet Protocol Version 4, Src: 172.16.1.21 (172.16.1.21), Dst: 224.0.0.251 (224.0.0.251)
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes
    Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00: Not-ECT (Not ECN-Capable Transport))
        0000 00.. = Differentiated Services Codepoint: Default (0x00)
        .... ..00 = Explicit Congestion Notification: Not-ECT (Not ECN-Capable Transport) (0x00)
    Total Length: 243
    Identification: 0x9311 (37649)
    Flags: 0x00
        0... .... = Reserved bit: Not set
        .0.. .... = Don't fragment: Not set
        ..0. .... = More fragments: Not set
    Fragment offset: 0
    Time to live: 255
    Protocol: UDP (17)
    Header checksum: 0x99c7 [validation disabled]
        [Good: False]
        [Bad: False]
    Source: 172.16.1.21 (172.16.1.21)
    Destination: 224.0.0.251 (224.0.0.251)
    [Source GeoIP: Unknown]
    [Destination GeoIP: Unknown]
User Datagram Protocol, Src Port: 5353 (5353), Dst Port: 5353 (5353)
    Source Port: 5353 (5353)
    Destination Port: 5353 (5353)
    Length: 223
    Checksum: 0x62ae [validation disabled]
        [Good Checksum: False]
        [Bad Checksum: False]
    [Stream index: 0]
Domain Name System (response)
    Transaction ID: 0x0000
    Flags: 0x8400 Standard query response, No error
        1... .... .... .... = Response: Message is a response
        .000 0... .... .... = Opcode: Standard query (0)
        .... .1.. .... .... = Authoritative: Server is an authority for domain
        .... ..0. .... .... = Truncated: Message is not truncated
        .... ...0 .... .... = Recursion desired: Don't do query recursively
        .... .... 0... .... = Recursion available: Server can't do recursive queries
        .... .... .0.. .... = Z: reserved (0)
        .... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
        .... .... ...0 .... = Non-authenticated data: Unacceptable
        .... .... .... 0000 = Reply code: No error (0)
    Questions: 0
    Answer RRs: 3
    Authority RRs: 0
    Additional RRs: 4
    Answers
        _practiscoresync._tcp.local: type PTR, class IN, JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
            Name: _practiscoresync._tcp.local
            Type: PTR (domain name PoinTeR) (12)
            .000 0000 0000 0001 = Class: IN (0x0001)
            0... .... .... .... = Cache flush: False
            Time to live: 4500
            Data length: 33
            Domain Name: JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type TXT, class IN, cache flush
            Name: JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
            Type: TXT (Text strings) (16)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 4500
            Data length: 1
            TXT Length: 0
            TXT:
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type SRV, class IN, cache flush, priority 0, weight 0, port 59613, target JCWs-iPad-Mini-White.local
            Service: JCW's iPad Mini White:315c54cc
            Protocol: _practiscoresync
            Name: _tcp.local
            Type: SRV (Server Selection) (33)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 4500
            Data length: 29
            Priority: 0
            Weight: 0
            Port: 59613
            Target: JCWs-iPad-Mini-White.local
    Additional records
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type TXT, class IN, cache flush
             Name: JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local
             Type: TXT (Text strings) (16)
             .000 0000 0000 0001 = Class: IN (0x0001)
             1... .... .... .... = Cache flush: True
             Time to live: 4500
             Data length: 1
             TXT Length: 0
             TXT:
        JCW's iPad Mini White:315c54cc._practiscoresync._tcp.local: type SRV, class IN, cache flush, priority 0, weight 0, port 59613, target JCWs-iPad-Mini-White.local
            Service: JCW's iPad Mini White:315c54cc
            Protocol: _practiscoresync
            Name: _tcp.local
            Type: SRV (Server Selection) (33)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 4500
            Data length: 8
            Priority: 0
            Weight: 0
            Port: 59613
            Target: JCWs-iPad-Mini-White.local
        JCWs-iPad-Mini-White.local: type A, class IN, cache flush, addr 172.16.1.21
            Name: JCWs-iPad-Mini-White.local
            Type: A (Host Address) (1)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 120
            Data length: 4
            Address: 172.16.1.21 (172.16.1.21)
        JCWs-iPad-Mini-White.local: type AAAA, class IN, cache flush, addr fe80::86a:dfa9:6f2f:2303
            Name: JCWs-iPad-Mini-White.local
            Type: AAAA (IPv6 Address) (28)
            .000 0000 0000 0001 = Class: IN (0x0001)
            1... .... .... .... = Cache flush: True
            Time to live: 120
            Data length: 16
            AAAA Address: fe80::86a:dfa9:6f2f:2303 (fe80::86a:dfa9:6f2f:2303)

0000  01 00 5e 00 00 fb 1c e6  2b 8d c2 c1 08 00 45 00   ..^..... +.....E.
0010  00 f3 93 11 00 00 ff 11  99 c7 ac 10 01 15 e0 00   ........ ........
0020  00 fb 14 e9 14 e9 00 df  62 ae 00 00 84 00 00 00   ........ b.......
0030  00 03 00 00 00 04 10 5f  70 72 61 63 74 69 73 63   ......._ practisc
0040  6f 72 65 73 79 6e 63 04  5f 74 63 70 05 6c 6f 63   oresync. _tcp.loc
0050  61 6c 00 00 0c 00 01 00  00 11 94 00 21 1e 4a 43   al...... ....!.JC
0060  57 27 73 20 69 50 61 64  20 4d 69 6e 69 20 57 68   W's iPad  Mini Wh
0070  69 74 65 3a 33 31 35 63  35 34 63 63 c0 0c c0 33   ite:315c 54cc...3
0080  00 10 80 01 00 00 11 94  00 01 00 c0 33 00 21 80   ........ ....3.!.
0090  01 00 00 11 94 00 1d 00  00 00 00 e8 dd 14 4a 43   ........ ......JC
00a0  57 73 2d 69 50 61 64 2d  4d 69 6e 69 2d 57 68 69   Ws-iPad- Mini-Whi
00b0  74 65 c0 22 c0 33 00 10  80 01 00 00 11 94 00 01   te.".3.. ........
00c0  00 c0 33 00 21 80 01 00  00 11 94 00 08 00 00 00   ..3.!... ........
00d0  00 e8 dd c0 73 c0 73 00  01 80 01 00 00 00 78 00   ....s.s. ......x.
00e0  04 ac 10 01 15 c0 73 00  1c 80 01 00 00 00 78 00   ......s. ......x.
00f0  10 fe 80 00 00 00 00 00  00 08 6a df a9 6f 2f 23   ........ ..j..o/#
0100  03                                                 .                
agnat commented 9 years ago

Thanks for the report.

Thing is: There really is not much parsing going on. Here is the stop condition. It looks like Mac OS thinks we're done... :-/

Just to make sure it is not on our end: Could you downgrade to mdns v2.2.1 for a second and see if that works better?

Another thing that helped in the past with odd Mac OS issues was disabling IPv6 support:

networksetup -setv6off wi-fi 
networksetup -setv6off Ethernet

Taken from #51 (which is unrelated).

HTH

jcwren commented 9 years ago

Here's what I found so far. In lib/resolver_sequence_tasks.js, DNSServiceGetAddrInfo(), sets family_flags=0. The docs at https://developer.apple.com/library/IOs/documentation/Networking/Reference/DNSServiceDiscovery_CRef/index.html#//apple_ref/c/func/DNSServiceGetAddrInfo say that passing in 0 applies an algorithm.

I decided to try and force IPv4 by setting family_flags=dns_sd.kDNSServiceProtocol_IPv4. This failed:

events.js:72 throw er; // Unhandled 'error' event ^ TypeError: argument 4 must be an integer (DNSServiceProtocol) at Array.DNSServiceGetAddrInfo as 1 at next (/Users/jcw/Practiscore/pme/node_modules/mdns/lib/browser.js:97:21) at MDNSService.on_resolver_done (/Users/jcw/Practiscore/pme/node_modules/mdns/lib/resolver_sequence_tasks.js:33:11) at SocketWatcher.MDNSService.self.watcher.callback (/Users/jcw/Practiscore/pme/node_modules/mdns/lib/mdns_service.js:17:38)

Added a couple console.logs() to display dns_sd.kDNSServiceProtocol_IPv4 and dns_sd.kDNSServiceProtocol_IPv6, and they're undefined.

Looked through src/dns_sd.cpp, and I see a lot of NODE_DEFINE_CONSTANTS, but none for those two variables.

If I hardcode family_flags=1, everything starts working again. If I set it to 3, it apparently behaves just as it does when it's 0. Disabling IPv6 via the networksetup commands had no impact. I don't mind forcing IPv4 for my app. The application running on the iPads is not yet IPv6 aware, so that won't cause any problems in the short term.

agnat commented 9 years ago

Thanks for putting in the work. One result seems to be: We're missing kDNSServiceProtocol_IPv4 and kDNSServiceProtocol_IPv6. I'll look into adding them. Unfortunately this is not as easy as it looks, because of ... well... long story.

jcwren commented 9 years ago

Any chance you could tell us a long story? :)

This is a real PITA on an OSX system. Not all apps are yet IPv6 aware, and not being able to detect said IPv4-only app comes online is very troublesome.

For whatever reason, BonjourBrowser always finds both. The author of BonjourBrowser had this to say: "There's significant discussion about the "interface index" parameter passed to DNSServiceResolve() and DNSServiceQueryRecord(). Along with a reference to if_nametoindex(3), they also discuss in "Constants for specifying an interface index" how Any doesn't include P2P, and that additional resolution may appear as an ADD.

I avoid the C interface because it's rarely necessary, and you might have an easier time writing a wrapper over NSNetService. Otherwise, the source has plenty of examples http://www.opensource.apple.com/source/mDNSResponder/mDNSResponder-561.1.1, including the dns-sd utility's implementation."

agnat commented 9 years ago

Any chance you could tell us a long story? :)

Short version of a long story is: Since node switched the build system from waf to gyp a few years back add-ons lack a configure phase, There simply is no way to detect the presence of said constants.

IMHO, the best cause of action is to hardcode the constant on your end like you did. I really can not see why this is a PITA...

Not all apps are yet IPv6 aware

Well... it's 2015...

jcwren commented 9 years ago

Sorry, the PITA part is not getting IPv4 addresses.

I can go the hard-coding route. But if the build system can't detect the constants, could node_mdns determine which OS it's running on when the module loads, then make a best guess for what the constants should be? Or maybe throw an error if they're both zero?

agnat commented 9 years ago

Nope. Wild-ass guessing based on the OS is exactly what library code should not do. It would break on older Mac OS versions and for anybody using mDNSResponder on Linux.

So, the OS test should go in your code...