0

I'm working on a fairly complex Xamarin.Forms application. We make a lot of REST requests. It's been reported that our application isn't respecting DNS failover for load balancing in a timely manner, so I started investigating. I'm running dnsmasq so I can look at when the app makes DNS requests. The code is currently using HttpWebRequest, and I noticed it's making DNS queries at least 10 minutes apart.

I understand part of this is because most .NET networking bits use a keepalive connection. I certainly see a higher rate of DNS queries if I force the headers to not use keepalive, but that adds network overhead so it's not a desirable solution. But I didn't initially see a clear way to control how HttpWebRequest makes DNS queries.

It looked promising that I could get its ServicePoint property and set the ConnectionLeaseTimeout on that. Unfortunately, that throws NotImplementedException in Xamarin so it's not going to be part of any solution.

I thought that perhaps HttpClient would be more configurable. I see a lot of discussion about how to use it properly, and that if you do it that way you need to set ServicePointManager.DnsRefreshTimeout to a smaller value for use cases where you want to expect DNS to update frequently. But this is usually done in conjunction with getting the ServicePoint for the deisred endpoint and also modifying ConnectionLeaseTimeout, which isn't possible.

I've been testing with a very simple app that reuses an HttpClient and makes the same request any time I push a button. Slap this ViewModel behind some Xaml with a button:

using System;
using Xamarin.Forms;
using System.Net.Http;
using System.Net;

namespace TestDns {
    public class MainPageViewModel {

        private const string _URL = "http://www.example.com";

        private HttpClient _httpClient;

        private ServicePoint _sp;

        public MainPageViewModel() {
            var sp = ServicePointManager.FindServicePoint(new Uri(_URL));
            _sp = sp;
            //_sp.ConnectionLeaseTimeout = 100; // throws NIE

            _httpClient = new HttpClient();

            ServicePointManager.DnsRefreshTimeout = 100;
        }

        public Command WhenButtonIsClicked {
            get {
                return new Command(() => SendRequest());
            }
        }

        private async void SendRequest() {
            Console.WriteLine($"{_sp.CurrentConnections}");
            var url = "http://www.example.com";
            var response = await _httpClient.GetAsync(url);
            Console.WriteLine($"{response.Content}");
        }
    }
}

I didn't expect ConnectionLeaseTimeout to throw. I expected this code to only cache DNS requests for 100ms, I was going to choose a more reasonable timeframe like 2-3 minutes in more production-oriented tests. But since I can't get this simple example to function like I want, it seems moot to increase the delays.

Surely someone else has had this problem in a Xamarin app? Is my only solution going to be to look deeper and try to use native networking constructs?

OwenP
  • 24,950
  • 13
  • 65
  • 102

2 Answers2

1

If you're doing this on Android, DNS is cached for 10 minutes, and I don't believe you have any access to the expiration/refresh time from inside of your app. There are a number of ways to force a refresh but they all involve user actions like going into Network Connections and flipping from Static to DHCP and back, etc.

The only way I can think of to be sure of getting a fresh DNS lookup from inside your app is to have 10+ minutes worth of DNS entries that all alias to your server, and cycle your app through them, so every time you ask for a DNS lookup, it's a new name and not in the cache.

For example, look for 1.server.example.com 2.server.example.com, etc. Each new name will force a new lookup and won't be pulled from the cache because it's not there.

Terry Carmen
  • 3,720
  • 1
  • 16
  • 32
  • Hmm, I did some early testing on iOS but have mostly been testing on Android, this is very consistent with what I'm seeing. Disappointing, but you can't squeeze blood from a turnip I guess. – OwenP Jun 22 '18 at 20:25
  • Actually: is this true even if the TTL expiration is very short? The server I'm looking at has a TTL of 60 seconds. Shouldn't Android throw away its cached result after TTL expires? – OwenP Jun 22 '18 at 21:18
  • Aha. I managed to hunt down a system security setting that tells Java to use a lower TTL for all DNS. It'd be nicest if it respected DNS TTL, but this will work. Thanks for sending me down that trail! – OwenP Jun 22 '18 at 22:13
  • "Shouldn't Android throw away its cached result after TTL expires?" It *should* but I can't say for certain that it does. For that matter there's no guarantee that whatever DNS server you're using respects it either. DNS servers are notorious for ignoring TTLs. This is a real problem when trying to switch hosting providers for high profile sites and you can't afford to annoy the users. – Terry Carmen Jun 26 '18 at 00:59
0

It seems that Java has decided the solution to "some people implement DNS improperly and ignore TTL" is to make the problem worse by ensuring devices that use Java implement DNS improperly. There is a single TTL used for all DNS entries in the cache. There's some philosophical debate and what led me to the answer in this question which I adapted for the answer.

In terms of Xamarin projects, add this somewhere (I chose early in MainActivity):

Java.Security.Security.SetProperty("networkaddress.cache.ttl", "<integer seconds>");

Replace "<integer seconds>" with your desired TTL for all DNS entries. Be aware lower values might mean you make more DNS queries than you used to, if you're seriously trying to save networking bytes this could be an issue.

I'm leaving Terry Carmen's answer selected as "the answer".

OwenP
  • 24,950
  • 13
  • 65
  • 102