-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Throw h_errno specific exceptions after res_query errors. #17
Conversation
See #4 |
@dminuoso not sure why I cannot write comments right inside code review (probably, I don't have permission). I wanted to propose replacing the 4 lines with |
src/Network/DNS.hs
Outdated
fail "res_query(3) failed" | ||
h_errno <- c_get_h_errno | ||
case h_errno of | ||
1 -> throw DnsHostNotFound |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean throwIO
here and in other 3 cases below? throwIO
is preferable in the IO context.
src/Network/DNS.hs
Outdated
2 -> throw DnsNoData | ||
3 -> throw DnsNoRecovery | ||
4 -> throw DnsTryAgain | ||
_ -> fail "res_query (3) failed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar messages (res_send(3) failed
and res_mkquery(3) failed
have no space between the function name and the man section tag (3)
, consider removing the space here for consistency.
There is more space for improvements. Note that File configure.acdiff --git a/configure.ac b/configure.ac
index 93942e2..2c735fa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -30,6 +30,10 @@ AC_CHECK_DECLS([res_query, res_nquery], [], [], [[
#include <resolv.h>
]])
+AC_CHECK_DECLS([h_errno], [], [], [[
+#include <netdb.h>
+]])
+
dnl ----------------------------------------------------------------------------
RESOLV_SEARCH_LIBS([res_query],[res_query(0,0,0,0,0)],[resolv bind],[EXTRA_LIBS="$EXTRA_LIBS $ac_lib"],[
@@ -71,6 +75,17 @@ fi
fi
+AC_CHECK_MEMBERS([struct __res_state.res_h_errno],[],[],[[
+#include <sys/types.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_NAMESER_H
+# include <arpa/nameser.h>
+#endif
+#include <resolv.h>
+]])
+
AC_MSG_CHECKING([which DNS api to use])
case "x$USE_RES_NQUERY" in Check that field File cbits/hs_resolv.hdiff --git a/cbits/hs_resolv.h b/cbits/hs_resolv.h
index 9e2aa32..5bf1eea 100644
--- a/cbits/hs_resolv.h
+++ b/cbits/hs_resolv.h
@@ -9,6 +9,10 @@
# include <netinet/in.h>
#endif
+#if defined(HAVE_DECL_H_ERRNO)
+# include <netdb.h>
+#endif
+
#if defined(HAVE_ARPA_NAMESER_H)
# include <arpa/nameser.h>
#endif
@@ -86,6 +90,48 @@ hs_res_close(struct __res_state *s)
res_nclose(s);
}
+#if defined(HAVE_STRUCT___RES_STATE_RES_H_ERRNO)
+
+inline static int
+hs_get_h_errno(struct __res_state *s)
+{
+ assert(s);
+
+ switch(s->res_h_errno)
+ {
+ case HOST_NOT_FOUND: return 1;
+ case NO_DATA: return 2;
+ case NO_RECOVERY: return 3;
+ case TRY_AGAIN: return 4;
+ default: return -1;
+ }
+}
+
+#elif defined(HAVE_DECL_H_ERRNO)
+
+inline static int
+hs_get_h_errno(struct __res_state *s)
+{
+ switch(h_errno)
+ {
+ case HOST_NOT_FOUND: return 1;
+ case NO_DATA: return 2;
+ case NO_RECOVERY: return 3;
+ case TRY_AGAIN: return 4;
+ default: return -1;
+ }
+}
+
+#else
+
+inline static int
+hs_get_h_errno(struct __res_state *s)
+{
+ return -1;
+}
+
+#endif
+
#else
/* use non-reentrant API */
@@ -139,6 +185,31 @@ hs_res_close(void *s)
{
}
+#if defined(HAVE_DECL_H_ERRNO)
+
+inline static int
+hs_get_h_errno(void *s)
+{
+ switch(h_errno)
+ {
+ case HOST_NOT_FOUND: return 1;
+ case NO_DATA: return 2;
+ case NO_RECOVERY: return 3;
+ case TRY_AGAIN: return 4;
+ default: return -1;
+ }
+}
+
+#else
+
+inline static int
+hs_get_h_errno(void *s)
+{
+ return -1;
+}
+
+#endif
+
#endif
#endif /* HS_RESOLV_H */ Note that File src/Network/DNS/FFI.hsdiff --git a/src/Network/DNS/FFI.hs b/src/Network/DNS/FFI.hs
index 04e895d..8c4bd2e 100644
--- a/src/Network/DNS/FFI.hs
+++ b/src/Network/DNS/FFI.hs
@@ -87,3 +87,6 @@ foreign import capi safe "hs_resolv.h hs_res_mkquery" c_res_mkquery :: Ptr CResS
-- void hs_res_close(void *);
foreign import capi safe "hs_resolv.h hs_res_close" c_res_close :: Ptr CResState -> IO ()
+-- void *get_h_errno(void *s, int c, size_t n);
+foreign import capi unsafe "hs_resolv.h hs_get_h_errno" c_get_h_errno :: Ptr CResState -> IO CInt
+ File src/Network/DNS.hsdiff --git a/src/Network/DNS.hs b/src/Network/DNS.hs
index ab24be0..96b80b4 100644
--- a/src/Network/DNS.hs
+++ b/src/Network/DNS.hs
@@ -98,18 +98,22 @@ import Compat
import Network.DNS.FFI
import Network.DNS.Message
--- | Exception thrown in case of errors while encoding or decoding into a 'Msg'.
+-- | Exception thrown in case of errors while resolving or encoding/decoding into a 'Msg'.
--
-- @since 0.1.1.0
data DnsException = DnsEncodeException
| DnsDecodeException
+ | DnsHostNotFound -- ^ No such domain (authoritative)
+ | DnsNoData -- ^ No record for requested type
+ | DnsNoRecovery -- ^ Non recoverable errors, REFUSED, NOTIMP
+ | DnsTryAgain -- ^ No such domain (non-authoritative) or SERVERFAIL
deriving (Show, Typeable)
instance Exception DnsException
-- | Send a query via @res_query(3)@ and decode its response into a 'Msg'
--
--- Throws 'DnsException' in case of encoding or decoding errors. May throw other IO exceptions in case of network errors.
+-- Throws 'DnsException' in case of resolving or encoding/decoding errors. May throw other IO exceptions in case of network errors.
--
-- === Example
--
@@ -158,7 +162,13 @@ queryRaw (Class cls) (Name name) qtype = withCResState $ \stptr ->
unless (errno == eOK) $
throwErrno "res_query"
- fail "res_query(3) failed"
+ h_errno <- c_get_h_errno stptr
+ case h_errno of
+ 1 -> throwIO DnsHostNotFound
+ 2 -> throwIO DnsNoData
+ 3 -> throwIO DnsNoRecovery
+ 4 -> throwIO DnsTryAgain
+ _ -> fail "res_query(3) failed"
BS.packCStringLen (resptr, fromIntegral reslen) This patch was made against changes in another branch, so it's hardly can be applied directly in your branch. |
Now with #12 merged, this PR needs some conflict resolution. |
Yes, the conflict should arise in DNS.hs in function fail "res_query(3) failed" by the new hunk from the original patch and indent the new text correctly. |
After merge i would replace I would also consider getting state from field |
@andreasabel I can try to resolve conflicts |
Ah, the conflict is in DNS/FFI.hs. Then it should be even simpler. |
Prefer getting h_errno status from __res_state.res_h_errno rather than from the global variable h_errno.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add @since 0.2.0.0
to the new API elements and add them to the CHANGELOG.
Yes, it should list all the changes that are relevant for users of this package. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the @since
info I requested. Works if placed on a separated new line.
4ac3821
to
8c9cee6
Compare
@lyokha: I tried to remove the amount of cut&paste code by using a new macro Hang on, found something else... Ok, further reduced cut&paste. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
It is a bit unsettling that there are no tests for this new feature. |
Just made a manual test. {-# LANGUAGE OverloadedStrings #-}
module Main where
import Network.DNS
import Control.Exception
main :: IO ()
main = handle (\e -> putStrLn $ "Caught: " ++ show (e :: DnsException)) $
queryA (Name "www.auuaauuaoogle.com") >>= print
So this works at first glance. I have more issues to discuss:
|
Btw, DNS query with some bogus name can be used to test DnsHostNotFound (until someone registers such a name though). |
Good catch. This is indeed not a good design and can lead to accidential errors. It should be patched to return
Hard for me to judge the implications of this. Should be documented in the CHANGELOG.
This sounds like a test to this extent could be added. DNS query should be cheap enough to do in CI. |
I just recalled how I used this in a custom application: !addrs <- handle (\e -> if isUserError e &&
"(res_query(3) failed)" `T.isSuffixOf` T.pack (show e)
then do It wasn't simple as well :) Now, if I want to catch all exceptions (modulo encoding/decoding), I must use The changelog could be more precise:
to
Now I hesitate. Tests cannot control DNS system settings on the user side, and thus get no reliable. |
Yeah, this is definitely an improvement!
Ok, but maybe it is sufficient that tests run correctly in the CI and in vanilla user environments. |
@andreasabel, don't you mind against the following change in the changelog? diff --git a/ChangeLog.md b/ChangeLog.md
index af36318..6eb29b1 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -10,7 +10,10 @@ _2023-03-xx, Alexey Radkov and Andreas Abel_
(PR [#16](https://github.com/haskell-hvr/resolv/pull/16).)
* Fix memory leaks due to missing `res_nclose()` after each `res_ninit()` call.
(PR [#12](https://github.com/haskell-hvr/resolv/pull/12).)
-* Check the value of `h_errno` on failures of `res_nquery()` and throw an appropriate exception.
+* Check the value of `h_errno` on failures of `res_nquery()` and throw an
+ appropriate exception of type `DnsException` built with one of new
+ constructors `DnsHostNotFound`, `DnsNoData`, `DnsNoRecovery`, or `DnsTryAgain`.
+ Note that previously such exceptions were thrown by `fail` and had type `IOError`.
(PR [#17](https://github.com/haskell-hvr/resolv/pull/17).)
* Suppress configure warning on option `--with-compiler` passed by Cabal.
(PR [#21](https://github.com/haskell-hvr/resolv/pull/21).) Any suggestions? |
I did this now. Please check! Changelog looks good! |
Yes, this is exactly what I meant! The case when |
I committed updates in the changelog in the branch I used before. |
Yes looking at the call site, it is only called if there was an error indeed: https://github.com/haskell-hvr/resolv/pull/17/files#diff-7f2d6596edf47f19c2c00ce2c44fb33f4695a697116995962c5f10284eafb324R174-R178 So, strictly speaking, my change to preserve 0 was unnecessary. However, it is not wrong and looks more robust. I think we can merge now. |
* Changelog for version 0.2.0.0 * Changelog for 0.2.0.0: links to PRs, release authors * More precise changelog message about changes in #17 * Set release date * Correct release date --------- Co-authored-by: Andreas Abel <andreas.abel@ifi.lmu.de>
resolv
currently ignores h_errno and only produces a generic user error when res_query fails.This PR forwards h_errno as different DnsException constructors when h_errno is available.