dprox 0.1.1 → 0.1.2
raw patch · 4 files changed
+105/−52 lines, 4 filesdep ~attoparsecdep ~basedep ~bytestring
Dependency ranges changed: attoparsec, base, bytestring, containers, iproute, network, optparse-applicative, streaming-commons, unix, unordered-containers
Files
- README.md +41/−5
- dprox.cabal +22/−22
- src/Config.hs +34/−19
- src/Main.hs +8/−6
README.md view
@@ -7,23 +7,59 @@ [](https://aur.archlinux.org/packages/dprox/) [](https://github.com/bjin/dprox/blob/master/LICENSE) -dprox is a lightweight DNS proxy server. It's written as a drop-in replacement-of dnsmasq to work with [dnsmasq-china-list](https://github.com/felixonmars/dnsmasq-china-list),+`dprox` is a lightweight DNS proxy server. It's created as a drop-in replacement+of [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) to work with+[dnsmasq-china-list](https://github.com/felixonmars/dnsmasq-china-list), while improving the overall lookup performance over large domain list. ### Installation -Only Linux and macOS are supported. [stack](https://docs.haskellstack.org/en/stable/README/#how-to-install) is required to build `dprox`.+`dprox` should build and work on all unix-like OS with [ghc](https://www.haskell.org/ghc/) support, but it's only+been tested on Linux and macOS. +While `dprox` can be built with [cabal](https://www.haskell.org/cabal/) like any other Hackage packages, for a+reliable compilation with pinned dependencies, [stack](https://docs.haskellstack.org/en/stable/README/#how-to-install) is generally recommended.+ ```sh+stack setup stack install ``` +For Arch Linux users, an [AUR package](https://aur.archlinux.org/packages/dprox/) is also provided.+ ### Usage -Only a small subset of dnsmasq options are implemented at the moment, just barely enough to work with `dnsmasq-china-list`.+Only a small subset of dnsmasq options are implemented at the moment, just barely enough to work with `dnsmasq-china-list` and [hosts-blocklists](https://github.com/notracking/hosts-blocklists). -Use `dprox --help` to list those options. A [systemd unit file](https://github.com/bjin/dprox/blob/master/systemd/dprox.service) is also provided for Linux user.+Here is the list of implemented dnsmasq options (with `server`, `local`, `address` and `bogus-nxdomain` options allowed in configuration file):++```+-u, --user=<username>+-p, --port=<port>+-a, --listen-address=<ipaddr>+-C, --conf-file=<file>+-h, --no-hosts+-H, --addn-hosts=<file>+-S, --local, --server=[/<domain>/]<ipaddr>[#<port>]+-A, --address=[/<domain>/]<ipaddr>+-B, --bogus-nxdomain=<ipaddr>+```++Use `dprox --help` or [dnsmasq manpage](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) for further details about these options.+But be aware that there might be minor differences on some options like `--server`.++To use `dprox` with `dnsmasq-china-list`, with "8.8.4.4" as the remote DNS server+(systemd user can also use [this unit file](https://github.com/bjin/dprox/blob/master/systemd/dprox.service)):++```sh+dprox -C /etc/dnsmasq.d/accelerated-domains.china.conf -C /etc/dnsmasq.d/bogus-nxdomain.china.conf -S 8.8.4.4+```++To use `dprox` with `hosts-blocklists` and the default remote DNS server ("8.8.8.8"), without loading system hosts file:++```sh+dprox -C /opt/hosts-blocklists/domains.txt -H /opt/hosts-blocklists/hostnames.txt -h+``` ### Known Issue
dprox.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: d262889761a247d0874e1aea677d066dce6b02883a49661fd85293dd83929b03+-- hash: e6d3163f54b56ac9d5fc87fae25c5446de14a1af8a295aa09b2db319888e28d7 name: dprox-version: 0.1.1+version: 0.1.2 synopsis: a lightweight DNS proxy server description: Please see the README on GitHub at <https://github.com/bjin/dprox#readme> category: DNS@@ -41,17 +41,17 @@ src ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N build-depends:- attoparsec- , base >=4.7 && <5- , bytestring- , containers+ attoparsec >=0.13+ , base >=4.12 && <5+ , bytestring >=0.10+ , containers >=0.6 , dns >=3.0.4- , iproute- , network- , optparse-applicative- , streaming-commons- , unix- , unordered-containers+ , iproute >=1.7+ , network >=2.8 && <3+ , optparse-applicative >=0.14+ , streaming-commons >=0.2+ , unix >=2.7+ , unordered-containers >=0.2 if flag(static) ghc-options: -optl-static default-language: Haskell2010@@ -66,16 +66,16 @@ test ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N build-depends:- attoparsec- , base >=4.7 && <5- , bytestring- , containers+ attoparsec >=0.13+ , base >=4.12 && <5+ , bytestring >=0.10+ , containers >=0.6 , dns >=3.0.4 , hspec- , iproute- , network- , optparse-applicative- , streaming-commons- , unix- , unordered-containers+ , iproute >=1.7+ , network >=2.8 && <3+ , optparse-applicative >=0.14+ , streaming-commons >=0.2+ , unix >=2.7+ , unordered-containers >=0.2 default-language: Haskell2010
src/Config.hs view
@@ -7,6 +7,7 @@ , Config(..) , getConfig , IP(..)+, invalidIPAddress , PortNumber ) where @@ -18,7 +19,8 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import Data.IP (IP (..))-import Data.Maybe (catMaybes)+import Data.Maybe (catMaybes, fromMaybe,+ isNothing) import Data.Streaming.Network (HostPreference) import Data.String (fromString) import qualified Network.DNS as DNS@@ -47,7 +49,7 @@ where opts = info ((,,,) <$> globalOption <*> configFileOption <*> hostsFilesOption <*> plainOption <**> helper)- ( fullDesc <> progDesc "a simple DNS proxy server")+ ( fullDesc <> progDesc "a lightweight DNS proxy server, supports a small subset of dnsmasq options") readConfigFromFile file = handle handler (parseConfigFile <$> BS.readFile file) readHostsFromFile file = handle handler (parseHostsFile <$> BS.readFile file)@@ -64,6 +66,7 @@ parseConfig = parsePair "server" serverValue <|>+ parsePair "local" serverValue <|> parsePair "address" addressValue <|> parsePair "bogus-nxdomain" bogusNXValue @@ -115,6 +118,9 @@ Nothing -> fail ("invalid IP address: " ++ ipstr) Just ipaddr -> return ipaddr +invalidIPAddress :: IP+invalidIPAddress = "::"+ port :: P.Parser PortNumber port = read . BS8.unpack <$> P.takeWhile1 P8.isDigit_w8 <?> "Port Number" @@ -126,39 +132,39 @@ userOption = optional $ strOption ( long "user" <> short 'u'- <> metavar "uid"- <> help "set user id")+ <> metavar "<username>"+ <> help "Specify the userid to which dprox will change after startup") portOption = optional $ option auto ( long "port" <> short 'p'- <> metavar "port"- <> help "listen on this port instead of 53")+ <> metavar "<port>"+ <> help "Listen on this port instead of 53") listenOption = optional $ fromString <$> strOption ( long "listen-address" <> short 'a'- <> metavar "ipaddr"+ <> metavar "<ipaddr>" <> help "Listen on the given IP address") configFileOption :: Parser [FilePath] configFileOption = many $ strOption ( long "conf-file" <> short 'C'- <> metavar "path/to/dprox.conf"- <> help "configure file to read")+ <> metavar "<file>"+ <> help "Configure file to read") hostsFilesOption :: Parser [FilePath] hostsFilesOption = combine <$> noHostsOption <*> many newHostsOption where combine False newHosts = "/etc/hosts" : newHosts- combine True newHosts = newHosts+ combine True newHosts = newHosts newHostsOption = strOption ( long "addn-hosts" <> short 'H'- <> metavar "path/to/hosts"- <> help "additional hosts file to read (other than /etc/hosts)")+ <> metavar "<file>"+ <> help "Additional hosts file to read other than /etc/hosts") noHostsOption = switch ( long "no-hosts"@@ -170,21 +176,29 @@ where server = option (attoparsecReader serverValue) ( long "server"+ <> long "local" <> short 'S'- <> metavar "[/domain/]ip[#port]"- <> help "remote dns server ip")+ <> metavar "[/<domain>/]<ipaddr>[#<port>]"+ <> help serverMsg) + serverMsg = "Specify remote DNS server to use. " +++ "If multiple servers are specified, only the last one will be used. " +++ "If no server is specified, 8.8.8.8 will be used. " +++ "If <domain> is specified, queries matching this domain or its subdomains will use use specified remote DNS server. " +++ "If <ipaddr> is empty, queries matching specified domains will be handled by local hosts file only. " +++ "<port> can be used to specify alternative port for DNS server."+ address = option (attoparsecReader addressValue) ( long "address" <> short 'A'- <> metavar "/domain/ip"- <> help "specifiy ip for target domain")+ <> metavar "[/<domain>/]<ipaddr>"+ <> help "For DNS queries matching <domain> or its subdomains, replies <ipaddr> directly") bogusnx = option (attoparsecReader bogusNXValue) ( long "bogus-nxdomain" <> short 'B' <> metavar "ip"- <> help "specify ip for no such domain blacklist")+ <> help "Transform replies which contain the IP address given into \"No such domain\" replies") attoparsecReader :: P.Parser a -> ReadM a attoparsecReader p = eitherReader (P.parseOnly (p <* P.endOfInput) . BS8.pack)@@ -192,9 +206,10 @@ serverValue :: P.Parser Config serverValue = do parsedDomain <- P.option Nothing (Just <$> (P8.char '/' *> domain <* P8.char '/'))- parsedIP <- ip+ parsedIP <- P.option Nothing (Just <$> ip)+ when (isNothing parsedDomain && isNothing parsedIP) $ fail "at least one of <domain> and <ip> must be specified" parsedPort <- P.option Nothing (Just <$> (P8.char '#' *> port))- return (Server parsedDomain parsedIP parsedPort)+ return (Server parsedDomain (fromMaybe invalidIPAddress parsedIP) parsedPort) addressValue :: P.Parser Config addressValue = Address <$> (P8.char '/' *> domain <* P8.char '/') <*> ip
src/Main.hs view
@@ -7,10 +7,9 @@ import Control.Concurrent (forkIO) import Control.Exception (SomeException, handle)-import Control.Monad (forM, forever)+import Control.Monad (forM, forever, join) import Data.ByteString (ByteString) import qualified Data.Foldable as F-import Data.Map ((!)) import qualified Data.Map as M import Data.Maybe (fromMaybe) import qualified Data.Set as S@@ -67,10 +66,12 @@ q <- DNS.decode bs if DNS.qOrR (DNS.flags (DNS.header q)) == DNS.QR_Query then return q else Left DNS.FormatError -handleServer :: DomainRoute Resolver -> Resolver-handleServer route qd = resolver qd+handleServer :: DomainRoute (Maybe Resolver) -> Resolver+handleServer route qd qt = case resolver of+ Nothing -> return (Left DNS.NameError)+ Just resolver' -> resolver' qd qt where- resolver = fromMaybe (error "handleServer: internal error") (getDomainRouteByPrefix route qd)+ resolver = join (getDomainRouteByPrefix route qd) handleAddressAndHosts :: DomainRoute [IP] -> DomainRoute [IP] -> Resolver -> Resolver handleAddressAndHosts address hosts resolver qd qt =@@ -122,6 +123,7 @@ resolvConfs = [ (addr, rc) | addr@(host, port) <- S.toList serverAddressSet+ , host /= invalidIPAddress , let rsinfo = if port == defaultPort then DNS.RCHostName (show host) else DNS.RCHostPort (show host) port@@ -146,7 +148,7 @@ createResolvers ((k,v):xs) m = DNS.withResolver v $ \rs -> createResolvers xs (M.insert k (DNS.lookup rs) m)- createResolvers [] m = let serverRoute' = fmap (m!) serverRoute+ createResolvers [] m = let serverRoute' = fmap (`M.lookup`m) serverRoute resolver = handleBogusNX bogusnxSet $ handleAddressAndHosts addressRoute hostsRoute $ handleServer serverRoute'