-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
113 lines (102 loc) · 4.29 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
{-# LANGUAGE DoAndIfThenElse #-}
import Control.Monad (forever,forM,guard,void)
import Control.Concurrent (forkIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Char8 as C
import Data.Binary.Get (getWord32host,runGet)
import Data.Char (isAscii)
import Data.Functor ((<$>))
import Data.IP (IPv4,toHostAddress)
import Data.Maybe (catMaybes)
import Debug.Trace (traceShow)
import Network.DNS
import Network.Multicast (addMembership)
import Network.Socket hiding (recv,sendTo)
import Network.Socket.ByteString(recv,sendTo)
import System.Environment (getArgs)
import System.Exit (exitFailure)
-- | The port used for MDNS requests/respones
mdnsPort :: PortNumber
mdnsPort = 5353
-- | The multicast IP address used for MDNS responses
mdnsIp :: IPv4
mdnsIp = read "224.0.0.251"
-- | The SockAddr used for MDNS response
mdnsAddr :: SockAddr
mdnsAddr = SockAddrInet mdnsPort (toHostAddress mdnsIp)
-- | The maximum size of UDP DNS message defined in RFC-1035
maxDNSMsgSize :: Int
maxDNSMsgSize = 512
-- | Convert a String with only ascii characters to Domain
toDomain :: String -> Domain
toDomain = C.pack
-- | Convert strict ByteString to lazy ByteString
bsFromStrict :: B.ByteString -> BL.ByteString
bsFromStrict = BL.pack . B.unpack
-- | Convert lazy ByteString to strict ByteString
bsFromLazy :: BL.ByteString -> B.ByteString
bsFromLazy = B.concat . BL.toChunks
-- | Create a MDNS response
responseMDNS :: DNSMessage -- ^ The original MDNS request
-> [ResourceRecord] -- ^ The answers to response
-> DNSMessage -- ^ The result MDNS response
responseMDNS req answers = DNSMessage h [] [ a { rrttl = 120 } | a <- answers] [] []
where
h = DNSHeader { identifier = identifier (header req)
, flags = (flags $ header req) {qOrR = QR_Response}
}
-- | Query DNS for a list of qustions
lookupDNS :: Resolver -- ^ The resolver to lookup with
-> [Question] -- ^ The list of questions to look up
-> IO [ResourceRecord] -- ^ The answers
lookupDNS resolver questions = concat <$> forM questions lookup'
where
lookup' :: Question -> IO [ResourceRecord]
-- returns [] if no results found
lookup' q = either (const []) answer <$> lookupRaw resolver (qname q) (qtype q)
-- | Proxy MDNS queries for domains ending with the given suffixes.
proxyForSuffixes :: [Domain] -> IO ()
proxyForSuffixes suffixes = withSocketsDo $ do
seed <- makeResolvSeed defaultResolvConf
sock <- socket AF_INET Datagram defaultProtocol
-- We should work properly when other MDNS server(e.g. avahi-daemon) is
-- running, so we need to set ReuseAddr socket option.
setSocketOption sock ReuseAddr 1
bind sock serverAddr
addMembership sock (show mdnsIp) Nothing
forever $ tryReceivingMsg sock seed
where
serverAddr = SockAddrInet mdnsPort 0
tryReceivingMsg sock seed = do
bytes <- recv sock maxDNSMsgSize
case decode (bsFromStrict bytes) of
Left err -> putStrLn $ "received a invalid message:" ++ err
Right msg' -> processMsg sock seed msg'
processMsg sock seed msg = proxyIt
where
proxyIt
| notRequest || null questionToUs = return ()
| otherwise = do
putStrLn $ "will handle:" ++ show questionToUs
void $ forkIO $ withResolver seed $ \resolver -> do
answers <- lookupDNS resolver questionToUs
let rsp = responseMDNS msg answers
void $ sendTo sock (msgToByteString rsp) mdnsAddr
questionToUs = [ q | q <- question msg
, qtype q == A
, any (`C.isSuffixOf` qname q) suffixes]
notRequest = qOrR (flags $ header msg) /= QR_Query
-- encode the response and then convert it to strict ByteString from a
-- lazy one.
msgToByteString = bsFromLazy . encode
main = do
suffixes <- getArgs
if all (all isAscii) suffixes
then proxyForSuffixes $ map (toDomain . fixSuffix) suffixes
else putStrLn "Only supports domain names in ascii!!" >> exitFailure
where
-- names in DNS questions should end in "."
fixSuffix suffix
| last suffix == '.' = suffix
| otherwise = suffix ++ "."