packages feed

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"