From 63a2be439ef6e372b55c64bbfbc24413ff3b2916 Mon Sep 17 00:00:00 2001 From: Brian Cunnie Date: Mon, 16 Sep 2024 17:39:35 -0700 Subject: [PATCH] Return NS records randomly Previously when the NS records were returned, ns-aws was always returned first. Coincidentally, 64% of the queries were directed to ns-aws. And once I exceeded AWS's 10 TB bandwidth limit, AWS began gouging me for bandwidth charges, and $12.66/month rapidly climbed to $62.30 I'm hoping that by randomly rotating the order of nameservers, the traffic will balance across the nameservers. Current snapshot (already ns-ovh is helping): ns-aws.sslip.io "Queries: 237744377 (1800.6/s)" "Answered Queries: 63040894 (477.5/s)" ns-azure.sslip.io "Queries: 42610823 (323.4/s)" "Answered Queries: 14660603 (111.3/s)" ns-gce.sslip.io "Queries: 59734371 (454.1/s)" "Answered Queries: 17636444 (134.1/s)" ns-ovh.sslip.io "Queries: 135897332 (1034.4/s)" "Answered Queries: 36010164 (274.1/s)" --- integration_flags_test.go | 7 ++++--- integration_test.go | 12 +++++++----- xip/xip.go | 6 +++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/integration_flags_test.go b/integration_flags_test.go index 90fb14d..c38290d 100644 --- a/integration_flags_test.go +++ b/integration_flags_test.go @@ -42,12 +42,13 @@ var _ = Describe("flags", func() { Expect(err).ToNot(HaveOccurred()) Eventually(digSession).Should(Say(`flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0`)) Eventually(digSession).Should(Say(`;; ANSWER SECTION:`)) - Eventually(digSession).Should(Say(`mickey.minnie.\n`)) - Eventually(digSession).Should(Say(`daffy.duck.\n`)) Eventually(digSession, 1).Should(Exit(0)) + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`mickey.minnie.\n`)) + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`daffy.duck.\n`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`Adding nameserver "mickey\.minnie\."\n`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`Adding nameserver "daffy\.duck\."\n`)) - Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeNS example.com. \? mickey\.minnie\., daffy\.duck\.\n`)) + // we don't know the order in which the nameservers will be returned, so we try both + Eventually(string(serverSession.Err.Contents())).Should(Or(MatchRegexp(`TypeNS example.com. \? mickey\.minnie\., daffy\.duck\.\n`), MatchRegexp(`TypeNS example.com. \? daffy\.duck\., mickey\.minnie\.\n`))) }) When("a nameserver is an empty string", func() { BeforeEach(func() { diff --git a/integration_test.go b/integration_test.go index 3ae9840..6e396ba 100644 --- a/integration_test.go +++ b/integration_test.go @@ -268,10 +268,6 @@ var _ = Describe("sslip.io-dns-server", func() { Expect(err).ToNot(HaveOccurred()) Eventually(digSession).Should(Say(`flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 7`)) Eventually(digSession).Should(Say(`;; ANSWER SECTION:`)) - Eventually(digSession).Should(Say(`ns-aws.sslip.io.\n`)) - Eventually(digSession).Should(Say(`ns-azure.sslip.io.\n`)) - Eventually(digSession).Should(Say(`ns-gce.sslip.io.\n`)) - Eventually(digSession).Should(Say(`ns-ovh.sslip.io.\n`)) Eventually(digSession).Should(Say(`;; ADDITIONAL SECTION:`)) Eventually(digSession).Should(Say(`ns-aws.sslip.io..*52.0.56.137\n`)) Eventually(digSession).Should(Say(`ns-aws.sslip.io..*2600:1f18:aaf:6900::a\n`)) @@ -281,6 +277,11 @@ var _ = Describe("sslip.io-dns-server", func() { Eventually(digSession).Should(Say(`ns-ovh.sslip.io..*51.75.53.19\n`)) Eventually(digSession).Should(Say(`ns-ovh.sslip.io..*2001:41d0:602:2313::1\n`)) Eventually(digSession, 1).Should(Exit(0)) + // the server names may appear out-of-order + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`NS\tns-aws.sslip.io.\n`)) + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`NS\tns-azure.sslip.io.\n`)) + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`NS\tns-gce.sslip.io.\n`)) + Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`NS\tns-ovh.sslip.io.\n`)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeNS example.com. \? ns-aws.sslip.io., ns-azure.sslip.io., ns-gce.sslip.io., ns-ovh.sslip.io.\n`)) }) }) @@ -400,9 +401,10 @@ var _ = Describe("sslip.io-dns-server", func() { "@localhost international-raiffeisen-bank.fc00--.sslip.io aaaa +short", `\Afc00::\n\z`, `TypeAAAA international-raiffeisen-bank.fc00--.sslip.io. \? fc00::\n$`), + // use regex to account for rotated nameserver order Entry("an NS record with acme_challenge with a forbidden string is not delegated", "@localhost _acme-challenge.raiffeisen.fe80--.sslip.io ns +short", - `\Ans-aws.sslip.io.\nns-azure.sslip.io.\nns-gce.sslip.io.\nns-ovh.sslip.io.\n\z`, + `\Ans-[a-z]+.sslip.io.\nns-[a-z]+.sslip.io.\nns-[a-z]+.sslip.io.\nns-[a-z]+.sslip.io.\n\z`, `TypeNS _acme-challenge.raiffeisen.fe80--.sslip.io. \? ns-aws.sslip.io., ns-azure.sslip.io., ns-gce.sslip.io., ns-ovh.sslip.io.\n$`), Entry("an A record with a forbidden CIDR is redirected", "@localhost nf.43.134.66.67.sslip.io +short", diff --git a/xip/xip.go b/xip/xip.go index 7ba392c..9540cc5 100644 --- a/xip/xip.go +++ b/xip/xip.go @@ -647,9 +647,13 @@ func (x *Xip) NSResponse(name dnsmessage.Name, response Response, logMessage str var logMessages []string if response.Header.Authoritative { // we're authoritative, so we reply with the answers + // but we rotate the nameservers every second so ns-aws doesn't bear the brunt (64%) of the traffic + epoch := time.Now().UTC().Unix() + index := int(epoch) % len(x.NameServers) + rotatedNameservers := append(x.NameServers[index:], x.NameServers[0:index]...) response.Answers = append(response.Answers, func(b *dnsmessage.Builder) error { - return buildNSRecords(b, name, x.NameServers) + return buildNSRecords(b, name, rotatedNameservers) }) } else { // we're NOT authoritative, so we reply who is authoritative