pocket-dns-0.1.0: Network/DNS/Pocket/Server.hs
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
{-# LANGUAGE TypeFamilies, EmptyDataDecls, GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
module Network.DNS.Pocket.Server where
import Control.Applicative
import Control.Concurrent
import Control.Monad
import qualified Data.ByteString as S
import Data.ByteString.Lazy hiding (putStrLn, filter, length)
import Data.Default
import Data.IP
import Data.Maybe
import Data.Monoid
import qualified Data.Aeson as A
import qualified Data.Yaml as Y
import qualified Data.HashMap.Strict as H
import Network.BSD
import Network.DNS hiding (lookup)
import Network.Socket hiding (recvFrom)
import Network.Socket.ByteString
import System.Timeout
import Database.Persist.Sqlite (SqliteConf)
import Database.Persist.Zookeeper (ZookeeperConf)
import Network.DNS.Pocket.Type
import Network.DNS.Pocket.Zookeeper ()
import Network.DNS.Pocket.Sqlite ()
type Port = Int
timeout' :: String -> Int -> IO a -> IO (Maybe a)
timeout' msg tm io = do
result <- timeout tm io
maybe (putStrLn msg) (const $ return ()) result
return result
proxyRequest :: DnsConf -> ResolvConf -> DNSFormat -> IO (Maybe DNSFormat)
proxyRequest DnsConf{..} rc req = do
let worker Resolver{..} = do
let packet = mconcat . toChunks $ encode req
sendAll dnsSock packet
receive dnsSock
rs <- makeResolvSeed rc
withResolver rs $ \r ->
(>>= check) <$> timeout' "proxy timeout" timeOut (worker r)
where
ident = identifier . header $ req
check :: DNSFormat -> Maybe DNSFormat
check rsp = let hdr = header rsp
in if identifier hdr == ident
then Just rsp
else Nothing
handleRequest :: DnsBackend c => c -> DnsConf -> Conn c -> ResolvConf -> DNSFormat -> Int -> IO (Maybe DNSFormat)
handleRequest pconf conf conn rc req n = do
mr <- lookupHosts
case mr of
Just _ -> return mr
Nothing -> proxyRequest conf rc req
where
filterA = filter ((==A) . qtype)
ident = identifier . header $ req
lookupHosts :: IO (Maybe DNSFormat)
lookupHosts = do
let mq = listToMaybe . filterA . question $ req
case mq of
Just q -> do
val <- getRecord pconf (qname q) conn
-- print $ q
-- print $ qname q
-- print $ val
let len = length val
if len /= 0
then
case (val !! (n `mod` len)) of
IPv4 ip -> return $ Just $ responseA ident q ip
IPv6 ip -> return $ Just $ responseAAAA ident q ip
else
return Nothing
Nothing -> return Nothing
handlePacket :: DnsBackend c => c -> DnsConf -> Conn c -> Socket -> SockAddr -> S.ByteString -> Int -> IO ()
handlePacket pconf conf@DnsConf{..} conn sock addr bs n = case decode (fromChunks [bs]) of
Right req -> do
let rc = defaultResolvConf { resolvInfo = RCFilePath "/etc/resolv.conf" }
mrsp <- handleRequest pconf conf conn rc req n
case mrsp of
Just rsp ->
let packet = mconcat . toChunks $ encode rsp
in void $ timeout' "send timeout" timeOut (sendAllTo sock packet addr)
Nothing -> return ()
Left msg -> putStrLn msg
runServer' :: DnsBackend c => c -> Port -> IO ()
runServer' conf port = do
withSocketsDo $ do
mconn <- setup conf
case mconn of
Just conn -> do
addrinfos <- getAddrInfo
(Just (defaultHints {addrFlags = [AI_PASSIVE]}))
Nothing (Just "domain")
addrinfo <- maybe (fail "no addr info") return (listToMaybe addrinfos)
addrinfo' <- do
if port == 53
then return addrinfo
else do
let sockaddr = addrAddress addrinfo
let sockaddr' = case sockaddr of
SockAddrInet _ addr -> SockAddrInet (fromIntegral port) addr
SockAddrInet6 _ f h s -> SockAddrInet6 (fromIntegral port) f h s
SockAddrUnix _ -> sockaddr
return $ addrinfo { addrAddress = sockaddr' }
sock <- socket (addrFamily addrinfo') Datagram defaultProtocol
bindSocket sock (addrAddress addrinfo')
loop sock conn def 0
Nothing -> error $ "Setup failure"
where
loop sock conn def' n = do
(bs, addr) <- recvFrom sock (bufSize def')
_ <- forkIO $ handlePacket conf def' conn sock addr bs n
case n of
255 -> loop sock conn def' 0
_ -> loop sock conn def' (n+1)
loadConf :: FilePath -> IO (Maybe Conf)
loadConf yamlConfFile = do
mconf <- Y.decodeFile yamlConfFile :: IO (Maybe Y.Value)
case mconf of
Just (Y.Object obj) -> do
-- let obj' = Y.encode $ Y.Object $ H.delete "backend" obj
case H.lookup "backend" obj of
Just (A.String "zookeeper") -> do
v <- load yamlConfFile :: IO (Maybe ZookeeperConf)
return $ fmap Zookeeper v
Just (A.String "sqlite") -> do
v <- load yamlConfFile :: IO (Maybe SqliteConf)
return $ fmap Sqlite v
_ -> return Nothing
_ -> return Nothing
runServer :: FilePath -> Port -> IO ()
runServer file port = do
mconf <- loadConf file
case mconf of
Just (Zookeeper conf) -> runServer' conf port
Just (Sqlite conf) -> runServer' conf port
Nothing -> error "Can not parse config-file"
setDomain :: FilePath -> Domain -> [IP] -> IO Bool
setDomain file domain ips = do
mconf <- loadConf file
case mconf of
Just (Zookeeper conf) -> setDomain' conf domain ips
Just (Sqlite conf) -> setDomain' conf domain ips
Nothing -> error "Can not parse config-file"
where
setDomain' :: DnsBackend c => c -> Domain -> [IP] -> IO Bool
setDomain' conf domain' ips' = do
withSocketsDo $ do
mconn <- setup conf
case mconn of
Just conn -> setRecord conf domain' ips' conn
Nothing -> error "Setup failure"
getDomain :: FilePath -> Domain -> IO [IP]
getDomain file domain = do
mconf <- loadConf file
case mconf of
Just (Zookeeper conf) -> getDomain' conf domain
Just (Sqlite conf) -> getDomain' conf domain
Nothing -> error "Can not parse config-file"
where
getDomain' :: DnsBackend c => c -> Domain -> IO [IP]
getDomain' conf domain' = do
withSocketsDo $ do
mconn <- setup conf
case mconn of
Just conn -> getRecord conf domain' conn
Nothing -> error "Setup failure"
deleteDomain :: FilePath -> Domain -> IO ()
deleteDomain file domain = do
mconf <- loadConf file
case mconf of
Just (Zookeeper conf) -> deleteDomain' conf domain
Just (Sqlite conf) -> deleteDomain' conf domain
Nothing -> error "Can not parse config-file"
where
deleteDomain' :: DnsBackend c => c -> Domain -> IO ()
deleteDomain' conf domain' = do
withSocketsDo $ do
mconn <- setup conf
case mconn of
Just conn -> deleteRecord conf domain' conn
Nothing -> error "Setup failure"
listDomain :: FilePath -> IO [(Domain,[IP])]
listDomain file = do
mconf <- loadConf file
case mconf of
Just (Zookeeper conf) -> listDomain' conf
Just (Sqlite conf) -> listDomain' conf
Nothing -> error "Can not parse config-file"
where
listDomain' :: DnsBackend c => c -> IO [(Domain,[IP])]
listDomain' conf = do
withSocketsDo $ do
mconn <- setup conf
case mconn of
Just conn -> listRecord conf conn
Nothing -> error "Setup failure"