[libvirt] [PATCH v2 0/5] network: Add support for local PTR domains
I run a system dnsmasq to be able to forward specific DNS requests to specific servers. And I have it configured for both forward and reverse lookups. Another dnsmasq is started for a virtual network with domain "virt". The system dnsmasq knows it needs to forward any requests for the "virt" domain and corresponding PTR domain to the dnsmasq started by libvirt. The problem is dnsmasq forwards queries for unknown names to the upstream name server (which is the system instance in my case). One can get nice endless loops of DNS requests pretty easily. Forward loops can be avoided by specifying localOnly='yes', but there was no way to avoid reverse lookup loops. And this is what I'm trying to address in the following patches. Version 2: - RNG schema changes and tests Jiri Denemark (5): tests: Check more network XMLs for schema compliance schema: Let elements in /network/ip be specified in any order conf: Make virNetworkIPDefParseXML a little bit saner util: Introduce virSocketAddrPTRDomain network: Add support for local PTR domains docs/formatnetwork.html.in | 37 ++++++-- docs/schemas/basictypes.rng | 6 ++ docs/schemas/network.rng | 92 ++++++++++++-------- src/conf/network_conf.c | 108 +++++++++++++++++++----- src/conf/network_conf.h | 4 + src/libvirt_private.syms | 1 + src/network/bridge_driver.c | 47 +++++++++++ src/util/virsocketaddr.c | 85 +++++++++++++++++++ src/util/virsocketaddr.h | 9 ++ tests/networkxml2confdata/dhcp6-nat-network.xml | 2 +- tests/networkxml2confdata/netboot-network.xml | 2 +- tests/networkxml2confdata/ptr-domains-auto.conf | 20 +++++ tests/networkxml2confdata/ptr-domains-auto.xml | 21 +++++ tests/networkxml2confdata/ptr-domains.conf | 24 ++++++ tests/networkxml2confdata/ptr-domains.xml | 24 ++++++ tests/networkxml2conftest.c | 2 + tests/virschematest.c | 2 +- 17 files changed, 418 insertions(+), 68 deletions(-) create mode 100644 tests/networkxml2confdata/ptr-domains-auto.conf create mode 100644 tests/networkxml2confdata/ptr-domains-auto.xml create mode 100644 tests/networkxml2confdata/ptr-domains.conf create mode 100644 tests/networkxml2confdata/ptr-domains.xml -- 2.11.0
This revealed bugs in RNG schema for /network/dns/srv. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/schemas/network.rng | 8 ++++++++ tests/virschematest.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 1a18e64b2..9d8c803ff 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -283,13 +283,21 @@ </attribute> <optional> <attribute name="domain"><ref name="dnsName"/></attribute> + </optional> + <optional> <attribute name="target"><text/></attribute> + </optional> + <optional> <attribute name="port"> <ref name="unsignedShort"/> </attribute> + </optional> + <optional> <attribute name="priority"> <ref name="unsignedShort"/> </attribute> + </optional> + <optional> <attribute name="weight"> <ref name="unsignedShort"/> </attribute> diff --git a/tests/virschematest.c b/tests/virschematest.c index b31a5bca9..faf66d642 100644 --- a/tests/virschematest.c +++ b/tests/virschematest.c @@ -208,7 +208,7 @@ mymain(void) "domainsnapshotxml2xmlout"); DO_TEST("interface.rng", "interfaceschemadata"); DO_TEST("network.rng", "../src/network", "networkxml2xmlin", - "networkxml2xmlout"); + "networkxml2xmlout", "networkxml2confdata"); DO_TEST("nodedev.rng", "nodedevschemadata"); DO_TEST("nwfilter.rng", "nwfilterxml2xmlout"); DO_TEST("secret.rng", "secretxml2xmlin"); -- 2.11.0
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
This revealed bugs in RNG schema for /network/dns/srv.
ACK.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/schemas/network.rng | 8 ++++++++ tests/virschematest.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 1a18e64b2..9d8c803ff 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -283,13 +283,21 @@ </attribute> <optional> <attribute name="domain"><ref name="dnsName"/></attribute> + </optional> + <optional> <attribute name="target"><text/></attribute> + </optional> + <optional> <attribute name="port"> <ref name="unsignedShort"/> </attribute> + </optional> + <optional> <attribute name="priority"> <ref name="unsignedShort"/> </attribute> + </optional> + <optional> <attribute name="weight"> <ref name="unsignedShort"/> </attribute> diff --git a/tests/virschematest.c b/tests/virschematest.c index b31a5bca9..faf66d642 100644 --- a/tests/virschematest.c +++ b/tests/virschematest.c @@ -208,7 +208,7 @@ mymain(void) "domainsnapshotxml2xmlout"); DO_TEST("interface.rng", "interfaceschemadata"); DO_TEST("network.rng", "../src/network", "networkxml2xmlin", - "networkxml2xmlout"); + "networkxml2xmlout", "networkxml2confdata"); DO_TEST("nodedev.rng", "nodedevschemadata"); DO_TEST("nwfilter.rng", "nwfilterxml2xmlout"); DO_TEST("secret.rng", "secretxml2xmlin");
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/schemas/network.rng | 80 +++++++++++++------------ tests/networkxml2confdata/dhcp6-nat-network.xml | 2 +- tests/networkxml2confdata/netboot-network.xml | 2 +- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 9d8c803ff..986119596 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -339,48 +339,52 @@ <optional> <attribute name="family"><ref name="addr-family"/></attribute> </optional> - <optional> - <element name="tftp"> - <attribute name="root"><text/></attribute> - </element> - </optional> - <optional> - <!-- Define the range(s) of IP addresses that the DHCP - server should hand out --> - <element name="dhcp"> - <zeroOrMore> - <element name="range"> - <attribute name="start"><ref name="ipAddr"/></attribute> - <attribute name="end"><ref name="ipAddr"/></attribute> - </element> - </zeroOrMore> - <zeroOrMore> - <element name="host"> - <choice> - <group> + <interleave> + <optional> + <element name="tftp"> + <attribute name="root"><text/></attribute> + </element> + </optional> + <optional> + <!-- Define the range(s) of IP addresses that the DHCP + server should hand out --> + <element name="dhcp"> + <interleave> + <zeroOrMore> + <element name="range"> + <attribute name="start"><ref name="ipAddr"/></attribute> + <attribute name="end"><ref name="ipAddr"/></attribute> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="host"> <choice> - <attribute name="mac"><ref name="uniMacAddr"/></attribute> - <attribute name="id"><ref name="DUID"/></attribute> - </choice> - <optional> + <group> + <choice> + <attribute name="mac"><ref name="uniMacAddr"/></attribute> + <attribute name="id"><ref name="DUID"/></attribute> + </choice> + <optional> + <attribute name="name"><text/></attribute> + </optional> + </group> <attribute name="name"><text/></attribute> - </optional> - </group> - <attribute name="name"><text/></attribute> - </choice> - <attribute name="ip"><ref name="ipAddr"/></attribute> - </element> - </zeroOrMore> - <optional> - <element name="bootp"> - <attribute name="file"><ref name="filePath"/></attribute> + </choice> + <attribute name="ip"><ref name="ipAddr"/></attribute> + </element> + </zeroOrMore> <optional> - <attribute name="server"><ref name="dnsName"/></attribute> + <element name="bootp"> + <attribute name="file"><ref name="filePath"/></attribute> + <optional> + <attribute name="server"><ref name="dnsName"/></attribute> + </optional> + </element> </optional> - </element> - </optional> - </element> - </optional> + </interleave> + </element> + </optional> + </interleave> </element> </zeroOrMore> <!-- <route> element --> diff --git a/tests/networkxml2confdata/dhcp6-nat-network.xml b/tests/networkxml2confdata/dhcp6-nat-network.xml index a67bde8f3..cab0700f2 100644 --- a/tests/networkxml2confdata/dhcp6-nat-network.xml +++ b/tests/networkxml2confdata/dhcp6-nat-network.xml @@ -5,9 +5,9 @@ <bridge name='virbr0' stp='on' delay='0'/> <ip address='192.168.122.1' netmask='255.255.255.0'> <dhcp> - <range start='192.168.122.2' end='192.168.122.254'/> <host mac='00:16:3e:77:e2:ed' name='a.example.com' ip='192.168.122.10'/> <host mac='00:16:3e:3e:a9:1a' name='b.example.com' ip='192.168.122.11'/> + <range start='192.168.122.2' end='192.168.122.254'/> </dhcp> </ip> <ip family='ipv4' address='192.168.123.1' netmask='255.255.255.0'> diff --git a/tests/networkxml2confdata/netboot-network.xml b/tests/networkxml2confdata/netboot-network.xml index 8d5ad31a0..ec06c3005 100644 --- a/tests/networkxml2confdata/netboot-network.xml +++ b/tests/networkxml2confdata/netboot-network.xml @@ -5,10 +5,10 @@ <bridge name='virbr1' stp='off' delay='1'/> <domain name='example.com'/> <ip address='192.168.122.1' netmask='255.255.255.0'> - <tftp root='/var/lib/tftproot'/> <dhcp> <range start='192.168.122.2' end='192.168.122.254'/> <bootp file='pxeboot.img'/> </dhcp> + <tftp root='/var/lib/tftproot'/> </ip> </network> -- 2.11.0
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/schemas/network.rng | 80 +++++++++++++------------ tests/networkxml2confdata/dhcp6-nat-network.xml | 2 +- tests/networkxml2confdata/netboot-network.xml | 2 +- 3 files changed, 44 insertions(+), 40 deletions(-)
diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 9d8c803ff..986119596 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -339,48 +339,52 @@ <optional> <attribute name="family"><ref name="addr-family"/></attribute> </optional> - <optional> - <element name="tftp"> - <attribute name="root"><text/></attribute> - </element> - </optional> - <optional> - <!-- Define the range(s) of IP addresses that the DHCP - server should hand out --> - <element name="dhcp"> - <zeroOrMore> - <element name="range"> - <attribute name="start"><ref name="ipAddr"/></attribute> - <attribute name="end"><ref name="ipAddr"/></attribute> - </element> - </zeroOrMore> - <zeroOrMore> - <element name="host"> - <choice> - <group> + <interleave> + <optional> + <element name="tftp"> + <attribute name="root"><text/></attribute> + </element> + </optional> + <optional> + <!-- Define the range(s) of IP addresses that the DHCP + server should hand out --> + <element name="dhcp"> + <interleave> + <zeroOrMore> + <element name="range"> + <attribute name="start"><ref name="ipAddr"/></attribute> + <attribute name="end"><ref name="ipAddr"/></attribute> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="host">
[etc] ACK ("git log -p -w" is my friend of the day :-)
Iterating over all child nodes when we only support one instance of each child is pretty weired. And it would even cause memory leaks if more than one <tftp> element was specified. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/conf/network_conf.c | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index aabf315c9..b6849ceab 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1501,7 +1501,8 @@ virNetworkIPDefParseXML(const char *networkName, * On failure clear it out, but don't free it. */ - xmlNodePtr cur, save; + xmlNodePtr save; + xmlNodePtr dhcp; char *address = NULL, *netmask = NULL; unsigned long prefix = 0; int prefixRc; @@ -1603,29 +1604,20 @@ virNetworkIPDefParseXML(const char *networkName, goto cleanup; } - cur = node->children; - while (cur != NULL) { - if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "dhcp")) { - if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0) - goto cleanup; - } else if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "tftp")) { - char *root; + if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) && + virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0) + goto cleanup; - if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("Unsupported <tftp> element in an IPv6 element in network '%s'"), - networkName); - goto cleanup; - } - if (!(root = virXMLPropString(cur, "root"))) { - cur = cur->next; - continue; - } - def->tftproot = (char *)root; + if (virXPathNode("./tftp[1]", ctxt)) { + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Unsupported <tftp> element in an IPv6 element " + "in network '%s'"), + networkName); + goto cleanup; } - cur = cur->next; + + def->tftproot = virXPathString("string(./tftp[1]/@root)", ctxt); } result = 0; -- 2.11.0
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
Iterating over all child nodes when we only support one instance of each child is pretty weired. And it would even cause memory leaks if more than one <tftp> element was specified.
ACK, but could you also look for dhcp[2]/tftp[2] and log an error if found? I know there are *lots* of places we ignore extra elements in the XML, but in this case there would be a silent behavior change if someone had (erroneous) multiple tftp or dhcp elements - previously we would have honored the final occurence of each element, but now we honor the first. So even though it's their own fault, it would be nice to (BTW, I've always disliked that some of our XML parsing code iterates through the raw nodes like this used to, and some uses XPath to get specific nodes. It would be so much easier for newcomers to understand if we picked on method and used it consistently...)
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/conf/network_conf.c | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index aabf315c9..b6849ceab 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1501,7 +1501,8 @@ virNetworkIPDefParseXML(const char *networkName, * On failure clear it out, but don't free it. */
- xmlNodePtr cur, save; + xmlNodePtr save; + xmlNodePtr dhcp; char *address = NULL, *netmask = NULL; unsigned long prefix = 0; int prefixRc; @@ -1603,29 +1604,20 @@ virNetworkIPDefParseXML(const char *networkName, goto cleanup; }
- cur = node->children; - while (cur != NULL) { - if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "dhcp")) { - if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0) - goto cleanup; - } else if (cur->type == XML_ELEMENT_NODE && - xmlStrEqual(cur->name, BAD_CAST "tftp")) { - char *root; + if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) && + virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0) + goto cleanup;
- if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("Unsupported <tftp> element in an IPv6 element in network '%s'"), - networkName); - goto cleanup; - } - if (!(root = virXMLPropString(cur, "root"))) { - cur = cur->next; - continue; - } - def->tftproot = (char *)root; + if (virXPathNode("./tftp[1]", ctxt)) { + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Unsupported <tftp> element in an IPv6 element " + "in network '%s'"), + networkName); + goto cleanup; } - cur = cur->next; + + def->tftproot = virXPathString("string(./tftp[1]/@root)", ctxt); }
result = 0;
On Wed, Dec 14, 2016 at 10:46:20 -0500, Laine Stump wrote:
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
Iterating over all child nodes when we only support one instance of each child is pretty weired. And it would even cause memory leaks if more than one <tftp> element was specified.
ACK, but could you also look for dhcp[2]/tftp[2] and log an error if found? I know there are *lots* of places we ignore extra elements in the XML, but in this case there would be a silent behavior change if someone had (erroneous) multiple tftp or dhcp elements - previously we would have honored the final occurence of each element, but now we honor the first. So even though it's their own fault, it would be nice to
Well, if a user provided such a wrong XML, libvirt didn't do what the user asked for anyway. I don't think implementing a different way of not doing what they asked for is something we should worry about :-) That said, adding the check there is trivial, but I'm not sure it's a good idea since we do not check for extra elements anywhere else.
(BTW, I've always disliked that some of our XML parsing code iterates through the raw nodes like this used to, and some uses XPath to get specific nodes. It would be so much easier for newcomers to understand if we picked on method and used it consistently...)
Yes, I think we'll eventually rewrite all parsing to use XPath, but it will take some time. Jirka
On 12/16/2016 11:39 AM, Jiri Denemark wrote:
On Wed, Dec 14, 2016 at 10:46:20 -0500, Laine Stump wrote:
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
Iterating over all child nodes when we only support one instance of each child is pretty weired. And it would even cause memory leaks if more than one <tftp> element was specified. ACK, but could you also look for dhcp[2]/tftp[2] and log an error if found? I know there are *lots* of places we ignore extra elements in the XML, but in this case there would be a silent behavior change if someone had (erroneous) multiple tftp or dhcp elements - previously we would have honored the final occurence of each element, but now we honor the first. So even though it's their own fault, it would be nice to Well, if a user provided such a wrong XML, libvirt didn't do what the user asked for anyway. I don't think implementing a different way of not doing what they asked for is something we should worry about :-)
That said, adding the check there is trivial, but I'm not sure it's a good idea since we do not check for extra elements anywhere else.
I retract this suggestion, since doing as I suggest would cause the code to now *fail* to parse XML that previously passed, which is much worse that just failing in a different manner than it had previously failed. I still think this should be taken care of, but in a separate patch that modifies the validation at network define time (so that re-parsing of existing networks won't fail)
The API creates PTR domain which corresponds to a given addr/prefix. Both IPv4 and IPv6 addresses are supported, but the prefix must be divisible by 8 for IPv4 and divisible by 4 for IPv6. The generated PTR domain has the following format IPv4: 1.2.3.4.in-addr.arpa IPv6: 0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.ip6.arpa Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virsocketaddr.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/virsocketaddr.h | 9 +++++ 3 files changed, 95 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index ffbf46c1a..27c1edaef 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2391,6 +2391,7 @@ virSocketAddrParse; virSocketAddrParseIPv4; virSocketAddrParseIPv6; virSocketAddrPrefixToNetmask; +virSocketAddrPTRDomain; virSocketAddrSetIPv4Addr; virSocketAddrSetIPv4AddrNetOrder; virSocketAddrSetIPv6Addr; diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index 33b1e9e1f..41f75d5c2 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -27,6 +27,7 @@ #include "virerror.h" #include "virstring.h" #include "viralloc.h" +#include "virbuffer.h" #include <netdb.h> @@ -40,6 +41,8 @@ typedef unsigned char virSocketAddrIPv4[4]; typedef virSocketAddrIPv4 *virSocketAddrIPv4Ptr; typedef unsigned short virSocketAddrIPv6[8]; typedef virSocketAddrIPv6 *virSocketAddrIPv6Ptr; +typedef unsigned char virSocketAddrIPv6Nibbles[32]; +typedef virSocketAddrIPv6Nibbles *virSocketAddrIPv6NibblesPtr; static int virSocketAddrGetIPv4Addr(const virSocketAddr *addr, @@ -78,6 +81,23 @@ virSocketAddrGetIPv6Addr(const virSocketAddr *addr, virSocketAddrIPv6Ptr tab) } static int +virSocketAddrGetIPv6Nibbles(const virSocketAddr *addr, + virSocketAddrIPv6NibblesPtr tab) +{ + size_t i; + + if (!addr || !tab || addr->data.stor.ss_family != AF_INET6) + return -1; + + for (i = 0; i < 16; i++) { + (*tab)[2 * i] = addr->data.inet6.sin6_addr.s6_addr[i] >> 4; + (*tab)[2 * i + 1] = addr->data.inet6.sin6_addr.s6_addr[i] & 0xF; + } + + return 0; +} + +static int virSocketAddrParseInternal(struct addrinfo **res, const char *val, int family, @@ -1118,3 +1138,68 @@ virSocketAddrIsNumericLocalhost(const char *addr) return false; } + + +/** + * virSocketAddrPTRDomain: + * + * Create PTR domain which corresponds to @addr/@prefix. Both IPv4 and IPv6 + * addresses are supported, but @prefix must be divisible by 8 for IPv4 and + * divisible by 4 for IPv6, otherwise -2 will be returned. + * + * Returns -2 if the PTR record cannot be automatically created, + * -1 on error, + * 0 on success. + */ +int +virSocketAddrPTRDomain(const virSocketAddr *addr, + unsigned int prefix, + char **ptr) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + size_t i; + int ret = -1; + + if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET)) { + virSocketAddrIPv4 ip; + + if (prefix == 0 || prefix >= 32 || prefix % 8 != 0) + goto unsupported; + + if (virSocketAddrGetIPv4Addr(addr, &ip) < 0) + goto cleanup; + + for (i = prefix / 8; i > 0; i--) + virBufferAsprintf(&buf, "%u.", ip[i - 1]); + + virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV4_ARPA); + } else if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET6)) { + virSocketAddrIPv6Nibbles ip; + + if (prefix == 0 || prefix >= 128 || prefix % 4 != 0) + goto unsupported; + + if (virSocketAddrGetIPv6Nibbles(addr, &ip) < 0) + goto cleanup; + + for (i = prefix / 4; i > 0; i--) + virBufferAsprintf(&buf, "%x.", ip[i - 1]); + + virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV6_ARPA); + } else { + goto unsupported; + } + + if (!(*ptr = virBufferContentAndReset(&buf))) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; + + unsupported: + ret = -2; + goto cleanup; +} diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index 7ee993b1a..43a370620 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -57,6 +57,9 @@ typedef struct { # define VIR_SOCKET_ADDR_IPV4_ALL "0.0.0.0" # define VIR_SOCKET_ADDR_IPV6_ALL "::" +# define VIR_SOCKET_ADDR_IPV4_ARPA "in-addr.arpa" +# define VIR_SOCKET_ADDR_IPV6_ARPA "ip6.arpa" + typedef virSocketAddr *virSocketAddrPtr; typedef struct _virSocketAddrRange virSocketAddrRange; @@ -136,4 +139,10 @@ bool virSocketAddrIsWildcard(const virSocketAddr *addr); int virSocketAddrNumericFamily(const char *address); bool virSocketAddrIsNumericLocalhost(const char *addr); + +int virSocketAddrPTRDomain(const virSocketAddr *addr, + unsigned int prefix, + char **ptr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); + #endif /* __VIR_SOCKETADDR_H__ */ -- 2.11.0
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
The API creates PTR domain which corresponds to a given addr/prefix. Both IPv4 and IPv6 addresses are supported, but the prefix must be divisible by 8 for IPv4 and divisible by 4 for IPv6.
The generated PTR domain has the following format
IPv4: 1.2.3.4.in-addr.arpa IPv6: 0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.ip6.arpa
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virsocketaddr.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/virsocketaddr.h | 9 +++++ 3 files changed, 95 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index ffbf46c1a..27c1edaef 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2391,6 +2391,7 @@ virSocketAddrParse; virSocketAddrParseIPv4; virSocketAddrParseIPv6; virSocketAddrPrefixToNetmask; +virSocketAddrPTRDomain; virSocketAddrSetIPv4Addr; virSocketAddrSetIPv4AddrNetOrder; virSocketAddrSetIPv6Addr; diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index 33b1e9e1f..41f75d5c2 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -27,6 +27,7 @@ #include "virerror.h" #include "virstring.h" #include "viralloc.h" +#include "virbuffer.h"
#include <netdb.h>
@@ -40,6 +41,8 @@ typedef unsigned char virSocketAddrIPv4[4]; typedef virSocketAddrIPv4 *virSocketAddrIPv4Ptr; typedef unsigned short virSocketAddrIPv6[8]; typedef virSocketAddrIPv6 *virSocketAddrIPv6Ptr; +typedef unsigned char virSocketAddrIPv6Nibbles[32]; +typedef virSocketAddrIPv6Nibbles *virSocketAddrIPv6NibblesPtr;
static int virSocketAddrGetIPv4Addr(const virSocketAddr *addr, @@ -78,6 +81,23 @@ virSocketAddrGetIPv6Addr(const virSocketAddr *addr, virSocketAddrIPv6Ptr tab) }
static int +virSocketAddrGetIPv6Nibbles(const virSocketAddr *addr, + virSocketAddrIPv6NibblesPtr tab) +{ + size_t i; + + if (!addr || !tab || addr->data.stor.ss_family != AF_INET6) + return -1; + + for (i = 0; i < 16; i++) { + (*tab)[2 * i] = addr->data.inet6.sin6_addr.s6_addr[i] >> 4; + (*tab)[2 * i + 1] = addr->data.inet6.sin6_addr.s6_addr[i] & 0xF; + } + + return 0; +}
It *could* have been done without this extra type and function, but it does make it easier to follow :-)
+ +static int virSocketAddrParseInternal(struct addrinfo **res, const char *val, int family, @@ -1118,3 +1138,68 @@ virSocketAddrIsNumericLocalhost(const char *addr)
return false; } + + +/** + * virSocketAddrPTRDomain: + * + * Create PTR domain which corresponds to @addr/@prefix. Both IPv4 and IPv6 + * addresses are supported, but @prefix must be divisible by 8 for IPv4 and + * divisible by 4 for IPv6, otherwise -2 will be returned. + * + * Returns -2 if the PTR record cannot be automatically created, + * -1 on error, + * 0 on success. + */ +int +virSocketAddrPTRDomain(const virSocketAddr *addr, + unsigned int prefix, + char **ptr) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + size_t i; + int ret = -1; + + if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET)) { + virSocketAddrIPv4 ip; + + if (prefix == 0 || prefix >= 32 || prefix % 8 != 0) + goto unsupported; + + if (virSocketAddrGetIPv4Addr(addr, &ip) < 0) + goto cleanup; + + for (i = prefix / 8; i > 0; i--) + virBufferAsprintf(&buf, "%u.", ip[i - 1]); + + virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV4_ARPA); + } else if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET6)) { + virSocketAddrIPv6Nibbles ip; + + if (prefix == 0 || prefix >= 128 || prefix % 4 != 0) + goto unsupported; + + if (virSocketAddrGetIPv6Nibbles(addr, &ip) < 0) + goto cleanup; + + for (i = prefix / 4; i > 0; i--) + virBufferAsprintf(&buf, "%x.", ip[i - 1]); + + virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV6_ARPA); + } else { + goto unsupported;
Okay, so you're not calling it "error" because it's only for a special kind of error. Unconventional, but okay.
+ } + + if (!(*ptr = virBufferContentAndReset(&buf))) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + return ret; + + unsupported: + ret = -2; + goto cleanup; +} diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index 7ee993b1a..43a370620 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -57,6 +57,9 @@ typedef struct { # define VIR_SOCKET_ADDR_IPV4_ALL "0.0.0.0" # define VIR_SOCKET_ADDR_IPV6_ALL "::"
+# define VIR_SOCKET_ADDR_IPV4_ARPA "in-addr.arpa" +# define VIR_SOCKET_ADDR_IPV6_ARPA "ip6.arpa" + typedef virSocketAddr *virSocketAddrPtr;
typedef struct _virSocketAddrRange virSocketAddrRange; @@ -136,4 +139,10 @@ bool virSocketAddrIsWildcard(const virSocketAddr *addr); int virSocketAddrNumericFamily(const char *address);
bool virSocketAddrIsNumericLocalhost(const char *addr); + +int virSocketAddrPTRDomain(const virSocketAddr *addr, + unsigned int prefix, + char **ptr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); + #endif /* __VIR_SOCKETADDR_H__ */
ACK.
Similarly to localOnly DNS domain, local PTR domains can be used to tell the DNS server not to forward reverse lookups for unknown IPs which belong to the virtual network. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/formatnetwork.html.in | 37 ++++++++++--- docs/schemas/basictypes.rng | 6 +++ docs/schemas/network.rng | 8 +++ src/conf/network_conf.c | 72 ++++++++++++++++++++++++- src/conf/network_conf.h | 4 ++ src/network/bridge_driver.c | 47 ++++++++++++++++ tests/networkxml2confdata/ptr-domains-auto.conf | 20 +++++++ tests/networkxml2confdata/ptr-domains-auto.xml | 21 ++++++++ tests/networkxml2confdata/ptr-domains.conf | 24 +++++++++ tests/networkxml2confdata/ptr-domains.xml | 24 +++++++++ tests/networkxml2conftest.c | 2 + 11 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 tests/networkxml2confdata/ptr-domains-auto.conf create mode 100644 tests/networkxml2confdata/ptr-domains-auto.xml create mode 100644 tests/networkxml2confdata/ptr-domains.conf create mode 100644 tests/networkxml2confdata/ptr-domains.xml diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index 9cf940052..3c9414779 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -855,7 +855,7 @@ <hostname>myhostalias</hostname> </host> </dns> -<ip address="192.168.122.1" netmask="255.255.255.0"> +<ip address="192.168.122.1" netmask="255.255.255.0" localPtr="yes"> <dhcp> <range start="192.168.122.100" end="192.168.122.254"/> <host mac="00:16:3e:77:e2:ed" name="foo.example.com" ip="192.168.122.10"/> @@ -863,6 +863,10 @@ </dhcp> </ip> <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64"/> +<ip family="ipv6" address="fec0::1" prefix="31" localPtr="yes"> + <ptr domain="0.0.0.0.0.c.e.f.ip6.arpa"/> + <ptr domain="1.0.0.0.0.c.e.f.ip6.arpa"/> +</ip> <route family="ipv6" address="2001:db9:ca1:1::" prefix="64" gateway="2001:db8:ca2:2::2"/> </pre> @@ -983,11 +987,20 @@ to specify the type of address — <code>ipv4</code> or <code>ipv6</code>; if no <code>family</code> is given, <code>ipv4</code> is assumed. More than one address of each family can - be defined for a network. The <code>ip</code> element is supported - <span class="since">since 0.3.0</span>. IPv6, multiple addresses on a - single network, <code>family</code>, and <code>prefix</code> are - supported <span class="since">since 0.8.7</span>. The <code>ip</code> - element may contain the following elements: + be defined for a network. The optional <code>localPtr</code> attribute + (<span class="since">since 3.0.0</span>) configures the DNS server not + to forward any reverse DNS requests for IP addresses from the network + configured by the <code>address</code> and + <code>netmask</code>/<code>prefix</code> attributes. For some unusual + network prefixes (not divisible by 8 for IPv4 or not divisible by 4 for + IPv6) libvirt may be unable to compute the PTR domain automatically, + in which case the domain has to be specified explicitly by the + <code>ptr</code> element described below. The <code>ip</code> element + is supported <span class="since">since 0.3.0</span>. IPv6, multiple + addresses on a single network, <code>family</code>, and + <code>prefix</code> are supported <span class="since">since + 0.8.7</span>. The <code>ip</code> element may contain the following + elements: <dl> <dt><code>tftp</code></dt> @@ -1051,6 +1064,18 @@ </dd> </dl> </dd> + + <dt><code>ptr</code></dt> + <dd>The <code>domain</code> attribute of the optional + <code>ptr</code> element specifies the PTR domain used for reverse + DNS lookups. The domain has to end with ".in-addr.arpa" for IPv4 + and ".ip6.arpa" for IPv6. Each <code>ip</code> element may contain + zero or more <code>ptr</code> elements. When + <code>localPtr='yes'</code> attribute is set in the parent + <code>ip</code> element the DNS server for this network will be + configured not to forward any reverse lookups within the specified + domains. <span class="since">Since 3.0.0</span> + </dd> </dl> </dd> </dl> diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng index 1b4f980e7..f99c76392 100644 --- a/docs/schemas/basictypes.rng +++ b/docs/schemas/basictypes.rng @@ -239,6 +239,12 @@ </data> </define> + <define name="ptrName"> + <data type="string"> + <param name="pattern">(((((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([1-9][0-9])|([0-9]))\.){1,3}in-addr)|((([0-9a-fA-F]\.){1,31})ip6)).arpa</param> + </data> + </define> + <define name="deviceName"> <data type="string"> <param name="pattern">[a-zA-Z0-9_\.\-\\:/]+</param> diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 986119596..1a3705a1f 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -339,6 +339,9 @@ <optional> <attribute name="family"><ref name="addr-family"/></attribute> </optional> + <optional> + <attribute name="localPtr"><ref name="virYesNo"/></attribute> + </optional> <interleave> <optional> <element name="tftp"> @@ -384,6 +387,11 @@ </interleave> </element> </optional> + <zeroOrMore> + <element name='ptr'> + <attribute name='domain'><ref name="ptrName"/></attribute> + </element> + </zeroOrMore> </interleave> </element> </zeroOrMore> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index b6849ceab..ce0bd0260 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -315,6 +315,10 @@ static void virNetworkIPDefClear(virNetworkIPDefPtr def) { VIR_FREE(def->family); + + virStringListFreeCount(def->ptrs, def->nptrs); + def->ptrs = NULL; + VIR_FREE(def->ranges); while (def->nhosts) @@ -1507,6 +1511,10 @@ virNetworkIPDefParseXML(const char *networkName, unsigned long prefix = 0; int prefixRc; int result = -1; + char *localPtr = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n; save = ctxt->node; ctxt->node = node; @@ -1549,6 +1557,17 @@ virNetworkIPDefParseXML(const char *networkName, else def->prefix = prefix; + localPtr = virXPathString("string(./@localPtr)", ctxt); + if (localPtr) { + def->localPTR = virTristateBoolTypeFromString(localPtr); + if (def->localPTR <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid localPtr value '%s' in network '%s'"), + localPtr, networkName); + goto cleanup; + } + } + /* validate address, etc. for each family */ if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) { if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || @@ -1604,6 +1623,46 @@ virNetworkIPDefParseXML(const char *networkName, goto cleanup; } + if ((n = virXPathNodeSet("./ptr", ctxt, &nodes)) < 0) + goto cleanup; + + if (n > 0) { + if (VIR_ALLOC_N(def->ptrs, n) < 0) + goto cleanup; + def->nptrs = n; + } + for (i = 0; i < n; i++) { + char *domain; + const char *suffix; + size_t len; + size_t suflen; + + if (!(domain = virXMLPropString(nodes[i], "domain"))) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing PTR domain in network '%s'"), + networkName); + goto cleanup; + } + + if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) + suffix = VIR_SOCKET_ADDR_IPV4_ARPA; + else + suffix = VIR_SOCKET_ADDR_IPV6_ARPA; + + len = strlen(domain); + suflen = strlen(suffix); + if (len <= suflen || + STRNEQ(domain + len - suflen, suffix) || + domain[len - suflen - 1] != '.') { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Invalid PTR domain '%s' in network '%s'"), + domain, networkName); + VIR_FREE(domain); + goto cleanup; + } + def->ptrs[i] = domain; + } + if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) && virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0) goto cleanup; @@ -1627,6 +1686,8 @@ virNetworkIPDefParseXML(const char *networkName, virNetworkIPDefClear(def); VIR_FREE(address); VIR_FREE(netmask); + VIR_FREE(localPtr); + VIR_FREE(nodes); ctxt->node = save; return result; @@ -2630,6 +2691,7 @@ static int virNetworkIPDefFormat(virBufferPtr buf, const virNetworkIPDef *def) { + size_t i; int result = -1; virBufferAddLit(buf, "<ip"); @@ -2652,15 +2714,23 @@ virNetworkIPDefFormat(virBufferPtr buf, } if (def->prefix > 0) virBufferAsprintf(buf, " prefix='%u'", def->prefix); + + if (def->localPTR) { + virBufferAsprintf(buf, " localPtr='%s'", + virTristateBoolTypeToString(def->localPTR)); + } + virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); + for (i = 0; i < def->nptrs; i++) + virBufferAsprintf(buf, "<ptr domain='%s'/>\n", def->ptrs[i]); + if (def->tftproot) { virBufferEscapeString(buf, "<tftp root='%s'/>\n", def->tftproot); } if ((def->nranges || def->nhosts)) { - size_t i; virBufferAddLit(buf, "<dhcp>\n"); virBufferAdjustIndent(buf, 2); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 09e091616..edf1a1abd 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -162,6 +162,10 @@ struct _virNetworkIPDef { unsigned int prefix; /* ipv6 - only prefix allowed */ virSocketAddr netmask; /* ipv4 - either netmask or prefix specified */ + int localPTR; /* virTristateBool */ + size_t nptrs; + char **ptrs; + size_t nranges; /* Zero or more dhcp ranges */ virSocketAddrRangePtr ranges; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index ae1589d8c..49753737f 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -994,6 +994,49 @@ networkBuildDnsmasqHostsList(dnsmasqContext *dctx, } +static int +networkDnsmasqConfLocalPTRs(virBufferPtr buf, + virNetworkDefPtr def) +{ + virNetworkIPDefPtr ip; + size_t i, j; + char *ptr = NULL; + int rc; + + for (i = 0; i < def->nips; i++) { + ip = def->ips + i; + + if (ip->localPTR != VIR_TRISTATE_BOOL_YES) + continue; + + for (j = 0; j < ip->nptrs; j++) + virBufferAsprintf(buf, "local=/%s/\n", ip->ptrs[j]); + + if (ip->nptrs > 0) + continue; + + if ((rc = virSocketAddrPTRDomain(&ip->address, + virNetworkIPDefPrefix(ip), + &ptr)) < 0) { + if (rc == -2) { + int family = VIR_SOCKET_ADDR_FAMILY(&ip->address); + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("PTR domain for %s network with prefix %u " + "cannot be automatically created"), + (family == AF_INET) ? "IPv4" : "IPv6", + virNetworkIPDefPrefix(ip)); + } + return -1; + } + + virBufferAsprintf(buf, "local=/%s/\n", ptr); + VIR_FREE(ptr); + } + + return 0; +} + + int networkDnsmasqConfContents(virNetworkObjPtr network, const char *pidfile, @@ -1079,6 +1122,10 @@ networkDnsmasqConfContents(virNetworkObjPtr network, network->def->domain); } + if (wantDNS && + networkDnsmasqConfLocalPTRs(&configbuf, network->def) < 0) + goto cleanup; + if (wantDNS && network->def->dns.forwardPlainNames == VIR_TRISTATE_BOOL_NO) { virBufferAddLit(&configbuf, "domain-needed\n"); /* need to specify local=// whether or not a domain is diff --git a/tests/networkxml2confdata/ptr-domains-auto.conf b/tests/networkxml2confdata/ptr-domains-auto.conf new file mode 100644 index 000000000..7f1a393dd --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains-auto.conf @@ -0,0 +1,20 @@ +##WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE +##OVERWRITTEN AND LOST. Changes to this configuration should be made using: +## virsh net-edit default +## or other application using the libvirt API. +## +## dnsmasq conf file created by libvirt +strict-order +local=/122.168.192.in-addr.arpa/ +local=/1.0.e.f.0.1.c.a.8.b.d.0.1.0.0.2.ip6.arpa/ +except-interface=lo +bind-dynamic +interface=virbr0 +dhcp-range=192.168.122.2,192.168.122.254 +dhcp-no-override +dhcp-authoritative +dhcp-lease-max=253 +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile +addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts +dhcp-range=2001:db8:ac10:fe01::1,ra-only +dhcp-range=2001:db8:ac10:fd01::1,ra-only diff --git a/tests/networkxml2confdata/ptr-domains-auto.xml b/tests/networkxml2confdata/ptr-domains-auto.xml new file mode 100644 index 000000000..7fe12dc67 --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains-auto.xml @@ -0,0 +1,21 @@ +<network> + <name>default</name> + <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid> + <forward dev='eth1' mode='nat'/> + <bridge name='virbr0' stp='on' delay='0'/> + <ip address='192.168.122.1' netmask='255.255.255.0' localPtr='yes'> + <dhcp> + <range start='192.168.122.2' end='192.168.122.254'/> + <host mac='00:16:3e:77:e2:ed' name='a.example.com' ip='192.168.122.10'/> + <host mac='00:16:3e:3e:a9:1a' name='b.example.com' ip='192.168.122.11'/> + </dhcp> + </ip> + <ip family='ipv4' address='192.168.123.1' netmask='255.255.255.0' localPtr='no'> + </ip> + <ip family='ipv6' address='2001:db8:ac10:fe01::1' prefix='64' localPtr='yes'> + </ip> + <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'> + </ip> + <ip family='ipv4' address='10.24.10.1'> + </ip> +</network> diff --git a/tests/networkxml2confdata/ptr-domains.conf b/tests/networkxml2confdata/ptr-domains.conf new file mode 100644 index 000000000..2900eebce --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains.conf @@ -0,0 +1,24 @@ +##WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE +##OVERWRITTEN AND LOST. Changes to this configuration should be made using: +## virsh net-edit test +## or other application using the libvirt API. +## +## dnsmasq conf file created by libvirt +strict-order +local=/30.20.10.in-addr.arpa/ +local=/31.20.10.in-addr.arpa/ +local=/2.0.0.0.0.c.e.f.ip6.arpa/ +local=/0.0.0.0.0.c.e.f.ip6.arpa/ +local=/1.0.0.0.0.c.e.f.ip6.arpa/ +local=/1.0.f.e.d.c.b.a.4.0.0.0.0.c.e.f.ip6.arpa/ +except-interface=lo +bind-dynamic +interface=virbr2 +dhcp-range=10.20.30.1,static +dhcp-no-override +dhcp-authoritative +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/test.hostsfile +addn-hosts=/var/lib/libvirt/dnsmasq/test.addnhosts +dhcp-range=fec0:2::1,ra-only +dhcp-range=fec0::1,ra-only +dhcp-range=fec0:4:abcd:ef01::1,ra-only diff --git a/tests/networkxml2confdata/ptr-domains.xml b/tests/networkxml2confdata/ptr-domains.xml new file mode 100644 index 000000000..64a329676 --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains.xml @@ -0,0 +1,24 @@ +<network> + <name>test</name> + <uuid>cafecafe-cafe-cafe-cafe-cafecafecafe</uuid> + <forward mode='nat'/> + <bridge name='virbr2' stp='on' delay='0'/> + <mac address='52:54:00:eb:c2:e8'/> + <ip address='10.20.30.1' prefix='23' localPtr='yes'> + <ptr domain='30.20.10.in-addr.arpa'/> + <ptr domain='31.20.10.in-addr.arpa'/> + <dhcp> + <host mac='ca:fe:ca:fe:ca:fe' name='ble' ip='10.20.30.2'/> + <host mac='ca:fe:ca:fe:ca:fe' name='bla' ip='10.20.30.3'/> + <host mac='52:54:00:16:c6:1a' name='pxe' ip='10.20.30.4'/> + </dhcp> + </ip> + <ip family='ipv6' address='fec0:2::1' prefix='32' localPtr='yes'> + </ip> + <ip family='ipv6' address='fec0::1' prefix='31' localPtr='yes'> + <ptr domain='0.0.0.0.0.c.e.f.ip6.arpa'/> + <ptr domain='1.0.0.0.0.c.e.f.ip6.arpa'/> + </ip> + <ip family='ipv6' address='fec0:4:abcd:ef01::1' prefix='64' localPtr='yes'> + </ip> +</network> diff --git a/tests/networkxml2conftest.c b/tests/networkxml2conftest.c index 65a0e3218..b6c967c45 100644 --- a/tests/networkxml2conftest.c +++ b/tests/networkxml2conftest.c @@ -129,6 +129,8 @@ mymain(void) DO_TEST("dhcp6-network", dhcpv6); DO_TEST("dhcp6-nat-network", dhcpv6); DO_TEST("dhcp6host-routed-network", dhcpv6); + DO_TEST("ptr-domains", dhcpv6); + DO_TEST("ptr-domains-auto", dhcpv6); virObjectUnref(dhcpv6); virObjectUnref(full); -- 2.11.0
On 12/13/2016 08:52 AM, Jiri Denemark wrote:
Similarly to localOnly DNS domain, local PTR domains can be used to tell the DNS server not to forward reverse lookups for unknown IPs which belong to the virtual network.
What's here is useful, but the <ptr> element doesn't fit with the purpose of a PTR record in an actual DNS server. We should implement <ptr> according to RFC 1035 so that it can be used to fill in a "ptr-record" in dnsmasq.conf. An example from there is: 10.IN-ADDR.ARPA. PTR MILNET-GW.ISI.EDU. (note the terminating "."s - those are *very* important!) To support that our XML syntax would at least need two attributes, but I think it's the *2nd* one that should more properly called "domain". I'm trying to find a reference to a good name for the 1st attribute, but it's been about 15 years since I configured bind, and I can't seem to concentrate long enough to follow the links in google. Anyway, it would need to be something like this: <ptr name='10.IN-ADDR.ARPA." domain="MILNET-GW.ISI.EDU."/> (I think in the RFC, the first is the RDATA, and the 2nd is the PTRDNAME/"domain name pointer"/domain-name). I *think* this directly translates to: ptr-record=10.IN-ADDR.ARPA.,MILNET-GW.ISI.EDU. in dnsmasq.conf, but I'm completely extrapolating from examples I've found, and haven't tested it, so I may be totally wrong. (you'll notice that the item you've called "domain" is *not* what I called "domain" in my suggested syntax) A subset of that could be a <ptr> that has a "name" (or whatever we call it) but no domain, and that might have the effect of making that particular range local-only (so it would fail if it couldn't be resolved.). We should make sure there is no other valid use of a PTR with no domain though. Note that it should already be possible to forward PTR requests for particular ranges to an external server (i.e. the opposite of "localPtr") by adding a "<forwarder domain='0.10.in-addr.arpa.' addr='8.8.8.8'/>". In the meantime, could you separate the parts of this patch that implement "localPtr='yes'" from the <ptr> parts? I think that can be pushed as it is so it's not held up by disscussion of the full <ptr>.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/formatnetwork.html.in | 37 ++++++++++--- docs/schemas/basictypes.rng | 6 +++ docs/schemas/network.rng | 8 +++ src/conf/network_conf.c | 72 ++++++++++++++++++++++++- src/conf/network_conf.h | 4 ++ src/network/bridge_driver.c | 47 ++++++++++++++++ tests/networkxml2confdata/ptr-domains-auto.conf | 20 +++++++ tests/networkxml2confdata/ptr-domains-auto.xml | 21 ++++++++ tests/networkxml2confdata/ptr-domains.conf | 24 +++++++++ tests/networkxml2confdata/ptr-domains.xml | 24 +++++++++ tests/networkxml2conftest.c | 2 + 11 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 tests/networkxml2confdata/ptr-domains-auto.conf create mode 100644 tests/networkxml2confdata/ptr-domains-auto.xml create mode 100644 tests/networkxml2confdata/ptr-domains.conf create mode 100644 tests/networkxml2confdata/ptr-domains.xml
diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index 9cf940052..3c9414779 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -855,7 +855,7 @@ <hostname>myhostalias</hostname> </host> </dns> -<ip address="192.168.122.1" netmask="255.255.255.0"> +<ip address="192.168.122.1" netmask="255.255.255.0" localPtr="yes"> <dhcp> <range start="192.168.122.100" end="192.168.122.254"/> <host mac="00:16:3e:77:e2:ed" name="foo.example.com" ip="192.168.122.10"/> @@ -863,6 +863,10 @@ </dhcp> </ip> <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64"/> +<ip family="ipv6" address="fec0::1" prefix="31" localPtr="yes"> + <ptr domain="0.0.0.0.0.c.e.f.ip6.arpa"/> + <ptr domain="1.0.0.0.0.c.e.f.ip6.arpa"/> +</ip> <route family="ipv6" address="2001:db9:ca1:1::" prefix="64" gateway="2001:db8:ca2:2::2"/> </pre>
@@ -983,11 +987,20 @@ to specify the type of address — <code>ipv4</code> or <code>ipv6</code>; if no <code>family</code> is given, <code>ipv4</code> is assumed. More than one address of each family can - be defined for a network. The <code>ip</code> element is supported - <span class="since">since 0.3.0</span>. IPv6, multiple addresses on a - single network, <code>family</code>, and <code>prefix</code> are - supported <span class="since">since 0.8.7</span>. The <code>ip</code> - element may contain the following elements: + be defined for a network. The optional <code>localPtr</code> attribute + (<span class="since">since 3.0.0</span>) configures the DNS server not + to forward any reverse DNS requests for IP addresses from the network + configured by the <code>address</code> and + <code>netmask</code>/<code>prefix</code> attributes. For some unusual + network prefixes (not divisible by 8 for IPv4 or not divisible by 4 for + IPv6) libvirt may be unable to compute the PTR domain automatically, + in which case the domain has to be specified explicitly by the + <code>ptr</code> element described below. The <code>ip</code> element + is supported <span class="since">since 0.3.0</span>. IPv6, multiple + addresses on a single network, <code>family</code>, and + <code>prefix</code> are supported <span class="since">since + 0.8.7</span>. The <code>ip</code> element may contain the following + elements:
<dl> <dt><code>tftp</code></dt> @@ -1051,6 +1064,18 @@ </dd> </dl> </dd> + + <dt><code>ptr</code></dt> + <dd>The <code>domain</code> attribute of the optional + <code>ptr</code> element specifies the PTR domain used for reverse + DNS lookups. The domain has to end with ".in-addr.arpa" for IPv4 + and ".ip6.arpa" for IPv6. Each <code>ip</code> element may contain + zero or more <code>ptr</code> elements. When + <code>localPtr='yes'</code> attribute is set in the parent + <code>ip</code> element the DNS server for this network will be + configured not to forward any reverse lookups within the specified + domains. <span class="since">Since 3.0.0</span> + </dd> </dl> </dd> </dl> diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng index 1b4f980e7..f99c76392 100644 --- a/docs/schemas/basictypes.rng +++ b/docs/schemas/basictypes.rng @@ -239,6 +239,12 @@ </data> </define>
+ <define name="ptrName"> + <data type="string"> + <param name="pattern">(((((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([1-9][0-9])|([0-9]))\.){1,3}in-addr)|((([0-9a-fA-F]\.){1,31})ip6)).arpa</param> + </data> + </define> + <define name="deviceName"> <data type="string"> <param name="pattern">[a-zA-Z0-9_\.\-\\:/]+</param> diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 986119596..1a3705a1f 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -339,6 +339,9 @@ <optional> <attribute name="family"><ref name="addr-family"/></attribute> </optional> + <optional> + <attribute name="localPtr"><ref name="virYesNo"/></attribute> + </optional> <interleave> <optional> <element name="tftp"> @@ -384,6 +387,11 @@ </interleave> </element> </optional> + <zeroOrMore> + <element name='ptr'> + <attribute name='domain'><ref name="ptrName"/></attribute> + </element> + </zeroOrMore> </interleave> </element> </zeroOrMore> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index b6849ceab..ce0bd0260 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -315,6 +315,10 @@ static void virNetworkIPDefClear(virNetworkIPDefPtr def) { VIR_FREE(def->family); + + virStringListFreeCount(def->ptrs, def->nptrs); + def->ptrs = NULL; + VIR_FREE(def->ranges);
while (def->nhosts) @@ -1507,6 +1511,10 @@ virNetworkIPDefParseXML(const char *networkName, unsigned long prefix = 0; int prefixRc; int result = -1; + char *localPtr = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n;
save = ctxt->node; ctxt->node = node; @@ -1549,6 +1557,17 @@ virNetworkIPDefParseXML(const char *networkName, else def->prefix = prefix;
+ localPtr = virXPathString("string(./@localPtr)", ctxt); + if (localPtr) { + def->localPTR = virTristateBoolTypeFromString(localPtr); + if (def->localPTR <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid localPtr value '%s' in network '%s'"), + localPtr, networkName); + goto cleanup; + } + } + /* validate address, etc. for each family */ if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) { if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || @@ -1604,6 +1623,46 @@ virNetworkIPDefParseXML(const char *networkName, goto cleanup; }
+ if ((n = virXPathNodeSet("./ptr", ctxt, &nodes)) < 0) + goto cleanup; + + if (n > 0) { + if (VIR_ALLOC_N(def->ptrs, n) < 0) + goto cleanup; + def->nptrs = n; + } + for (i = 0; i < n; i++) { + char *domain; + const char *suffix; + size_t len; + size_t suflen; + + if (!(domain = virXMLPropString(nodes[i], "domain"))) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing PTR domain in network '%s'"), + networkName); + goto cleanup; + } + + if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) + suffix = VIR_SOCKET_ADDR_IPV4_ARPA; + else + suffix = VIR_SOCKET_ADDR_IPV6_ARPA; + + len = strlen(domain); + suflen = strlen(suffix); + if (len <= suflen || + STRNEQ(domain + len - suflen, suffix) || + domain[len - suflen - 1] != '.') { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Invalid PTR domain '%s' in network '%s'"), + domain, networkName); + VIR_FREE(domain); + goto cleanup; + } + def->ptrs[i] = domain; + } + if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) && virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0) goto cleanup; @@ -1627,6 +1686,8 @@ virNetworkIPDefParseXML(const char *networkName, virNetworkIPDefClear(def); VIR_FREE(address); VIR_FREE(netmask); + VIR_FREE(localPtr); + VIR_FREE(nodes);
ctxt->node = save; return result; @@ -2630,6 +2691,7 @@ static int virNetworkIPDefFormat(virBufferPtr buf, const virNetworkIPDef *def) { + size_t i; int result = -1;
virBufferAddLit(buf, "<ip"); @@ -2652,15 +2714,23 @@ virNetworkIPDefFormat(virBufferPtr buf, } if (def->prefix > 0) virBufferAsprintf(buf, " prefix='%u'", def->prefix); + + if (def->localPTR) { + virBufferAsprintf(buf, " localPtr='%s'", + virTristateBoolTypeToString(def->localPTR)); + } + virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2);
+ for (i = 0; i < def->nptrs; i++) + virBufferAsprintf(buf, "<ptr domain='%s'/>\n", def->ptrs[i]); + if (def->tftproot) { virBufferEscapeString(buf, "<tftp root='%s'/>\n", def->tftproot); } if ((def->nranges || def->nhosts)) { - size_t i; virBufferAddLit(buf, "<dhcp>\n"); virBufferAdjustIndent(buf, 2);
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 09e091616..edf1a1abd 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -162,6 +162,10 @@ struct _virNetworkIPDef { unsigned int prefix; /* ipv6 - only prefix allowed */ virSocketAddr netmask; /* ipv4 - either netmask or prefix specified */
+ int localPTR; /* virTristateBool */ + size_t nptrs; + char **ptrs; + size_t nranges; /* Zero or more dhcp ranges */ virSocketAddrRangePtr ranges;
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index ae1589d8c..49753737f 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -994,6 +994,49 @@ networkBuildDnsmasqHostsList(dnsmasqContext *dctx, }
+static int +networkDnsmasqConfLocalPTRs(virBufferPtr buf, + virNetworkDefPtr def) +{ + virNetworkIPDefPtr ip; + size_t i, j; + char *ptr = NULL; + int rc; + + for (i = 0; i < def->nips; i++) { + ip = def->ips + i; + + if (ip->localPTR != VIR_TRISTATE_BOOL_YES) + continue; + + for (j = 0; j < ip->nptrs; j++) + virBufferAsprintf(buf, "local=/%s/\n", ip->ptrs[j]); + + if (ip->nptrs > 0) + continue; + + if ((rc = virSocketAddrPTRDomain(&ip->address, + virNetworkIPDefPrefix(ip), + &ptr)) < 0) { + if (rc == -2) { + int family = VIR_SOCKET_ADDR_FAMILY(&ip->address); + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("PTR domain for %s network with prefix %u " + "cannot be automatically created"), + (family == AF_INET) ? "IPv4" : "IPv6", + virNetworkIPDefPrefix(ip)); + } + return -1; + } + + virBufferAsprintf(buf, "local=/%s/\n", ptr); + VIR_FREE(ptr); + } + + return 0; +} + + int networkDnsmasqConfContents(virNetworkObjPtr network, const char *pidfile, @@ -1079,6 +1122,10 @@ networkDnsmasqConfContents(virNetworkObjPtr network, network->def->domain); }
+ if (wantDNS && + networkDnsmasqConfLocalPTRs(&configbuf, network->def) < 0) + goto cleanup; + if (wantDNS && network->def->dns.forwardPlainNames == VIR_TRISTATE_BOOL_NO) { virBufferAddLit(&configbuf, "domain-needed\n"); /* need to specify local=// whether or not a domain is diff --git a/tests/networkxml2confdata/ptr-domains-auto.conf b/tests/networkxml2confdata/ptr-domains-auto.conf new file mode 100644 index 000000000..7f1a393dd --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains-auto.conf @@ -0,0 +1,20 @@ +##WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE +##OVERWRITTEN AND LOST. Changes to this configuration should be made using: +## virsh net-edit default +## or other application using the libvirt API. +## +## dnsmasq conf file created by libvirt +strict-order +local=/122.168.192.in-addr.arpa/ +local=/1.0.e.f.0.1.c.a.8.b.d.0.1.0.0.2.ip6.arpa/ +except-interface=lo +bind-dynamic +interface=virbr0 +dhcp-range=192.168.122.2,192.168.122.254 +dhcp-no-override +dhcp-authoritative +dhcp-lease-max=253 +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile +addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts +dhcp-range=2001:db8:ac10:fe01::1,ra-only +dhcp-range=2001:db8:ac10:fd01::1,ra-only diff --git a/tests/networkxml2confdata/ptr-domains-auto.xml b/tests/networkxml2confdata/ptr-domains-auto.xml new file mode 100644 index 000000000..7fe12dc67 --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains-auto.xml @@ -0,0 +1,21 @@ +<network> + <name>default</name> + <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid> + <forward dev='eth1' mode='nat'/> + <bridge name='virbr0' stp='on' delay='0'/> + <ip address='192.168.122.1' netmask='255.255.255.0' localPtr='yes'> + <dhcp> + <range start='192.168.122.2' end='192.168.122.254'/> + <host mac='00:16:3e:77:e2:ed' name='a.example.com' ip='192.168.122.10'/> + <host mac='00:16:3e:3e:a9:1a' name='b.example.com' ip='192.168.122.11'/> + </dhcp> + </ip> + <ip family='ipv4' address='192.168.123.1' netmask='255.255.255.0' localPtr='no'> + </ip> + <ip family='ipv6' address='2001:db8:ac10:fe01::1' prefix='64' localPtr='yes'> + </ip> + <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'> + </ip> + <ip family='ipv4' address='10.24.10.1'> + </ip> +</network> diff --git a/tests/networkxml2confdata/ptr-domains.conf b/tests/networkxml2confdata/ptr-domains.conf new file mode 100644 index 000000000..2900eebce --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains.conf @@ -0,0 +1,24 @@ +##WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE +##OVERWRITTEN AND LOST. Changes to this configuration should be made using: +## virsh net-edit test +## or other application using the libvirt API. +## +## dnsmasq conf file created by libvirt +strict-order +local=/30.20.10.in-addr.arpa/ +local=/31.20.10.in-addr.arpa/ +local=/2.0.0.0.0.c.e.f.ip6.arpa/ +local=/0.0.0.0.0.c.e.f.ip6.arpa/ +local=/1.0.0.0.0.c.e.f.ip6.arpa/ +local=/1.0.f.e.d.c.b.a.4.0.0.0.0.c.e.f.ip6.arpa/ +except-interface=lo +bind-dynamic +interface=virbr2 +dhcp-range=10.20.30.1,static +dhcp-no-override +dhcp-authoritative +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/test.hostsfile +addn-hosts=/var/lib/libvirt/dnsmasq/test.addnhosts +dhcp-range=fec0:2::1,ra-only +dhcp-range=fec0::1,ra-only +dhcp-range=fec0:4:abcd:ef01::1,ra-only diff --git a/tests/networkxml2confdata/ptr-domains.xml b/tests/networkxml2confdata/ptr-domains.xml new file mode 100644 index 000000000..64a329676 --- /dev/null +++ b/tests/networkxml2confdata/ptr-domains.xml @@ -0,0 +1,24 @@ +<network> + <name>test</name> + <uuid>cafecafe-cafe-cafe-cafe-cafecafecafe</uuid> + <forward mode='nat'/> + <bridge name='virbr2' stp='on' delay='0'/> + <mac address='52:54:00:eb:c2:e8'/> + <ip address='10.20.30.1' prefix='23' localPtr='yes'> + <ptr domain='30.20.10.in-addr.arpa'/> + <ptr domain='31.20.10.in-addr.arpa'/> + <dhcp> + <host mac='ca:fe:ca:fe:ca:fe' name='ble' ip='10.20.30.2'/> + <host mac='ca:fe:ca:fe:ca:fe' name='bla' ip='10.20.30.3'/> + <host mac='52:54:00:16:c6:1a' name='pxe' ip='10.20.30.4'/> + </dhcp> + </ip> + <ip family='ipv6' address='fec0:2::1' prefix='32' localPtr='yes'> + </ip> + <ip family='ipv6' address='fec0::1' prefix='31' localPtr='yes'> + <ptr domain='0.0.0.0.0.c.e.f.ip6.arpa'/> + <ptr domain='1.0.0.0.0.c.e.f.ip6.arpa'/> + </ip> + <ip family='ipv6' address='fec0:4:abcd:ef01::1' prefix='64' localPtr='yes'> + </ip> +</network> diff --git a/tests/networkxml2conftest.c b/tests/networkxml2conftest.c index 65a0e3218..b6c967c45 100644 --- a/tests/networkxml2conftest.c +++ b/tests/networkxml2conftest.c @@ -129,6 +129,8 @@ mymain(void) DO_TEST("dhcp6-network", dhcpv6); DO_TEST("dhcp6-nat-network", dhcpv6); DO_TEST("dhcp6host-routed-network", dhcpv6); + DO_TEST("ptr-domains", dhcpv6); + DO_TEST("ptr-domains-auto", dhcpv6);
virObjectUnref(dhcpv6); virObjectUnref(full);
participants (2)
-
Jiri Denemark -
Laine Stump