mDNSResponder-client-1.0.0: Network/DNS/MDNSResponder/Client.hsc
{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, RecordWildCards #-}
{-# LANGUAGE ExistentialQuantification, Rank2Types #-}
{-|
Module : Network.DNS.MDNSResponder.Client
Description : Library for talking to the mDNSResponder daemon.
Copyright : (c) 2016, Obsidian Systems LLC
License: BSD3
Maintainer : shea@shealevy.com
Stability : experimental
-}
module Network.DNS.MDNSResponder.Client
( -- * Managing connections
Connection
, connect
, disconnect
, defaultAddr
, AsyncConnectionError (..)
, AsyncConnectionErrorHandler
-- * Data types
, NullFreeByteString
-- ** Flags
, DNSServiceFlags
-- ** Error codes
, DNSServiceErrorType
, kDNSServiceErr_NoError
, kDNSServiceErr_ServiceNotRunning
, kDNSServiceErr_ShortResponse
-- ** Interface indices
, InterfaceIndex
, kDNSServiceInterfaceIndexAny
, kDNSServiceInterfaceIndexLocalOnly
-- * Requests
, Request (..)
, request
-- * Responses
, AsyncResponseHandler
, ResponseHeader (..)
, Response (..)
, NTDResponse (..)
, ResolveResponse (..)
) where
#include "dnssd_ipc.h"
#include <sys/socket.h>
import Data.Word
import Data.Int
import Data.Bits
import Data.IORef
import Data.Typeable
import System.Environment
import Control.Exception
import Control.Concurrent
import Control.Monad
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import Foreign.Storable
import Foreign.C.Types
import Foreign.C.String
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class
import Data.ByteString as BS
import Data.ByteString.Unsafe
import Data.Endian
import qualified Control.Concurrent.Map as CM
import qualified Network.Socket as S
import Network.Socket.Msg
-- | Flag type for API calls.
newtype DNSServiceFlags =
DNSServiceFlags #{type DNSServiceFlags} deriving (Eq, Bits)
instance Monoid DNSServiceFlags where
mempty = DNSServiceFlags 0
mappend = (.|.)
-- | An index to specify on which interface a service exists.
newtype InterfaceIndex = InterfaceIndex Word32
-- | The service is served on any interface.
kDNSServiceInterfaceIndexAny :: InterfaceIndex
kDNSServiceInterfaceIndexAny =
InterfaceIndex #{const kDNSServiceInterfaceIndexAny}
-- | The service is only served to the local host.
kDNSServiceInterfaceIndexLocalOnly :: InterfaceIndex
kDNSServiceInterfaceIndexLocalOnly =
InterfaceIndex #{const kDNSServiceInterfaceIndexLocalOnly}
-- | Error codes returned by the daemon.
newtype DNSServiceErrorType =
DNSServiceErrorType #{type DNSServiceErrorType}
deriving (Eq, Show)
-- | There was no error.
kDNSServiceErr_NoError :: DNSServiceErrorType
kDNSServiceErr_NoError =
DNSServiceErrorType #{const kDNSServiceErr_NoError}
-- | The daemon is not running/closed the connection.
--
-- You should still call 'disconnect' to clean up resources!
kDNSServiceErr_ServiceNotRunning :: DNSServiceErrorType
kDNSServiceErr_ServiceNotRunning =
DNSServiceErrorType (#{const kDNSServiceErr_ServiceNotRunning})
-- | The response sent by the daemon was too short.
--
-- This is not an upstream error code, the official Apple client just
-- declines to call the callback in this case. It is outside of the
-- documented range for mDNS error codes.
kDNSServiceErr_ShortResponse :: DNSServiceErrorType
kDNSServiceErr_ShortResponse = DNSServiceErrorType 1
-- | A 'ByteString' with no null characters.
--
-- This invariant must be maintained by the caller.
type NullFreeByteString = ByteString
-- | The shared header for daemon responses.
--
-- The header also contains an error code on the wire, but it's always
-- 'kDNSServiceErr_NoError' on code paths that get passed a header.
data ResponseHeader = ResponseHeader
{ reshdr_flags :: !DNSServiceFlags -- ^ Flags applicable to the
-- response.
, reshdr_ifi :: !InterfaceIndex -- ^ The interface in question.
}
-- | A response with its header.
data Response a = Response !ResponseHeader !a
-- | A response containing a name, registration type, and domain.
data NTDResponse = NTDResponse
{ ntd_name :: !NullFreeByteString -- ^ The service name.
, ntd_regtype :: !NullFreeByteString -- ^ The service registration
-- type.
, ntd_domain :: !NullFreeByteString -- ^ The domain on which the
-- service is registered.
}
-- | A response to a 'ServiceResolve' request.
data ResolveResponse = ResolveResponse
{ resolve_fullname :: !NullFreeByteString -- ^ The full service
-- domain name.
, resolve_hosttarget :: !NullFreeByteString -- ^ The hostname of the
-- machine providing
-- the service.
, resolve_port :: !S.PortNumber -- ^ The port number the service is
-- served on.
, resolve_txt :: !ByteString -- ^ The primary TXT record for the
-- service.
}
-- | A request to the mDNSResponder daemon.
--
-- Parameterized by the type of the response.
--
-- Constructor fields documented with the constructor due to
-- https://ghc.haskell.org/trac/ghc/ticket/12050
--
-- See also the documentation for these requests in dns_sd.h
data Request a where
-- | Register a service.
--
-- Fields:
--
-- 1. flags: Indicate behavior on name conflict.
-- 2. index: The interface(s) on which to register the service.
-- 3. name: The service name to be registered, or 'empty' for the
-- default.
-- 4. type: The service type followed by the protocol, separated by
-- a dot.
-- 5. domain: The domain on which to advertise, or 'empty' for the
-- default domains.
-- 6. host: The SRV target host name, or 'empty' for the default
-- host name(s).
-- 7. port: The port the service listens on.
-- 8. txt: The text record data, or 'empty' for none.
ServiceRegister :: !DNSServiceFlags
-> !InterfaceIndex
-> !NullFreeByteString
-> !NullFreeByteString
-> !NullFreeByteString
-> !NullFreeByteString
-> !S.PortNumber
-> !ByteString
-> Request NTDResponse
-- | Browse for a service.
--
-- Fields:
--
-- 1. index: The interface(s) on which to browse.
-- 2. regtype: The service type being browsed for followed by the
-- protocol, with optional subtypes or group IDs.
-- 3. domain: The domain on which to browse, or 'empty' for the
-- default.
ServiceBrowse :: !InterfaceIndex
-> !NullFreeByteString
-> !NullFreeByteString
-> Request NTDResponse
-- | Resolve a service.
--
-- You probably want to populate the name, regtype, and domain from
-- the results of a 'ServiceBrowse' request.
--
-- Fields:
--
-- 1. flags: Specify multicast behavior.
-- 2. index: The interface(s) on which to resolve.
-- 3. name: The name of the service to be resolved.
-- 4. regtype: The type of the service to be resolved.
-- 5. domain: The domain of the service to be resolved.
ServiceResolve :: !DNSServiceFlags
-> !InterfaceIndex
-> !NullFreeByteString
-> !NullFreeByteString
-> !NullFreeByteString
-> Request ResolveResponse
-- | A connection to the daemon.
data Connection = Connection
{ sock :: !S.Socket
, counter :: !(IORef Word64)
, requestQueue :: !(Chan AnyRequestRegistration)
, responseMap :: !(CM.Map Word64 AnyAsyncResponseHandler)
, recvThreadId :: !ThreadId
, sendThreadId :: !ThreadId
}
-- | An error communicating with the daemon.
data AsyncConnectionError
= AsyncConnectionIOError !IOError -- ^ An 'IOError' occured while
-- using the socket.
| AsyncConnectionClosedError -- ^ The daemon closed the socket
-- unexpectedly.
| AsyncConnectionBadDaemonVersionError !Word32 -- ^ The daemon
-- reported a version
-- we can't handle.
deriving (Show, Typeable)
instance Exception AsyncConnectionError
-- | Handle a generic error communicating with the daemon.
--
-- These errors may occur at any time, are not associated with any
-- particular request, and are not generally recoverable. You should
-- still call 'disconnect' to clean up resources after recieving an
-- error.
--
-- The handler is called in its own thread.
type AsyncConnectionErrorHandler = AsyncConnectionError -> IO ()
-- | Connect to the daemon.
connect :: S.SockAddr -- ^ The address of the daemon.
--
-- You probably want 'defaultAddr'. In any case,
-- the implementation currently only works for
-- AF_UNIX-based daemons, even though there are
-- AF_INET-based daemons in existence.
-> AsyncConnectionErrorHandler
-> IO (Either DNSServiceErrorType Connection)
connect addr e_handler = bracketOnError makeSocket S.close $ \s -> do
S.connect s addr
allocaBytes ipcMsgHdrSz $ \hdr -> do
pokeHdr (IpcMsgHdr 0 #{const connection_request} 0) hdr
sendAll s (castPtr hdr) ipcMsgHdrSz
err <- recvError s
case err of
DNSServiceErrorType #{const kDNSServiceErr_NoError} -> do
chan <- newChan
handlers <- CM.empty
sTidVar <- newEmptyMVar
rTidVar <- newEmptyMVar
counter' <- newIORef 0
bracketOnError
(createSendThread s chan sTidVar rTidVar)
killThread $ \sTid -> do
bracketOnError
(createRecvThread s handlers sTidVar rTidVar)
killThread $ \rTid -> do
return . Right $
Connection s counter' chan handlers rTid sTid
_ -> return $ Left err
where
makeSocket = S.socket S.AF_UNIX S.Stream S.defaultProtocol
createSendThread s chan sTidVar rTidVar =
mask_ $ forkIOWithUnmask $
sendThread s chan e_handler sTidVar rTidVar
createRecvThread s handlers sTidVar rTidVar =
mask_ $ forkIOWithUnmask $
recvThread s handlers e_handler sTidVar rTidVar
-- | The default address for the daemon.
defaultAddr :: IO S.SockAddr
defaultAddr = do
m_def <- lookupEnv #{const_str MDNS_UDS_SERVERPATH_ENVVAR}
let p = case m_def of
Just p' -> p'
Nothing -> #{const_str MDNS_UDS_SERVERPATH}
return $ S.SockAddrUnix p
-- | Disconnect from the daemon.
--
-- It is an error to use the passed in 'Connection' during or after
-- this call, though it is safe to call this again if it fails due
-- to an asynchronous exception.
disconnect :: Connection
-> IO ()
disconnect (Connection {..}) = do
killThread recvThreadId
killThread sendThreadId
S.close sock
-- | Handle asynchronous responses to a request.
--
-- Some requests result in multiple responses.
--
-- The handler is called on its own thread.
type AsyncResponseHandler a = Either DNSServiceErrorType (Response a)
-> IO ()
recvError :: S.Socket -> IO DNSServiceErrorType
recvError s = alloca $ \buf -> do
res <- recvAll s (castPtr buf) #{size DNSServiceErrorType}
case res of
RecvAllOK -> DNSServiceErrorType . fromBigEndian <$> peek buf
RecvAllClosed -> return kDNSServiceErr_ServiceNotRunning
-- | Send a request to the daemon.
--
-- The 'PeekableResponse' constraint is an implementation detail, all
-- 'Request's have a type appropriate for 'request'. Unfortunately,
-- it doesn't seem possible to hide this constraint from the haddock
-- docs.
request :: PeekableResponse a
=> Connection
-> Request a
-> AsyncResponseHandler a
-> IO DNSServiceErrorType -- ^ The immediate error from the
-- daemon, if any. Even if
-- 'kDNSServiceErr_NoError' is
-- returned here, there still may be
-- asynchronous errors for this
-- request.
request (Connection {..}) req handler =
bracket makeSocks closeSocks $ \(us, them) -> do
ctx <- atomicModifyIORef' counter (\x -> (x + 1, x + 1))
CM.insert ctx (AnyAsyncResponseHandler handler) responseMap
writeChan requestQueue (AnyRequestRegistration ctx req them)
recvError us
where
makeSocks = S.socketPair S.AF_UNIX S.Stream S.defaultProtocol
closeSocks (us, them) = S.close us >> S.close them
data AnyAsyncResponseHandler = forall a . PeekableResponse a =>
AnyAsyncResponseHandler !(AsyncResponseHandler a)
data AnyRequestRegistration =
forall a . AnyRequestRegistration !Word64 !(Request a) !S.Socket
data IpcMsgHdr = IpcMsgHdr
{ datalen :: !Word32
, op :: !Word32
, context :: !Word64
}
ipcMsgHdrSz :: Int
ipcMsgHdrSz = #{size ipc_msg_hdr}
pokeHdr :: IpcMsgHdr
-> (Ptr IpcMsgHdr)
-> IO ()
pokeHdr (IpcMsgHdr {..}) hdr = do
#{poke ipc_msg_hdr, version} hdr $
toBigEndian (#{const VERSION} :: Word32)
#{poke ipc_msg_hdr, datalen} hdr $ toBigEndian datalen
#{poke ipc_msg_hdr, ipc_flags} hdr (0 :: Word32)
#{poke ipc_msg_hdr, op} hdr $ toBigEndian op
#{poke ipc_msg_hdr, client_context} hdr context
#{poke ipc_msg_hdr, reg_index} hdr (0 :: Word32)
peekHdr :: Ptr IpcMsgHdr -> IO IpcMsgHdr
peekHdr hdr = do
ver <-
(fromBigEndian <$> #{peek ipc_msg_hdr, version} hdr) :: IO Word32
case ver of
#{const VERSION} -> do
datalen <- fromBigEndian <$> #{peek ipc_msg_hdr, datalen} hdr
op <- fromBigEndian <$> #{peek ipc_msg_hdr, op} hdr
context <- #{peek ipc_msg_hdr, client_context} hdr
return $ IpcMsgHdr datalen op context
_ -> throwIO $ AsyncConnectionBadDaemonVersionError ver
size :: Request a -> Int
size (ServiceRegister _ _ name ty domain host _ txt) =
#{size DNSServiceFlags} +
4 + -- InterfaceIndex, Word32
(BS.length name) + 1 +
(BS.length ty) + 1 +
(BS.length domain) + 1 +
(BS.length host) + 1 +
2 + -- Port
2 + -- txtLen
(BS.length txt)
size (ServiceBrowse _ ty domain) =
#{size DNSServiceFlags} +
4 + -- InterfaceIndex, Word32
(BS.length ty) + 1 +
(BS.length domain) + 1
size (ServiceResolve _ _ name regtype domain) =
#{size DNSServiceFlags} +
4 + -- InterfaceIndex, Word32
(BS.length name) + 1 +
(BS.length regtype) + 1 +
(BS.length domain) + 1
operation :: Request a -> Word32
operation (ServiceRegister _ _ _ _ _ _ _ _) =
#{const reg_service_request}
operation (ServiceBrowse _ _ _) = #{const browse_request}
operation (ServiceResolve _ _ _ _ _) = #{const resolve_request}
type Poke =
(Ptr Word8 -> IO (), Int)
runPokes :: Ptr Word8 -> [ Poke ] -> IO ()
runPokes _ [] = return ()
runPokes p ((io, sz) : pokes) = do
io p
runPokes (plusPtr p sz) pokes
pokeBSNull :: Int -> ByteString -> Ptr Word8 -> IO ()
pokeBSNull sz bs ptr = do
pokeBS sz bs ptr
poke (plusPtr ptr sz) (0 :: Word8)
pokeBS :: Int -> ByteString -> Ptr Word8 -> IO ()
pokeBS sz bs ptr = unsafeUseAsCString bs $ \buf -> do
copyBytes ptr (castPtr buf) sz
pokeBody :: Request a -> Ptr (Request a) -> IO ()
pokeBody (ServiceRegister
(DNSServiceFlags flags)
(InterfaceIndex ifi)
name
ty
domain
host
port
txt
) ptr = runPokes (castPtr ptr)
[ (flip poke (toBigEndian flags) . castPtr, 4)
, (flip poke (toBigEndian ifi) . castPtr, 4)
, (pokeBSNull name_sz name, name_sz + 1)
, (pokeBSNull ty_sz ty, ty_sz + 1)
, (pokeBSNull domain_sz domain, domain_sz + 1)
, (pokeBSNull host_sz host, host_sz + 1)
, (flip poke port . castPtr, 2)
, (flip poke (toBigEndian txtln) . castPtr, 2)
, (pokeBS txt_sz txt, txt_sz)
]
where
name_sz = BS.length name
ty_sz = BS.length ty
domain_sz = BS.length domain
host_sz = BS.length host
txtln :: Word16
txtln = fromIntegral txt_sz
txt_sz = BS.length txt
pokeBody (ServiceBrowse (InterfaceIndex ifi) ty domain) ptr =
runPokes (castPtr ptr)
[ (flip poke (0 :: Word32) . castPtr, 4)
, (flip poke (toBigEndian ifi) . castPtr, 4)
, (pokeBSNull ty_sz ty, ty_sz + 1)
, (pokeBSNull domain_sz domain, domain_sz + 1)
]
where
ty_sz = BS.length ty
domain_sz = BS.length domain
pokeBody (ServiceResolve
(DNSServiceFlags flags)
(InterfaceIndex ifi)
name
ty
domain
) ptr = runPokes (castPtr ptr)
[ (flip poke (toBigEndian flags) . castPtr, 4)
, (flip poke (toBigEndian ifi) . castPtr, 4)
, (pokeBSNull name_sz name, name_sz + 1)
, (pokeBSNull ty_sz ty, ty_sz + 1)
, (pokeBSNull domain_sz domain, domain_sz + 1)
]
where
name_sz = BS.length name
ty_sz = BS.length ty
domain_sz = BS.length domain
data RecvAllResult = RecvAllOK | RecvAllClosed
recvAll :: S.Socket -> Ptr Word8 -> Int -> IO RecvAllResult
recvAll s = loop
where
loop ptr i = do
cnt <- S.recvBuf s ptr i
if cnt == i
then return RecvAllOK
else if cnt == 0
then return RecvAllClosed
else loop (plusPtr ptr cnt) (i - cnt)
sendAll :: S.Socket -> Ptr Word8 -> Int -> IO ()
sendAll s = loop
where
loop ptr' i' = do
cnt <- S.sendBuf s ptr' i'
if cnt == i'
then return ()
else loop (plusPtr ptr' cnt) (i' - cnt)
data SockEx = SockEx deriving (Show, Typeable)
instance Exception SockEx
sendThread :: S.Socket
-> Chan AnyRequestRegistration
-> AsyncConnectionErrorHandler
-> MVar ThreadId
-> MVar ThreadId
-> (forall a. IO a -> IO a)
-> IO ()
sendThread sock chan e_handler sTidVar rTidVar unmask = do
_ <- (try :: IO () -> IO (Either SockEx ())) . unmask $ do
myThreadId >>= putMVar sTidVar
Left e <- (try loop) :: IO (Either IOError ())
takeMVar rTidVar >>= flip throwTo SockEx
_ <- forkIO . e_handler $ AsyncConnectionIOError e
drain
unmask drain
where
loop = do
(AnyRequestRegistration ctx req them) <- readChan chan
let sz = size req
full_sz = ipcMsgHdrSz + sz
op = operation req
allocaBytes (full_sz + 1) $ \reqptr -> do
pokeHdr (IpcMsgHdr (fromIntegral $ sz + 1) op ctx) reqptr
poke (plusPtr reqptr ipcMsgHdrSz) (0 :: CChar)
pokeBody req $ (plusPtr reqptr (ipcMsgHdrSz + 1))
sendAll sock (castPtr reqptr) full_sz
alloca $ \cmsgptr -> do
poke cmsgptr (S.fdSocket them)
cmsg <- unsafePackCStringLen (castPtr cmsgptr, #{size int})
body <- unsafePackCStringLen ((plusPtr reqptr full_sz), 1)
sendMsg sock body (S.SockAddrUnix "")
[ CMsg #{const SOL_SOCKET} #{const SCM_RIGHTS} cmsg ]
S.close them
loop
drain = do
(AnyRequestRegistration _ _ them) <- readChan chan
S.close them
drain
class PeekableResponse a where
peekResponseBody :: Ptr a -> Int -> IO (Maybe a)
instance PeekableResponse NTDResponse where
peekResponseBody buf sz = runMaybeT $ do
name_null <- findNull (castPtr buf) sz
name <- lift $ packCStringLen (castPtr buf, name_null)
let buf' = plusPtr buf (name_null + 1)
sz' = sz - (name_null + 1)
regtype_null <- findNull buf' sz'
regtype <- lift $ packCStringLen (buf', regtype_null)
let buf'' = plusPtr buf' (regtype_null + 1)
sz'' = sz' - (regtype_null + 1)
domain_null <- findNull buf'' sz''
domain <- lift $ packCStringLen (buf'', domain_null)
return $ NTDResponse name regtype domain
instance PeekableResponse ResolveResponse where
peekResponseBody buf sz = runMaybeT $ do
name_null <- findNull (castPtr buf) sz
name <- lift $ packCStringLen (castPtr buf, name_null)
let buf' = plusPtr buf (name_null + 1)
sz' = sz - (name_null + 1)
target_null <- findNull buf' sz'
target <- lift $ packCStringLen (buf', target_null)
let buf'' = plusPtr buf' (target_null + 1)
sz'' = sz' - (target_null + 1)
port_sz = sizeOf (undefined :: S.PortNumber)
when (sz'' < port_sz) mzero
port <- lift $ peek buf''
let buf''' = plusPtr buf'' port_sz
sz''' = sz'' - port_sz
len_sz = 2 -- uint16
when (sz''' < len_sz) mzero
len <- (lift $ fromBigEndian <$> peek buf''') :: MaybeT IO Word16
let buf'''' = plusPtr buf''' len_sz
sz'''' = sz''' - len_sz
len' = fromIntegral len
when (sz'''' < len') mzero
txt <- lift $ packCStringLen (buf'''', len')
return $ ResolveResponse name target port txt
findNull :: CString -> Int -> MaybeT IO Int
findNull = go 0
where
go _ _ 0 = mzero
go acc ptr n = do
c <- lift $ peek ptr :: MaybeT IO CChar
case c of
0 -> return acc
_ -> go (acc + 1) (plusPtr ptr 1) (n - 1)
-- TODO: We should buffer reads here.
recvThread :: S.Socket
-> CM.Map Word64 AnyAsyncResponseHandler
-> AsyncConnectionErrorHandler
-> MVar ThreadId
-> MVar ThreadId
-> (forall a. IO a -> IO a)
-> IO ()
recvThread sock handlers e_handler sTidVar rTidVar unmask = do
_ <- (try :: IO () -> IO (Either SockEx ())) $ do
myThreadId >>= putMVar rTidVar
err <- try $ do
Left ex <- try $ unmask loop
return ex
readMVar sTidVar >>= flip throwTo SockEx
-- After here we can't get a SockEx from the send thread
unmask $ do
_ <- forkIO . e_handler $ case err of
Left e -> AsyncConnectionIOError e
Right e -> e
drain
unmask drain
where
loop = do
hdr <- allocaBytes ipcMsgHdrSz $ \buf -> do
res <- recvAll sock (castPtr buf) ipcMsgHdrSz
case res of
RecvAllClosed -> throwIO AsyncConnectionClosedError
RecvAllOK -> peekHdr buf
m_handler <- CM.lookup (context hdr) handlers
let len = fromIntegral $ datalen hdr
allocaBytes len $ \buf -> do
res <- recvAll sock buf len
case res of
RecvAllClosed -> throwIO AsyncConnectionClosedError
RecvAllOK -> return ()
case m_handler of
Nothing -> return ()
Just (AnyAsyncResponseHandler handler) -> do
e_r_hdr <- peekResponseHeader buf len
case e_r_hdr of
Left err -> void . forkIO . handler $ Left err
Right r_hdr -> do
m_r_body <- peekResponseBody
(plusPtr buf responseHdrSz)
(len - responseHdrSz)
let response = case m_r_body of
Just r_body -> Right $ Response r_hdr r_body
Nothing -> Left $ kDNSServiceErr_ShortResponse
void . forkIO $ handler response
loop
drain = do
entries <- CM.unsafeToList handlers
mapM_ (\(ctx, _) -> CM.delete ctx handlers) entries
-- This is ugly, but this is also an unlikely error path...
threadDelay 5000
drain
responseHdrSz =
#{size DNSServiceFlags} +
4 + -- interface index
#{size DNSServiceErrorType}
peekResponseHeader buf len = if len < responseHdrSz
then return $ Left kDNSServiceErr_ShortResponse
else do
flags <- DNSServiceFlags . fromBigEndian <$> peek (castPtr buf)
ifi <- InterfaceIndex . fromBigEndian <$>
peek (plusPtr buf #{size DNSServiceFlags})
err <- DNSServiceErrorType . fromBigEndian <$>
peek (plusPtr buf (#{size DNSServiceFlags} + 4))
return $ case err of
DNSServiceErrorType #{const kDNSServiceErr_NoError} ->
Right (ResponseHeader flags ifi)
_ -> Left err