← Back to team overview

group.of.nepali.translators team mailing list archive

[Bug 1811471] Re: local resolver stub fails to handle multiple TCP dns queries

 

** Changed in: systemd (Ubuntu Xenial)
       Status: In Progress => Invalid

** Changed in: systemd (Ubuntu Trusty)
       Status: In Progress => Invalid

** Changed in: systemd (Ubuntu Xenial)
   Importance: High => Undecided

** Changed in: systemd (Ubuntu Trusty)
   Importance: High => Undecided

** Changed in: systemd (Ubuntu Trusty)
     Assignee: Dan Streetman (ddstreet) => (unassigned)

** Changed in: systemd (Ubuntu Xenial)
     Assignee: Dan Streetman (ddstreet) => (unassigned)

** Description changed:

  [Impact]
  
  The systemd local 'stub' resolver handles all local DNS queries (by
  default configuration used in Ubuntu), and essentially proxies all
  requests to its configured upstream DNS resolvers.
  
  Most local DNS resolution by applications uses glibc's getaddrinfo()
  function.  This function is configured in various ways by the
  /etc/resolv.conf file, which tells glibc what nameserver/resolver to
  contact as well as how to talk to the name server.
  
  By default, glibc performs UDP DNS queries, with a single DNS query per
  UDP packet.  The UDP packet size is limited per DNS spec to 512 bytes.
  For some DNS lookups, a 512 byte UDP packet is not large enough to
  contain the entire response - for example, an A record lookup with a
  large number (e.g. 30) of A record addresses.  This number of A record
  entries is possible in some cases of load balancing.  When the DNS UDP
  response size is larger than 512 bytes, the server puts as much response
  as it can into the DNS UDP response, and marks the "trunacted" flag.
  This lets glibc know that the DNS UDP packet did not contain the entire
  response for all the A records.
  
  When glibc sees a UDP response that is "trunacted", by default it
  ignores the contents of that response and issues a new DNS query, using
  TCP instead of UDP.  The TCP packet size has a higher size limit (though
  see bug 1804487 which is a bug in systemd's max-sizing of TCP DNS
  packets), and so *should* allow glibc to receive the entire DNS
  response.
  
  However, glibc issues DNS queries for both A and AAAA records.  When it
  uses UDP, those DNS queries are separate (i.e. one UDP DNS packet with a
  single A query, and one UDP DNS packet with a single AAAA query).  When
  glibc uses TCP, it puts both DNS queries into a single TCP DNS packet -
  the RFC refers to this as "pipelining"
  (https://tools.ietf.org/html/rfc7766#section-6.2.1.1) and states that
  clients SHOULD do this, and that servers MUST expect to receive
  pipelined queries and SHOULD respond to all of them.  (Technically
  pipelining can be separate DNS queries, one per TCP packet, but both
  using the same TCP connection - but the clear intention of pipelining is
  to improve TCP performance, and putting both DNS queries into a single
  TCP packet is clearly more performant than using separate TCP packets).
  
  Unfortunately, systemd's local stub resolver has only very basic support
  for TCP DNS, and it handles TCP DNS queries almost identically to UDP
  DNS queries - it reads the DNS query 2-byte header (containing the
  length of the query data), reads in the single DNS query data, performs
  lookup and sends a response to that DNS query, and closes the TCP
  connection.  It does not check for "pipelined" queries in the TCP
  connection.
  
  That would be bad enough, as glibc is (rightly) expecting a response to
  both its A and AAAA queries; however what glibc gets is a TCP
  connection-reset error.  That is because the local systemd stub resolver
  has closed its TCP socket while input data was still pending (i.e. it
  never even read the second pipelined DNS query).  When the kernel sees
  unread input bytes in a TCP connection that is closed, it sends a TCP
  RST to the peer (i.e. glibc) and when the kernel sees the RST, it dumps
  all data in its socket buffer and passes the ECONNRESET error up to the
  application.  So glibc gets nothing besides a connection reset error.
  
  Note also that even if the systemd local stub resolver's socket flushes
  its input buffer before closing the TCP connection (which will avoid the
  TCP RST), glibc still expects responses to both its A and AAAA queries
  before systemd closes the TCP connection, and so a simple change to
  systemd to flush the input buffer is not enough to fix the bug (and
  would also not actually fix the bug since glibc would never get the AAAA
  response).
  
  [Test Case]
  
  This can be reproduced on any system using a local systemd stub
  resolver, when using an application that uses getaddrinfo() - such as
  ssh, telnet, ping, etc - or with a simple C program that uses
  getaddrinfo().  The dns name looked up must have enough A records to
  overflow the 512 byte maximum for a UDP DNS packet.
  
  Alternately, and trivially, glibc can be forced to always use TCP DNS queries by editing the /etc/resolv.conf file and adding:
  options use-vc
  
  With that option, glibc will fail to lookup 100% of DNS names, since all
  lookups will use TCP to talk to the local systemd stub resolver, which
  as explained above fails to ever correctly answer glibc's pipelined TCP
  DNS queries.
  
  Note that in default Ubuntu installs, /etc/resolv.conf is a symlink to
  ../run/systemd/resolve/stub-resolv.conf, which systemd thinks it owns
  100% - so any manual changes to the file may be overwritten at any time.
  There is no way (that I can find) to tell systemd to add any resolv.conf
  options (like 'use-vc') to its managed stub-resolv.conf file, so this
  test case requires re-editing the /etc/resolv.conf file intermittently,
  each time systemd overwrites it.
  
  Note also that the patch used to work around this (see Other Info below)
  will fix the case of lookup failures for very long A records; but the
  workaround will not help at all with the test case of using 'option use-
  vc'.  That test case will continue to fail for 100% of dns lookups.
  
  [Regression Potential]
  
  To workaround this, the patch enables edns0 in systemd's stub resolver
  resolv.conf file.  This could cause problems for any system code that
  does not expect the resolv.conf file to include a new line/option, or
  could introduce problems with edns0 lookups, since glibc was not
  previously using edns0.
  
  [Other Info]
  
  This bug exists upstream, with proposed patches to add dns tcp pipeline support:
  https://github.com/systemd/systemd/pull/11512
  
  The specific bug of TCP DNS fallback not working for DNS responses larger than 512 bytes can be worked around by editing the /etc/resolv.conf file to add:
  options edns0
  
  The EDNS0 option causes glibc to fall back to attempting UDP EDNS0 query
  (which has a higher max packet size than the default 512 byte UDP DNS).
  The systemd stub resolver does support EDNS0.  However, this workaround
  only temporarily works - as explained above, by default /etc/resolv.conf
  is a symlink to a file that systemd overwrites intermittently, which
  will remove the EDNS0 option.
  
  The upstream patch that will be used to work around this bug in exactly that way (i.e. adding option edns0 to resolv.conf) is:
  https://github.com/systemd/systemd/commit/93158c77bc69fde7cf5cff733617631c1e566fe8
  
  That patch is already included in Debian and so no Debian bug is
  required for this bug (since the only fix for this specific bug will be
  sru'ing the edns0 workaround)
+ 
+ Since Xenial and Trusty do not use the systemd stub resolver (by
+ default) I marked this Invalid for those releases.

** Description changed:

  [Impact]
  
  The systemd local 'stub' resolver handles all local DNS queries (by
  default configuration used in Ubuntu), and essentially proxies all
  requests to its configured upstream DNS resolvers.
  
  Most local DNS resolution by applications uses glibc's getaddrinfo()
  function.  This function is configured in various ways by the
  /etc/resolv.conf file, which tells glibc what nameserver/resolver to
  contact as well as how to talk to the name server.
  
  By default, glibc performs UDP DNS queries, with a single DNS query per
  UDP packet.  The UDP packet size is limited per DNS spec to 512 bytes.
  For some DNS lookups, a 512 byte UDP packet is not large enough to
  contain the entire response - for example, an A record lookup with a
  large number (e.g. 30) of A record addresses.  This number of A record
  entries is possible in some cases of load balancing.  When the DNS UDP
  response size is larger than 512 bytes, the server puts as much response
  as it can into the DNS UDP response, and marks the "trunacted" flag.
  This lets glibc know that the DNS UDP packet did not contain the entire
  response for all the A records.
  
  When glibc sees a UDP response that is "trunacted", by default it
  ignores the contents of that response and issues a new DNS query, using
  TCP instead of UDP.  The TCP packet size has a higher size limit (though
  see bug 1804487 which is a bug in systemd's max-sizing of TCP DNS
  packets), and so *should* allow glibc to receive the entire DNS
  response.
  
  However, glibc issues DNS queries for both A and AAAA records.  When it
  uses UDP, those DNS queries are separate (i.e. one UDP DNS packet with a
  single A query, and one UDP DNS packet with a single AAAA query).  When
  glibc uses TCP, it puts both DNS queries into a single TCP DNS packet -
  the RFC refers to this as "pipelining"
  (https://tools.ietf.org/html/rfc7766#section-6.2.1.1) and states that
  clients SHOULD do this, and that servers MUST expect to receive
  pipelined queries and SHOULD respond to all of them.  (Technically
  pipelining can be separate DNS queries, one per TCP packet, but both
  using the same TCP connection - but the clear intention of pipelining is
  to improve TCP performance, and putting both DNS queries into a single
  TCP packet is clearly more performant than using separate TCP packets).
  
  Unfortunately, systemd's local stub resolver has only very basic support
  for TCP DNS, and it handles TCP DNS queries almost identically to UDP
  DNS queries - it reads the DNS query 2-byte header (containing the
  length of the query data), reads in the single DNS query data, performs
  lookup and sends a response to that DNS query, and closes the TCP
  connection.  It does not check for "pipelined" queries in the TCP
  connection.
  
  That would be bad enough, as glibc is (rightly) expecting a response to
  both its A and AAAA queries; however what glibc gets is a TCP
  connection-reset error.  That is because the local systemd stub resolver
  has closed its TCP socket while input data was still pending (i.e. it
  never even read the second pipelined DNS query).  When the kernel sees
  unread input bytes in a TCP connection that is closed, it sends a TCP
  RST to the peer (i.e. glibc) and when the kernel sees the RST, it dumps
  all data in its socket buffer and passes the ECONNRESET error up to the
  application.  So glibc gets nothing besides a connection reset error.
  
  Note also that even if the systemd local stub resolver's socket flushes
  its input buffer before closing the TCP connection (which will avoid the
  TCP RST), glibc still expects responses to both its A and AAAA queries
  before systemd closes the TCP connection, and so a simple change to
  systemd to flush the input buffer is not enough to fix the bug (and
  would also not actually fix the bug since glibc would never get the AAAA
  response).
  
  [Test Case]
  
  This can be reproduced on any system using a local systemd stub
  resolver, when using an application that uses getaddrinfo() - such as
  ssh, telnet, ping, etc - or with a simple C program that uses
  getaddrinfo().  The dns name looked up must have enough A records to
- overflow the 512 byte maximum for a UDP DNS packet.
+ overflow the 512 byte maximum for a UDP DNS packet; e.g.:
+ 
+ $ ping testing.irongiantdesign.com
+ ping: testing.irongiantdesign.com: Temporary failure in name resolution
  
  Alternately, and trivially, glibc can be forced to always use TCP DNS queries by editing the /etc/resolv.conf file and adding:
  options use-vc
  
  With that option, glibc will fail to lookup 100% of DNS names, since all
  lookups will use TCP to talk to the local systemd stub resolver, which
  as explained above fails to ever correctly answer glibc's pipelined TCP
  DNS queries.
  
  Note that in default Ubuntu installs, /etc/resolv.conf is a symlink to
  ../run/systemd/resolve/stub-resolv.conf, which systemd thinks it owns
  100% - so any manual changes to the file may be overwritten at any time.
  There is no way (that I can find) to tell systemd to add any resolv.conf
  options (like 'use-vc') to its managed stub-resolv.conf file, so this
  test case requires re-editing the /etc/resolv.conf file intermittently,
  each time systemd overwrites it.
  
  Note also that the patch used to work around this (see Other Info below)
  will fix the case of lookup failures for very long A records; but the
  workaround will not help at all with the test case of using 'option use-
  vc'.  That test case will continue to fail for 100% of dns lookups.
  
  [Regression Potential]
  
  To workaround this, the patch enables edns0 in systemd's stub resolver
  resolv.conf file.  This could cause problems for any system code that
  does not expect the resolv.conf file to include a new line/option, or
  could introduce problems with edns0 lookups, since glibc was not
  previously using edns0.
  
  [Other Info]
  
  This bug exists upstream, with proposed patches to add dns tcp pipeline support:
  https://github.com/systemd/systemd/pull/11512
  
  The specific bug of TCP DNS fallback not working for DNS responses larger than 512 bytes can be worked around by editing the /etc/resolv.conf file to add:
  options edns0
  
  The EDNS0 option causes glibc to fall back to attempting UDP EDNS0 query
  (which has a higher max packet size than the default 512 byte UDP DNS).
  The systemd stub resolver does support EDNS0.  However, this workaround
  only temporarily works - as explained above, by default /etc/resolv.conf
  is a symlink to a file that systemd overwrites intermittently, which
  will remove the EDNS0 option.
  
  The upstream patch that will be used to work around this bug in exactly that way (i.e. adding option edns0 to resolv.conf) is:
  https://github.com/systemd/systemd/commit/93158c77bc69fde7cf5cff733617631c1e566fe8
  
  That patch is already included in Debian and so no Debian bug is
  required for this bug (since the only fix for this specific bug will be
  sru'ing the edns0 workaround)
  
  Since Xenial and Trusty do not use the systemd stub resolver (by
  default) I marked this Invalid for those releases.

-- 
You received this bug notification because you are a member of नेपाली
भाषा समायोजकहरुको समूह, which is subscribed to Xenial.
Matching subscriptions: Ubuntu 16.04 Bugs
https://bugs.launchpad.net/bugs/1811471

Title:
  local resolver stub fails to handle multiple TCP dns queries

Status in systemd:
  New
Status in systemd package in Ubuntu:
  In Progress
Status in systemd source package in Trusty:
  Invalid
Status in systemd source package in Xenial:
  Invalid
Status in systemd source package in Bionic:
  In Progress
Status in systemd source package in Cosmic:
  In Progress
Status in systemd source package in Disco:
  In Progress

Bug description:
  [Impact]

  The systemd local 'stub' resolver handles all local DNS queries (by
  default configuration used in Ubuntu), and essentially proxies all
  requests to its configured upstream DNS resolvers.

  Most local DNS resolution by applications uses glibc's getaddrinfo()
  function.  This function is configured in various ways by the
  /etc/resolv.conf file, which tells glibc what nameserver/resolver to
  contact as well as how to talk to the name server.

  By default, glibc performs UDP DNS queries, with a single DNS query
  per UDP packet.  The UDP packet size is limited per DNS spec to 512
  bytes.  For some DNS lookups, a 512 byte UDP packet is not large
  enough to contain the entire response - for example, an A record
  lookup with a large number (e.g. 30) of A record addresses.  This
  number of A record entries is possible in some cases of load
  balancing.  When the DNS UDP response size is larger than 512 bytes,
  the server puts as much response as it can into the DNS UDP response,
  and marks the "trunacted" flag.  This lets glibc know that the DNS UDP
  packet did not contain the entire response for all the A records.

  When glibc sees a UDP response that is "trunacted", by default it
  ignores the contents of that response and issues a new DNS query,
  using TCP instead of UDP.  The TCP packet size has a higher size limit
  (though see bug 1804487 which is a bug in systemd's max-sizing of TCP
  DNS packets), and so *should* allow glibc to receive the entire DNS
  response.

  However, glibc issues DNS queries for both A and AAAA records.  When
  it uses UDP, those DNS queries are separate (i.e. one UDP DNS packet
  with a single A query, and one UDP DNS packet with a single AAAA
  query).  When glibc uses TCP, it puts both DNS queries into a single
  TCP DNS packet - the RFC refers to this as "pipelining"
  (https://tools.ietf.org/html/rfc7766#section-6.2.1.1) and states that
  clients SHOULD do this, and that servers MUST expect to receive
  pipelined queries and SHOULD respond to all of them.  (Technically
  pipelining can be separate DNS queries, one per TCP packet, but both
  using the same TCP connection - but the clear intention of pipelining
  is to improve TCP performance, and putting both DNS queries into a
  single TCP packet is clearly more performant than using separate TCP
  packets).

  Unfortunately, systemd's local stub resolver has only very basic
  support for TCP DNS, and it handles TCP DNS queries almost identically
  to UDP DNS queries - it reads the DNS query 2-byte header (containing
  the length of the query data), reads in the single DNS query data,
  performs lookup and sends a response to that DNS query, and closes the
  TCP connection.  It does not check for "pipelined" queries in the TCP
  connection.

  That would be bad enough, as glibc is (rightly) expecting a response
  to both its A and AAAA queries; however what glibc gets is a TCP
  connection-reset error.  That is because the local systemd stub
  resolver has closed its TCP socket while input data was still pending
  (i.e. it never even read the second pipelined DNS query).  When the
  kernel sees unread input bytes in a TCP connection that is closed, it
  sends a TCP RST to the peer (i.e. glibc) and when the kernel sees the
  RST, it dumps all data in its socket buffer and passes the ECONNRESET
  error up to the application.  So glibc gets nothing besides a
  connection reset error.

  Note also that even if the systemd local stub resolver's socket
  flushes its input buffer before closing the TCP connection (which will
  avoid the TCP RST), glibc still expects responses to both its A and
  AAAA queries before systemd closes the TCP connection, and so a simple
  change to systemd to flush the input buffer is not enough to fix the
  bug (and would also not actually fix the bug since glibc would never
  get the AAAA response).

  [Test Case]

  This can be reproduced on any system using a local systemd stub
  resolver, when using an application that uses getaddrinfo() - such as
  ssh, telnet, ping, etc - or with a simple C program that uses
  getaddrinfo().  The dns name looked up must have enough A records to
  overflow the 512 byte maximum for a UDP DNS packet; e.g.:

  $ ping testing.irongiantdesign.com
  ping: testing.irongiantdesign.com: Temporary failure in name resolution

  Alternately, and trivially, glibc can be forced to always use TCP DNS queries by editing the /etc/resolv.conf file and adding:
  options use-vc

  With that option, glibc will fail to lookup 100% of DNS names, since
  all lookups will use TCP to talk to the local systemd stub resolver,
  which as explained above fails to ever correctly answer glibc's
  pipelined TCP DNS queries.

  Note that in default Ubuntu installs, /etc/resolv.conf is a symlink to
  ../run/systemd/resolve/stub-resolv.conf, which systemd thinks it owns
  100% - so any manual changes to the file may be overwritten at any
  time.  There is no way (that I can find) to tell systemd to add any
  resolv.conf options (like 'use-vc') to its managed stub-resolv.conf
  file, so this test case requires re-editing the /etc/resolv.conf file
  intermittently, each time systemd overwrites it.

  Note also that the patch used to work around this (see Other Info
  below) will fix the case of lookup failures for very long A records;
  but the workaround will not help at all with the test case of using
  'option use-vc'.  That test case will continue to fail for 100% of dns
  lookups.

  [Regression Potential]

  To workaround this, the patch enables edns0 in systemd's stub resolver
  resolv.conf file.  This could cause problems for any system code that
  does not expect the resolv.conf file to include a new line/option, or
  could introduce problems with edns0 lookups, since glibc was not
  previously using edns0.

  [Other Info]

  This bug exists upstream, with proposed patches to add dns tcp pipeline support:
  https://github.com/systemd/systemd/pull/11512

  The specific bug of TCP DNS fallback not working for DNS responses larger than 512 bytes can be worked around by editing the /etc/resolv.conf file to add:
  options edns0

  The EDNS0 option causes glibc to fall back to attempting UDP EDNS0
  query (which has a higher max packet size than the default 512 byte
  UDP DNS).  The systemd stub resolver does support EDNS0.  However,
  this workaround only temporarily works - as explained above, by
  default /etc/resolv.conf is a symlink to a file that systemd
  overwrites intermittently, which will remove the EDNS0 option.

  The upstream patch that will be used to work around this bug in exactly that way (i.e. adding option edns0 to resolv.conf) is:
  https://github.com/systemd/systemd/commit/93158c77bc69fde7cf5cff733617631c1e566fe8

  That patch is already included in Debian and so no Debian bug is
  required for this bug (since the only fix for this specific bug will
  be sru'ing the edns0 workaround)

  Since Xenial and Trusty do not use the systemd stub resolver (by
  default) I marked this Invalid for those releases.

To manage notifications about this bug go to:
https://bugs.launchpad.net/systemd/+bug/1811471/+subscriptions