Repository: netty/netty
Issue: DNS Resolver - Honor etc/resolv.conf options timeout, rotate and attempts

[/etc/resolv.conf](https://linux.die.net/man/5/resolv.conf) options support `timeout`, `rotate` and `attempts` but the [UnixResolverDnsServerAddressStreamProvider](https://github.com/netty/netty/blob/4.1/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java#L292) ignores these options.

These should be parsed and used accordingly to their documented behavior.

Issue comments:
@fabienrenaud we love contributions :) So if you are interested maybe you want to provide a PR
PR: https://github.com/netty/netty/pull/10207

Pull request review comments:
~let's not change the default values used here.~
2020
ah never mind... this is because we use the one configured by the system in `DnsNameResolver` then 
@fabienrenaud I wonder if we should better do this as part of the builder ?
Doable. 
But I'd need to keep this `ndots >= 0 ? ndots : DEFAULT_NDOTS` (with DEFAULT_NDOTS loaded from resolv.conf) just in case some users invoke the deprecated constructors with `ndots=-1` and expect the value to be loaded from file automatically.
Let me know which way you prefer.
why not use `int` ? 
why not use `int` ? 
why not use `int` ? 
why not use `int` ? 
`null` values are used for error control when parsing from strings failed for example (and not override defaults).
But I see you don't like it (I'm not fond of it either), so I moved catching `NumberFormatException` from `parseResIntOption` to `parseResOption`.
never mind... lets keep it as you did.
nit: make all of the methods package-private as the class is package-private as well. This also ensures we no expose these by mistake at some point. 
add a comment why its ok to ignore. 
done
done

Patch that resolved the issue:
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
index 67f85dcce6a..ca3668a6b7d 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
@@ -77,9 +77,9 @@
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
-import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots;
 import static io.netty.util.internal.ObjectUtil.checkNotNull;
 import static io.netty.util.internal.ObjectUtil.checkPositive;
 
@@ -111,7 +111,7 @@ public class DnsNameResolver extends InetNameResolver {
 
     static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
     static final String[] DEFAULT_SEARCH_DOMAINS;
-    private static final int DEFAULT_NDOTS;
+    private static final UnixResolverOptions DEFAULT_OPTIONS;
 
     static {
         if (NetUtil.isIpV4StackPreferred() || !anyInterfaceSupportsIpV6()) {
@@ -141,13 +141,13 @@ public class DnsNameResolver extends InetNameResolver {
         }
         DEFAULT_SEARCH_DOMAINS = searchDomains;
 
-        int ndots;
+        UnixResolverOptions options;
         try {
-            ndots = parseEtcResolverFirstNdots();
+            options = UnixResolverDnsServerAddressStreamProvider.parseEtcResolverOptions();
         } catch (Exception ignore) {
-            ndots = UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS;
+            options = UnixResolverOptions.newBuilder().build();
         }
-        DEFAULT_NDOTS = ndots;
+        DEFAULT_OPTIONS = options;
     }
 
     /**
@@ -383,10 +383,12 @@ public DnsNameResolver(
             boolean decodeIdn,
             boolean completeOncePreferredResolved) {
         super(eventLoop);
-        this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
+        this.queryTimeoutMillis = queryTimeoutMillis > 0
+            ? queryTimeoutMillis
+            : TimeUnit.SECONDS.toMillis(DEFAULT_OPTIONS.timeout());
         this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
         this.recursionDesired = recursionDesired;
-        this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
+        this.maxQueriesPerResolve = maxQueriesPerResolve > 0 ? maxQueriesPerResolve : DEFAULT_OPTIONS.attempts();
         this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
         this.optResourceEnabled = optResourceEnabled;
         this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
@@ -401,7 +403,7 @@ public DnsNameResolver(
                                                                dnsQueryLifecycleObserverFactory) :
                 checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
         this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
-        this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS;
+        this.ndots = ndots >= 0 ? ndots : DEFAULT_OPTIONS.ndots();
         this.decodeIdn = decodeIdn;
         this.completeOncePreferredResolved = completeOncePreferredResolved;
         this.socketChannelFactory = socketChannelFactory;
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java
index 4d3961221e4..332aaffe97a 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java
@@ -29,7 +29,6 @@
 import java.util.Arrays;
 import java.util.List;
 
-import static io.netty.resolver.dns.DnsServerAddressStreamProviders.platformDefault;
 import static io.netty.util.internal.ObjectUtil.checkNotNull;
 import static io.netty.util.internal.ObjectUtil.intValue;
 
@@ -46,16 +45,17 @@ public final class DnsNameResolverBuilder {
     private Integer minTtl;
     private Integer maxTtl;
     private Integer negativeTtl;
-    private long queryTimeoutMillis = 5000;
+    private long queryTimeoutMillis = -1;
     private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
     private boolean completeOncePreferredResolved;
     private boolean recursionDesired = true;
-    private int maxQueriesPerResolve = 16;
+    private int maxQueriesPerResolve = -1;
     private boolean traceEnabled;
     private int maxPayloadSize = 4096;
     private boolean optResourceEnabled = true;
     private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
-    private DnsServerAddressStreamProvider dnsServerAddressStreamProvider = platformDefault();
+    private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
+            DnsServerAddressStreamProviders.platformDefault();
     private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
             NoopDnsQueryLifecycleObserverFactory.INSTANCE;
     private String[] searchDomains;
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
index feac4b9145f..0bbe9136af0 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java
@@ -46,19 +46,22 @@
 public final class UnixResolverDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
     private static final InternalLogger logger =
             InternalLoggerFactory.getInstance(UnixResolverDnsServerAddressStreamProvider.class);
+
+    private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
+    private static final String RES_OPTIONS = System.getenv("RES_OPTIONS");
+
     private static final String ETC_RESOLV_CONF_FILE = "/etc/resolv.conf";
     private static final String ETC_RESOLVER_DIR = "/etc/resolver";
     private static final String NAMESERVER_ROW_LABEL = "nameserver";
     private static final String SORTLIST_ROW_LABEL = "sortlist";
-    private static final String OPTIONS_ROW_LABEL = "options";
+    private static final String OPTIONS_ROW_LABEL = "options ";
+    private static final String OPTIONS_ROTATE_FLAG = "rotate";
     private static final String DOMAIN_ROW_LABEL = "domain";
     private static final String SEARCH_ROW_LABEL = "search";
     private static final String PORT_ROW_LABEL = "port";
-    private static final String NDOTS_LABEL = "ndots:";
-    static final int DEFAULT_NDOTS = 1;
+
     private final DnsServerAddresses defaultNameServerAddresses;
     private final Map<String, DnsServerAddresses> domainToNameServerStreamMap;
-    private static final Pattern SEARCH_DOMAIN_PATTERN = Pattern.compile("\\s+");
 
     /**
      * Attempt to parse {@code /etc/resolv.conf} and files in the {@code /etc/resolver} directory by default.
@@ -154,6 +157,7 @@ private boolean mayOverrideNameServers() {
     private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) throws IOException {
         Map<String, DnsServerAddresses> domainToNameServerStreamMap =
                 new HashMap<String, DnsServerAddresses>(etcResolverFiles.length << 1);
+        boolean rotateGlobal = RES_OPTIONS != null && RES_OPTIONS.contains(OPTIONS_ROTATE_FLAG);
         for (File etcResolverFile : etcResolverFiles) {
             if (!etcResolverFile.isFile()) {
                 continue;
@@ -164,6 +168,7 @@ private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) t
                 br = new BufferedReader(fr);
                 List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(2);
                 String domainName = etcResolverFile.getName();
+                boolean rotate = rotateGlobal;
                 int port = DNS_PORT;
                 String line;
                 while ((line = br.readLine()) != null) {
@@ -173,7 +178,9 @@ private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) t
                         if (line.isEmpty() || (c = line.charAt(0)) == '#' || c == ';') {
                             continue;
                         }
-                        if (line.startsWith(NAMESERVER_ROW_LABEL)) {
+                        if (!rotate && line.startsWith(OPTIONS_ROW_LABEL)) {
+                            rotate = line.contains(OPTIONS_ROTATE_FLAG);
+                        } else if (line.startsWith(NAMESERVER_ROW_LABEL)) {
                             int i = indexOfNonWhiteSpace(line, NAMESERVER_ROW_LABEL.length());
                             if (i < 0) {
                                 throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL +
@@ -212,7 +219,7 @@ private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) t
                             }
                             domainName = line.substring(i);
                             if (!addresses.isEmpty()) {
-                                putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
+                                putIfAbsent(domainToNameServerStreamMap, domainName, addresses, rotate);
                             }
                             addresses = new ArrayList<InetSocketAddress>(2);
                         } else if (line.startsWith(PORT_ROW_LABEL)) {
@@ -230,7 +237,7 @@ private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) t
                     }
                 }
                 if (!addresses.isEmpty()) {
-                    putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
+                    putIfAbsent(domainToNameServerStreamMap, domainName, addresses, rotate);
                 }
             } finally {
                 if (br == null) {
@@ -245,9 +252,13 @@ private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) t
 
     private static void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap,
                                     String domainName,
-                                    List<InetSocketAddress> addresses) {
+                                    List<InetSocketAddress> addresses,
+                                    boolean rotate) {
         // TODO(scott): sortlist is being ignored.
-        putIfAbsent(domainToNameServerStreamMap, domainName, DnsServerAddresses.sequential(addresses));
+        DnsServerAddresses addrs = rotate
+            ? DnsServerAddresses.rotational(addresses)
+            : DnsServerAddresses.sequential(addresses);
+        putIfAbsent(domainToNameServerStreamMap, domainName, addrs);
     }
 
     private static void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap,
@@ -264,25 +275,25 @@ private static void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServ
     }
 
     /**
-     * Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return the
-     * value corresponding to the first ndots in an options configuration.
-     * @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
-     * found.
+     * Parse <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return options of interest, namely:
+     * timeout, attempts and ndots.
+     * @return The options values provided by /etc/resolve.conf.
      * @throws IOException If a failure occurs parsing the file.
      */
-    static int parseEtcResolverFirstNdots() throws IOException {
-        return parseEtcResolverFirstNdots(new File(ETC_RESOLV_CONF_FILE));
+    static UnixResolverOptions parseEtcResolverOptions() throws IOException {
+        return parseEtcResolverOptions(new File(ETC_RESOLV_CONF_FILE));
     }
 
     /**
-     * Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return the
-     * value corresponding to the first ndots in an options configuration.
+     * Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return options
+     * of interest, namely: timeout, attempts and ndots.
      * @param etcResolvConf a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
-     * @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
-     * found.
+     * @return The options values provided by /etc/resolve.conf.
      * @throws IOException If a failure occurs parsing the file.
      */
-    static int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException {
+    static UnixResolverOptions parseEtcResolverOptions(File etcResolvConf) throws IOException {
+        UnixResolverOptions.Builder optionsBuilder = UnixResolverOptions.newBuilder();
+
         FileReader fr = new FileReader(etcResolvConf);
         BufferedReader br = null;
         try {
@@ -290,12 +301,7 @@ static int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException {
             String line;
             while ((line = br.readLine()) != null) {
                 if (line.startsWith(OPTIONS_ROW_LABEL)) {
-                    int i = line.indexOf(NDOTS_LABEL);
-                    if (i >= 0) {
-                        i += NDOTS_LABEL.length();
-                        final int j = line.indexOf(' ', i);
-                        return Integer.parseInt(line.substring(i, j < 0 ? line.length() : j));
-                    }
+                    parseResOptions(line.substring(OPTIONS_ROW_LABEL.length()), optionsBuilder);
                     break;
                 }
             }
@@ -306,7 +312,35 @@ static int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException {
                 br.close();
             }
         }
-        return DEFAULT_NDOTS;
+
+        // amend options
+        if (RES_OPTIONS != null) {
+            parseResOptions(RES_OPTIONS, optionsBuilder);
+        }
+
+        return optionsBuilder.build();
+    }
+
+    private static void parseResOptions(String line, UnixResolverOptions.Builder builder) {
+        String[] opts = WHITESPACE_PATTERN.split(line);
+        for (String opt : opts) {
+            try {
+                if (opt.startsWith("ndots:")) {
+                    builder.setNdots(parseResIntOption(opt, "ndots:"));
+                } else if (opt.startsWith("attempts:")) {
+                    builder.setAttempts(parseResIntOption(opt, "attempts:"));
+                } else if (opt.startsWith("timeout:")) {
+                    builder.setTimeout(parseResIntOption(opt, "timeout:"));
+                }
+            } catch (NumberFormatException ignore) {
+                // skip bad int values from resolv.conf to keep value already set in UnixResolverOptions
+            }
+        }
+    }
+
+    private static int parseResIntOption(String opt, String fullLabel) {
+        String optValue = opt.substring(fullLabel.length());
+        return Integer.parseInt(optValue);
     }
 
     /**
@@ -346,7 +380,7 @@ static List<String> parseEtcResolverSearchDomains(File etcResolvConf) throws IOE
                     if (i >= 0) {
                         // May contain more then one entry, either seperated by whitespace or tab.
                         // See https://linux.die.net/man/5/resolver
-                        String[] domains = SEARCH_DOMAIN_PATTERN.split(line.substring(i));
+                        String[] domains = WHITESPACE_PATTERN.split(line.substring(i));
                         Collections.addAll(searchDomains, domains);
                     }
                 }
@@ -364,4 +398,5 @@ static List<String> parseEtcResolverSearchDomains(File etcResolvConf) throws IOE
                 ? Collections.singletonList(localDomain)
                 : searchDomains;
     }
+
 }
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverOptions.java b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverOptions.java
new file mode 100644
index 00000000000..dcfedd8baa2
--- /dev/null
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverOptions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.resolver.dns;
+
+/**
+ * Represents options defined in a file of the format <a href=https://linux.die.net/man/5/resolver>etc/resolv.conf</a>.
+ */
+final class UnixResolverOptions {
+
+    private final int ndots;
+    private final int timeout;
+    private final int attempts;
+
+    UnixResolverOptions(int ndots, int timeout, int attempts) {
+        this.ndots = ndots;
+        this.timeout = timeout;
+        this.attempts = attempts;
+    }
+
+    static UnixResolverOptions.Builder newBuilder() {
+        return new UnixResolverOptions.Builder();
+    }
+
+    /**
+     * The number of dots which must appear in a name before an initial absolute query is made.
+     * The default value is {@code 1}.
+     */
+    int ndots() {
+        return ndots;
+    }
+
+    /**
+     * The timeout of each DNS query performed by this resolver (in seconds).
+     * The default value is {@code 5}.
+     */
+    int timeout() {
+        return timeout;
+    }
+
+    /**
+     * The maximum allowed number of DNS queries to send when resolving a host name.
+     * The default value is {@code 16}.
+     */
+    int attempts() {
+        return attempts;
+    }
+
+    static final class Builder {
+
+        private int ndots = 1;
+        private int timeout = 5;
+        private int attempts = 16;
+
+        private Builder() {
+        }
+
+        void setNdots(int ndots) {
+            this.ndots = ndots;
+        }
+
+        void setTimeout(int timeout) {
+            this.timeout = timeout;
+        }
+
+        void setAttempts(int attempts) {
+            this.attempts = attempts;
+        }
+
+        UnixResolverOptions build() {
+            return new UnixResolverOptions(ndots, timeout, attempts);
+        }
+    }
+}


Test patch:
diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java
index 6bb133a11aa..97e3d8a75fb 100644
--- a/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java
+++ b/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java
@@ -29,8 +29,7 @@
 import java.util.Collections;
 import java.util.List;
 
-import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS;
-import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots;
+import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverOptions;
 import static org.junit.Assert.assertEquals;
 
 public class UnixResolverDnsServerAddressStreamProviderTest {
@@ -50,6 +49,62 @@ public void defaultLookupShouldReturnResultsIfOnlySingleFileSpecified() throws E
         assertHostNameEquals("127.0.0.3", stream.next());
     }
 
+    @Test
+    public void nameServerAddressStreamShouldBeRotationalWhenRotationOptionsIsPresent() throws Exception {
+        File f = buildFile("options rotate\n" +
+            "domain linecorp.local\n" +
+            "nameserver 127.0.0.2\n" +
+            "nameserver 127.0.0.3\n" +
+            "nameserver 127.0.0.4\n");
+        UnixResolverDnsServerAddressStreamProvider p =
+            new UnixResolverDnsServerAddressStreamProvider(f, null);
+
+        DnsServerAddressStream stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+
+        stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+        assertHostNameEquals("127.0.0.2", stream.next());
+
+        stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.4", stream.next());
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+
+        stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+    }
+
+    @Test
+    public void nameServerAddressStreamShouldAlwaysStartFromTheTopWhenRotationOptionsIsAbsent() throws Exception {
+        File f = buildFile("domain linecorp.local\n" +
+            "nameserver 127.0.0.2\n" +
+            "nameserver 127.0.0.3\n" +
+            "nameserver 127.0.0.4\n");
+        UnixResolverDnsServerAddressStreamProvider p =
+            new UnixResolverDnsServerAddressStreamProvider(f, null);
+
+        DnsServerAddressStream stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+
+        stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+
+        stream = p.nameServerAddressStream("");
+        assertHostNameEquals("127.0.0.2", stream.next());
+        assertHostNameEquals("127.0.0.3", stream.next());
+        assertHostNameEquals("127.0.0.4", stream.next());
+    }
+
     @Test
     public void defaultReturnedWhenNoBetterMatch() throws Exception {
         File f = buildFile("domain linecorp.local\n" +
@@ -83,23 +138,63 @@ public void moreRefinedSelectionReturnedWhenMatch() throws Exception {
     }
 
     @Test
-    public void ndotsIsParsedIfPresent() throws IOException {
+    public void ndotsOptionIsParsedIfPresent() throws IOException {
+        File f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n" +
+            "options ndots:0\n");
+        assertEquals(0, parseEtcResolverOptions(f).ndots());
+
+        f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n" +
+            "options ndots:123 foo:goo\n");
+        assertEquals(123, parseEtcResolverOptions(f).ndots());
+    }
+
+    @Test
+    public void defaultValueReturnedIfNdotsOptionsNotPresent() throws IOException {
+        File f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n");
+        assertEquals(1, parseEtcResolverOptions(f).ndots());
+    }
+
+    @Test
+    public void timeoutOptionIsParsedIfPresent() throws IOException {
+        File f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n" +
+            "options timeout:0\n");
+        assertEquals(0, parseEtcResolverOptions(f).timeout());
+
+        f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n" +
+            "options foo:bar timeout:124\n");
+        assertEquals(124, parseEtcResolverOptions(f).timeout());
+    }
+
+    @Test
+    public void defaultValueReturnedIfTimeoutOptionsIsNotPresent() throws IOException {
+        File f = buildFile("search localdomain\n" +
+            "nameserver 127.0.0.11\n");
+        assertEquals(5, parseEtcResolverOptions(f).timeout());
+    }
+
+    @Test
+    public void attemptsOptionIsParsedIfPresent() throws IOException {
         File f = buildFile("search localdomain\n" +
-                           "nameserver 127.0.0.11\n" +
-                           "options ndots:0\n");
-        assertEquals(0, parseEtcResolverFirstNdots(f));
+            "nameserver 127.0.0.11\n" +
+            "options attempts:0\n");
+        assertEquals(0, parseEtcResolverOptions(f).attempts());
 
         f = buildFile("search localdomain\n" +
-                      "nameserver 127.0.0.11\n" +
-                      "options ndots:123 foo:goo\n");
-        assertEquals(123, parseEtcResolverFirstNdots(f));
+            "nameserver 127.0.0.11\n" +
+            "options foo:bar attempts:12\n");
+        assertEquals(12, parseEtcResolverOptions(f).attempts());
     }
 
     @Test
-    public void defaultValueReturnedIfNdotsNotPresent() throws IOException {
+    public void defaultValueReturnedIfAttemptsOptionsIsNotPresent() throws IOException {
         File f = buildFile("search localdomain\n" +
-                           "nameserver 127.0.0.11\n");
-        assertEquals(DEFAULT_NDOTS, parseEtcResolverFirstNdots(f));
+            "nameserver 127.0.0.11\n");
+        assertEquals(16, parseEtcResolverOptions(f).attempts());
     }
 
     @Test