Skip to content

DNS health: checkMX() tests whichever MX record comes first, not the lowest preference #101

Description

@toolonghere

Type: Bug

checkMX() takes the first record of the MX answer without looking at its preference:

mxRecords = resolver.query(domain, "MX")
mxDomain = mxRecords[0].exchange # Mail-domain of domain
res["mxDomain"] = str(mxDomain)

        mxRecords = resolver.query(domain, "MX")
        mxDomain = mxRecords[0].exchange # Mail-domain of domain
        res["mxDomain"] = str(mxDomain)

DNS makes no promise about the order of records within an RRset, and resolvers are free to return them in any order. For a domain with a primary and a backup MX, for example

example.com.  MX  10 mail.example.com.
example.com.  MX  20 backup.mx.example.net.

the check therefore tests the primary on some calls and the backup provider on others. When it lands on the backup, externalDNS, internalDNS and reverseLookup all describe the provider's host rather than the grommunio server, and the MX chip in admin-web is scored against the wrong machine, for an unchanged zone.

We see both orders on one server: the local resolver returned the preference-20 record first, the public resolvers 1.1.1.1 and 9.9.9.9 returned the preference-10 record first, and a direct call of checkMX() a few minutes later picked the primary.

Suggested fix: sort by preference, with the exchange name as a tie-breaker, before picking:

        mxRecords = sorted(resolver.query(domain, "MX"),
                           key=lambda r: (r.preference, str(r.exchange)))
        mxDomain = mxRecords[0].exchange # Mail-domain of domain

The second key matters when several records share the lowest preference: sorted() is stable, so on preference alone it would simply keep the resolver's own order — which is the thing that varies. With the name as a tie-breaker the same host is picked on every call, whatever order the answer arrives in.

That still checks only one host. Testing every record at the lowest preference would be the complete answer, but it needs a change in the response format; a stable single pick at least stops the chip from changing colour on an unchanged zone.

(preference and exchange as in dnspython 2.6.1, the version shipped with Ubuntu 24.04.)

Present on current master (3077150) and in the Ubuntu 24.04 community package 1.21.7.m3077150.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions