From c7eec83f08329a2a0008ba939c07dd1dc7d9b7ff Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 12 Aug 2013 17:03:32 +0900 Subject: Support parsing RDNSS ND options from netlink. The RDNSS options (RFC 6106) used to configure DNS servers via router advertisements are passed from the kernel to userspace via RTM_NEWNDUSEROPT netlink messages. Add code to NetlinkEvent to parse them. Also fix a compiler warning and a couple of style issues. [Cherry-pick of b185e90dcc6ac111bff908edcc6d89fd6b37dc11] Bug: 9180552 Change-Id: I6c532c8f0ceef3afdc977a431a036df398013e1a --- libsysutils/src/NetlinkEvent.cpp | 125 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 4 deletions(-) (limited to 'libsysutils/src') diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp index aae2ae7..9f2606c 100644 --- a/libsysutils/src/NetlinkEvent.cpp +++ b/libsysutils/src/NetlinkEvent.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,7 @@ const int NetlinkEvent::NlActionLinkUp = 4; const int NetlinkEvent::NlActionLinkDown = 5; const int NetlinkEvent::NlActionAddressUpdated = 6; const int NetlinkEvent::NlActionAddressRemoved = 7; +const int NetlinkEvent::NlActionRdnss = 8; NetlinkEvent::NetlinkEvent() { mAction = NlActionUnknown; @@ -76,7 +78,7 @@ void NetlinkEvent::dump() { } /* - * Decode a RTM_NEWADDR or RTM_DELADDR message. + * Parse a RTM_NEWADDR or RTM_DELADDR message. */ bool NetlinkEvent::parseIfAddrMessage(int type, struct ifaddrmsg *ifaddr, int rtasize) { @@ -172,13 +174,111 @@ bool NetlinkEvent::parseIfAddrMessage(int type, struct ifaddrmsg *ifaddr, } /* - * Parse an binary message from a NETLINK_ROUTE netlink socket. + * Parse a RTM_NEWNDUSEROPT message. + */ +bool NetlinkEvent::parseNdUserOptMessage(struct nduseroptmsg *msg, int len) { + // Check the length is valid. + if (msg->nduseropt_opts_len > len) { + SLOGE("RTM_NEWNDUSEROPT invalid length %d > %d\n", + msg->nduseropt_opts_len, len); + return false; + } + len = msg->nduseropt_opts_len; + + // Check address family and packet type. + if (msg->nduseropt_family != AF_INET6) { + SLOGE("RTM_NEWNDUSEROPT message for unknown family %d\n", + msg->nduseropt_family); + return false; + } + + if (msg->nduseropt_icmp_type != ND_ROUTER_ADVERT || + msg->nduseropt_icmp_code != 0) { + SLOGE("RTM_NEWNDUSEROPT message for unknown ICMPv6 type/code %d/%d\n", + msg->nduseropt_icmp_type, msg->nduseropt_icmp_code); + return false; + } + + // Find the interface name. + char ifname[IFNAMSIZ + 1]; + if (!if_indextoname(msg->nduseropt_ifindex, ifname)) { + SLOGE("RTM_NEWNDUSEROPT on unknown ifindex %d\n", + msg->nduseropt_ifindex); + return false; + } + + // The kernel sends a separate netlink message for each ND option in the RA. + // So only parse the first ND option in the message. + struct nd_opt_hdr *opthdr = (struct nd_opt_hdr *) (msg + 1); + + // The length is in multiples of 8 octets. + uint16_t optlen = opthdr->nd_opt_len; + if (optlen * 8 > len) { + SLOGE("Invalid option length %d > %d for ND option %d\n", + optlen * 8, len, opthdr->nd_opt_type); + return false; + } + + if (opthdr->nd_opt_type == ND_OPT_RDNSS) { + // DNS Servers (RFC 6106). + // Each address takes up 2*8 octets, and the header takes up 8 octets. + // So for a valid option with one or more addresses, optlen must be + // odd and greater than 1. + if ((optlen < 3) || !(optlen & 0x1)) { + SLOGE("Invalid optlen %d for RDNSS option\n", optlen); + return false; + } + int numaddrs = (optlen - 1) / 2; + + // Find the lifetime. + struct nd_opt_rdnss *rndss_opt = (struct nd_opt_rdnss *) opthdr; + uint32_t lifetime = ntohl(rndss_opt->nd_opt_rdnss_lifetime); + + // Construct "SERVERS=". + // Reserve (INET6_ADDRSTRLEN + 1) chars for each address: all but the + // the last address are followed by ','; the last is followed by '\0'. + static const char kServerTag[] = "SERVERS="; + static const int kTagLength = sizeof(kServerTag) - 1; + int bufsize = kTagLength + numaddrs * (INET6_ADDRSTRLEN + 1); + char *buf = (char *) malloc(bufsize); + if (!buf) { + SLOGE("RDNSS option: out of memory\n"); + return false; + } + strcpy(buf, kServerTag); + int pos = kTagLength; + + struct in6_addr *addrs = (struct in6_addr *) (rndss_opt + 1); + for (int i = 0; i < numaddrs; i++) { + if (i > 0) { + buf[pos++] = ','; + } + inet_ntop(AF_INET6, addrs + i, buf + pos, bufsize - pos); + pos += strlen(buf + pos); + } + buf[pos] = '\0'; + + mAction = NlActionRdnss; + mSubsystem = strdup("net"); + asprintf(&mParams[0], "INTERFACE=%s", ifname); + asprintf(&mParams[1], "LIFETIME=%u", lifetime); + mParams[2] = buf; + } else { + SLOGD("Unknown ND option type %d\n", opthdr->nd_opt_type); + return false; + } + + return true; +} + +/* + * Parse a binary message from a NETLINK_ROUTE netlink socket. */ bool NetlinkEvent::parseBinaryNetlinkMessage(char *buffer, int size) { const struct nlmsghdr *nh; for (nh = (struct nlmsghdr *) buffer; - NLMSG_OK(nh, size) && (nh->nlmsg_type != NLMSG_DONE); + NLMSG_OK(nh, (unsigned) size) && (nh->nlmsg_type != NLMSG_DONE); nh = NLMSG_NEXT(nh, size)) { if (nh->nlmsg_type == RTM_NEWLINK) { @@ -245,8 +345,25 @@ bool NetlinkEvent::parseBinaryNetlinkMessage(char *buffer, int size) { if (!parseIfAddrMessage(nh->nlmsg_type, ifa, rtasize)) { continue; } + + } else if (nh->nlmsg_type == RTM_NEWNDUSEROPT) { + int len = nh->nlmsg_len - sizeof(*nh); + struct nduseroptmsg *ndmsg = (struct nduseroptmsg *) NLMSG_DATA(nh); + + if (sizeof(*ndmsg) > (size_t) len) { + SLOGE("Got a short RTM_NEWNDUSEROPT message\n"); + continue; + } + + size_t optsize = NLMSG_PAYLOAD(nh, sizeof(*ndmsg)); + if (!parseNdUserOptMessage(ndmsg, optsize)) { + continue; + } + + } else { - SLOGD("Unexpected netlink message. type=0x%x\n", nh->nlmsg_type); + SLOGD("Unexpected netlink message. type=0x%x\n", + nh->nlmsg_type); } } -- cgit v1.1