blob: be56c4e89ed6864ae95c2ae349338c7c73051442 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net-base/rtnl_message.h"
#include <net/if.h> // NB: order matters; this conflicts with <linux/if.h>
#include <arpa/inet.h> // NOLINT(build/include_alpha)
#include <linux/fib_rules.h>
#include <linux/if_addr.h>
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <array>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include "net-base/byte_utils.h"
#include "net-base/http_url.h"
namespace net_base {
namespace {
// Defined in the kernel at include/net/ndisc.h and not exposed in user space
// headers. Neighbor Discovery user option type definition.
#define ND_OPT_RDNSS 25 /* RFC 5006 */
#define ND_OPT_DNSSL 31 /* RFC 6106 */
#define ND_OPT_CAPTIVE_PORTAL 37 /* RFC 8910 */
using flag_info_t = std::pair<uint32_t, const char*>;
// Helper for pretty printing flags
template <std::size_t Dim>
std::string PrintFlags(uint32_t flags,
const std::array<flag_info_t, Dim>& flags_info,
const std::string& separator = " | ") {
std::string str = "";
if (flags == 0) {
return str;
}
std::string sep = "";
for (size_t i = 0; i < Dim; i++) {
if ((flags & flags_info[i].first) == 0) {
continue;
}
str += sep;
str += flags_info[i].second;
sep = separator;
}
return str;
}
// Flag names for Address events (ifa_flags field of struct ifaddrmsg). Defined
// in uapi/linux/if_addr.h
constexpr std::array<flag_info_t, 12> kIfaFlags{{
{IFA_F_TEMPORARY, "TEMPORARY"},
{IFA_F_NODAD, "NODAD"},
{IFA_F_OPTIMISTIC, "OPTIMISTIC"},
{IFA_F_DADFAILED, "DADFAILED"},
{IFA_F_HOMEADDRESS, "HOMEADDRESS"},
{IFA_F_DEPRECATED, "DEPRECATED"},
{IFA_F_TENTATIVE, "TENTATIVE"},
{IFA_F_PERMANENT, "PERMANENT"},
{IFA_F_MANAGETEMPADDR, "MANAGETEMPADDR"},
{IFA_F_NOPREFIXROUTE, "NOPREFIXROUTE"},
{IFA_F_MCAUTOJOIN, "MCAUTOJOIN"},
{IFA_F_STABLE_PRIVACY, "STABLE_PRIVACY"},
}};
// Flag names for Link events (ifi_flags field of struct ifinfomsg). Defined in
// uapi/linux/if.h
constexpr std::array<flag_info_t, 19> kNetDeviceFlags = {{
{IFF_ALLMULTI, "ALLMULTI"},
{IFF_AUTOMEDIA, "AUTOMEDIA"},
{IFF_BROADCAST, "BROADCAST"},
{IFF_DEBUG, "DEBUG"},
{IFF_DORMANT, "DORMANT"},
{IFF_DYNAMIC, "DYNAMIC"},
{IFF_ECHO, "ECHO"},
{IFF_LOOPBACK, "LOOPBACK"},
{IFF_LOWER_UP, "LOWER_UP"},
{IFF_MASTER, "MASTER"},
{IFF_MULTICAST, "MULTICAST"},
{IFF_NOARP, "NOARP"},
{IFF_NOTRAILERS, "NOTRAILERS"},
{IFF_POINTOPOINT, "POINTOPOINT"},
{IFF_PORTSEL, "PORTSEL"},
{IFF_PROMISC, "PROMISC"},
{IFF_RUNNING, "RUNNING"},
{IFF_SLAVE, "SLAVE"},
{IFF_UP, "UP"},
}};
// Returns the name associated with the give |ifi_type| corresponding to the
// ifi_type field of a struct ifinfomsg LINK message. The possible type values
// are defined in uapi/linux/if_arp.h.
std::string GetNetDeviceTypeName(unsigned int ifi_type) {
switch (ifi_type) {
case ARPHRD_NETROM:
return "NETROM";
case ARPHRD_ETHER:
return "ETHER";
case ARPHRD_EETHER:
return "EETHER";
case ARPHRD_AX25:
return "AX25";
case ARPHRD_PRONET:
return "PRONET";
case ARPHRD_CHAOS:
return "CHAOS";
case ARPHRD_IEEE802:
return "IEEE802";
case ARPHRD_ARCNET:
return "ARCNET";
case ARPHRD_APPLETLK:
return "APPLETLK";
case ARPHRD_DLCI:
return "DLCI";
case ARPHRD_ATM:
return "ATM";
case ARPHRD_METRICOM:
return "METRICOM";
case ARPHRD_IEEE1394:
return "IEEE1394";
case ARPHRD_EUI64:
return "EUI64";
case ARPHRD_INFINIBAND:
return "INFINIBAND";
case ARPHRD_SLIP:
return "SLIP";
case ARPHRD_CSLIP:
return "CSLIP";
case ARPHRD_SLIP6:
return "SLIP6";
case ARPHRD_CSLIP6:
return "CSLIP6";
case ARPHRD_RSRVD:
return "RSRVD";
case ARPHRD_ADAPT:
return "ADAPT";
case ARPHRD_ROSE:
return "ROSE";
case ARPHRD_X25:
return "X25";
case ARPHRD_HWX25:
return "HWX25";
case ARPHRD_CAN:
return "CAN";
case ARPHRD_PPP:
return "PPP";
case ARPHRD_CISCO:
return "CISCO"; // also ARPHRD_HDLC
case ARPHRD_LAPB:
return "LAPB";
case ARPHRD_DDCMP:
return "DDCMP";
case ARPHRD_RAWHDLC:
return "RAWHDLC";
case ARPHRD_RAWIP:
return "RAWIP";
case ARPHRD_TUNNEL:
return "TUNNEL";
case ARPHRD_TUNNEL6:
return "TUNNEL6";
case ARPHRD_FRAD:
return "FRAD";
case ARPHRD_SKIP:
return "SKIP";
case ARPHRD_LOOPBACK:
return "LOOPBACK";
case ARPHRD_LOCALTLK:
return "LOCALTLK";
case ARPHRD_FDDI:
return "FDDI";
case ARPHRD_BIF:
return "BIF";
case ARPHRD_SIT:
return "SIT";
case ARPHRD_IPDDP:
return "IPDDP";
case ARPHRD_IPGRE:
return "IPGRE";
case ARPHRD_PIMREG:
return "PIMREG";
case ARPHRD_HIPPI:
return "HIPPI";
case ARPHRD_ASH:
return "ASH";
case ARPHRD_ECONET:
return "ECONET";
case ARPHRD_IRDA:
return "IRDA";
case ARPHRD_FCPP:
return "FCPP";
case ARPHRD_FCAL:
return "FCAL";
case ARPHRD_FCPL:
return "FCPL";
case ARPHRD_FCFABRIC:
return "FCFABRIC";
case ARPHRD_IEEE802_TR:
return "IEEE802_TR";
case ARPHRD_IEEE80211:
return "IEEE80211";
case ARPHRD_IEEE80211_PRISM:
return "IEEE80211_PRISM";
case ARPHRD_IEEE80211_RADIOTAP:
return "IEEE80211_RADIOTAP";
case ARPHRD_IEEE802154:
return "IEEE802154 ";
case ARPHRD_IEEE802154_MONITOR:
return "IEEE802154_MONITOR";
case ARPHRD_PHONET:
return "PHONET";
case ARPHRD_PHONET_PIPE:
return "PHONET_PIPE";
case ARPHRD_CAIF:
return "CAIF";
case ARPHRD_IP6GRE:
return "IP6GRE";
case ARPHRD_NETLINK:
return "NETLINK";
case ARPHRD_6LOWPAN:
return "6LOWPAN";
case ARPHRD_VSOCKMON:
return "VSOCKMON";
case ARPHRD_VOID:
return "VOID";
case ARPHRD_NONE:
return "NONE";
default:
return std::to_string(ifi_type);
}
}
// Returns the name associated with the give |rtm_type| corresponding to the
// rtm_type field of a struct rtmsg ROUTE message. The possible type values
// are defined in uapi/linux/rtnetlink.h.
std::string GetRouteTypeName(uint8_t rtm_type) {
switch (rtm_type) {
case RTN_UNSPEC:
return "UNSPEC";
case RTN_UNICAST:
return "UNICAST";
case RTN_LOCAL:
return "LOCAL";
case RTN_BROADCAST:
return "BROADCAST";
case RTN_ANYCAST:
return "ANYCAST";
case RTN_MULTICAST:
return "MULTICAST";
case RTN_BLACKHOLE:
return "BLACKHOLE";
case RTN_UNREACHABLE:
return "UNREACHABLE";
case RTN_PROHIBIT:
return "PROHIBIT";
case RTN_THROW:
return "THROW";
case RTN_NAT:
return "NAT";
case RTN_XRESOLVE:
return "XRESOLVE";
default:
return std::to_string(rtm_type);
}
}
// Helper function to return route protocol names defined by the kernel.
// User reserved protocol values are returned as decimal numbers.
// Route protocols. Defined in uapi/linux/rtnetlink.h
std::string GetRouteProtocol(uint8_t protocol) {
switch (protocol) {
case RTPROT_UNSPEC:
return "UNSPEC";
case RTPROT_REDIRECT:
return "REDIRECT";
case RTPROT_KERNEL:
return "KERNEL";
case RTPROT_BOOT:
return "BOOT";
case RTPROT_STATIC:
return "STATIC";
case RTPROT_GATED:
return "GATED";
case RTPROT_RA:
return "RA";
case RTPROT_MRT:
return "MRT";
case RTPROT_ZEBRA:
return "ZEBRA";
case RTPROT_BIRD:
return "BIRD";
case RTPROT_DNROUTED:
return "DNROUTED";
case RTPROT_XORP:
return "XORP";
case RTPROT_NTK:
return "NTK";
case RTPROT_DHCP:
return "DHCP";
case RTPROT_MROUTED:
return "MROUTED";
case RTPROT_BABEL:
return "BABEL";
// The following protocols are not defined on Linux 4.14
case 186 /* RTPROT_BGP */:
return "BGP";
case 187 /* RTPROT_ISIS */:
return "ISIS";
case 188 /* RTPROT_OSPF */:
return "OSPF";
case 189 /* RTPROT_RIP */:
return "RIP";
case 192 /* RTPROT_EIGRP */:
return "EIGRP";
default:
return std::to_string(protocol);
}
}
// Returns the name associated with the given |rule_rtm_type| routing rule
// action type corresponding to the rtm_type field of a struct rtmsg message.
// The possible rule action values are defined in uapi/linux/fib_rules.h. The
// struct fib_rule_hdr in uapi/linux/fib_rules.h such that it aligns with the
// |rtm_type| field of struct rtmsg defined in uapi/linux/rtnetlink.h.
std::string GetRuleActionName(uint16_t rule_rtm_type) {
switch (rule_rtm_type) {
case FR_ACT_UNSPEC:
return "UNSPEC";
case FR_ACT_TO_TBL:
return "TO_TBL";
case FR_ACT_GOTO:
return "GOTO";
case FR_ACT_NOP:
return "NOP";
case FR_ACT_RES3:
return "RES3";
case FR_ACT_RES4:
return "RES4";
case FR_ACT_BLACKHOLE:
return "BLACKHOLE";
case FR_ACT_UNREACHABLE:
return "UNREACHABLE";
case FR_ACT_PROHIBIT:
return "PROHIBIT";
default:
return std::to_string(rule_rtm_type);
}
}
std::unique_ptr<RTNLAttrMap> ParseAttrs(const struct rtattr* data,
size_t attr_len) {
const auto* attr_data = reinterpret_cast<const char*>(data);
if (attr_len > INT_MAX) {
LOG(ERROR) << "Buffer length " << attr_len << " is over INT_MAX";
return nullptr;
}
int len = static_cast<int>(attr_len);
RTNLAttrMap attrs;
while (data && RTA_OK(data, len)) {
attrs[data->rta_type] = {
reinterpret_cast<uint8_t*>(RTA_DATA(data)),
reinterpret_cast<uint8_t*>(RTA_DATA(data)) + RTA_PAYLOAD(data)};
// Note: RTA_NEXT() performs subtraction on 'len'. It's important that
// 'len' is a signed integer, so underflow works properly.
data = RTA_NEXT(data, len);
}
if (len) {
LOG(ERROR) << "Error parsing RTNL attributes <"
<< base::HexEncode(attr_data, attr_len)
<< ">, trailing length: " << len;
return nullptr;
}
return std::make_unique<RTNLAttrMap>(attrs);
}
// Returns the interface name for the device with interface index |ifindex|, or
// returns an empty string if it fails to find the interface.
std::string IndexToName(unsigned int ifindex) {
char buf[IFNAMSIZ] = {};
if_indextoname(ifindex, buf);
return std::string(buf);
}
} // namespace
struct RTNLHeader {
RTNLHeader() { memset(this, 0, sizeof(*this)); }
struct nlmsghdr hdr;
union {
struct ifinfomsg ifi;
struct ifaddrmsg ifa;
struct rtmsg rtm;
struct nduseroptmsg nd_user_opt;
struct ndmsg ndm;
};
};
// Neighbor Discovery user option header definition.
struct NDUserOptionHeader {
uint8_t type;
uint8_t length;
} __attribute__((__packed__));
std::string RTNLMessage::NeighborStatus::ToString() const {
return base::StringPrintf("NeighborStatus state %d flags %X type %d", state,
flags, type);
}
// |lifetime| is unsigned. Printing as signed so that infinity (0xfffffff) get
// printed as -1. Same below.
std::string RTNLMessage::RdnssOption::ToString() const {
return base::StringPrintf("RdnssOption lifetime %d", lifetime);
}
std::string RTNLMessage::DnsslOption::ToString() const {
return base::StringPrintf("DnsslOption lifetime %d", lifetime);
}
std::string RTNLMessage::NdUserOption::ToString() const {
return base::StringPrintf("NdUserOption type %u", type);
}
// static
std::vector<uint8_t> RTNLMessage::PackAttrs(const RTNLAttrMap& attrs) {
std::vector<uint8_t> attributes;
for (const auto& pair : attrs) {
size_t len = RTA_LENGTH(pair.second.size());
struct rtattr rt_attr = {
// Linter discourages 'unsigned short', but 'unsigned short' is used in
// the UAPI.
static_cast<unsigned short>(len), // NOLINT(runtime/int)
pair.first,
};
std::vector<uint8_t> header =
net_base::byte_utils::ToBytes<struct rtattr>(rt_attr);
header.resize(RTA_ALIGN(header.size()), 0);
attributes.insert(attributes.end(), header.begin(), header.end());
std::vector<uint8_t> data = pair.second;
data.resize(RTA_ALIGN(data.size()), 0);
attributes.insert(attributes.end(), data.begin(), data.end());
}
return attributes;
}
RTNLMessage::RTNLMessage(Type type,
Mode mode,
uint16_t flags,
uint32_t seq,
uint32_t pid,
int32_t interface_index,
sa_family_t family)
: type_(type),
mode_(mode),
flags_(flags),
seq_(seq),
pid_(pid),
interface_index_(interface_index),
family_(family) {}
// static
std::unique_ptr<RTNLMessage> RTNLMessage::Decode(
base::span<const uint8_t> data) {
// Parse the nlmsghdr, and trim the data to the size |nlmsg_len|.
if (data.size() < sizeof(struct nlmsghdr)) {
return nullptr;
}
const struct nlmsghdr* header =
reinterpret_cast<const struct nlmsghdr*>(data.data());
if (data.size() < header->nlmsg_len) {
return nullptr;
}
data = data.subspan(0, header->nlmsg_len);
// Split the remaining data after the header.
if (data.size() < NLMSG_HDRLEN) {
return nullptr;
}
const base::span<const uint8_t> payload = data.subspan(NLMSG_HDRLEN);
Mode mode = kModeUnknown;
switch (header->nlmsg_type) {
case RTM_NEWLINK:
case RTM_NEWADDR:
case RTM_NEWROUTE:
case RTM_NEWRULE:
case RTM_NEWNDUSEROPT:
case RTM_NEWNEIGH:
mode = kModeAdd;
break;
case RTM_DELLINK:
case RTM_DELADDR:
case RTM_DELROUTE:
case RTM_DELRULE:
case RTM_DELNEIGH:
mode = kModeDelete;
break;
default:
return nullptr;
}
rtattr* attr_data = nullptr;
size_t attr_length = 0;
std::unique_ptr<RTNLMessage> msg;
switch (header->nlmsg_type) {
case RTM_NEWLINK:
case RTM_DELLINK:
attr_data = IFLA_RTA(NLMSG_DATA(header));
attr_length = IFLA_PAYLOAD(header);
msg = DecodeLink(mode, payload);
break;
case RTM_NEWADDR:
case RTM_DELADDR:
attr_data = IFA_RTA(NLMSG_DATA(header));
attr_length = IFA_PAYLOAD(header);
msg = DecodeAddress(mode, payload);
break;
case RTM_NEWROUTE:
case RTM_DELROUTE:
attr_data = RTM_RTA(NLMSG_DATA(header));
attr_length = RTM_PAYLOAD(header);
msg = DecodeRoute(mode, payload);
break;
case RTM_NEWRULE:
case RTM_DELRULE:
attr_data = RTM_RTA(NLMSG_DATA(header));
attr_length = RTM_PAYLOAD(header);
msg = DecodeRule(mode, payload);
break;
case RTM_NEWNDUSEROPT:
msg = DecodeNdUserOption(mode, payload);
break;
case RTM_NEWNEIGH:
case RTM_DELNEIGH:
attr_data = RTM_RTA(NLMSG_DATA(header));
attr_length = RTM_PAYLOAD(header);
msg = DecodeNeighbor(mode, payload);
break;
default:
NOTREACHED();
}
if (!msg) {
return nullptr;
}
msg->flags_ = header->nlmsg_flags;
msg->seq_ = header->nlmsg_seq;
msg->pid_ = header->nlmsg_pid;
const std::unique_ptr<RTNLAttrMap> attrs = ParseAttrs(attr_data, attr_length);
if (!attrs) {
return nullptr;
}
for (const auto& pair : *attrs) {
msg->SetAttribute(pair.first, pair.second);
}
return msg;
}
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeLink(
Mode mode, base::span<const uint8_t> payload) {
// Parse ifinfomsg struct.
if (payload.size() < NLMSG_ALIGN(sizeof(struct ifinfomsg))) {
return nullptr;
}
const struct ifinfomsg* ifi =
reinterpret_cast<const struct ifinfomsg*>(payload.data());
payload = payload.subspan(NLMSG_ALIGN(sizeof(struct ifinfomsg)));
// Parse the attributes.
// Note: |payload.data()| here is equivalent to IFLA_RTA() with the original
// payload.
std::unique_ptr<RTNLAttrMap> attrs = ParseAttrs(
reinterpret_cast<const rtattr*>(payload.data()), payload.size());
if (!attrs) {
return nullptr;
}
std::optional<std::string> kind_option;
if (base::Contains(*attrs, IFLA_LINKINFO)) {
auto& bytes = attrs->find(IFLA_LINKINFO)->second;
struct rtattr* link_data = reinterpret_cast<struct rtattr*>(bytes.data());
size_t link_len = bytes.size();
std::unique_ptr<RTNLAttrMap> linkinfo = ParseAttrs(link_data, link_len);
if (linkinfo && base::Contains(*linkinfo, IFLA_INFO_KIND)) {
const auto& kind_bytes = linkinfo->find(IFLA_INFO_KIND)->second;
const auto kind_string =
net_base::byte_utils::StringFromCStringBytes(kind_bytes);
if (base::IsStringASCII(kind_string)) {
kind_option = kind_string;
} else {
LOG(ERROR) << base::StringPrintf(
"Invalid kind <%s>, interface index %d",
base::HexEncode(kind_bytes).c_str(), ifi->ifi_index);
}
}
}
auto msg = std::make_unique<RTNLMessage>(kTypeLink, mode, 0, 0, 0,
ifi->ifi_index, ifi->ifi_family);
msg->set_link_status(
LinkStatus(ifi->ifi_type, ifi->ifi_flags, ifi->ifi_change, kind_option));
return msg;
}
// static
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeAddress(
Mode mode, base::span<const uint8_t> payload) {
if (payload.size() < sizeof(struct ifaddrmsg)) {
return nullptr;
}
const struct ifaddrmsg* ifa =
reinterpret_cast<const struct ifaddrmsg*>(payload.data());
auto msg = std::make_unique<RTNLMessage>(kTypeAddress, mode, 0, 0, 0,
ifa->ifa_index, ifa->ifa_family);
msg->set_address_status(
AddressStatus(ifa->ifa_prefixlen, ifa->ifa_flags, ifa->ifa_scope));
return msg;
}
// static
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeRoute(
Mode mode, base::span<const uint8_t> payload) {
if (payload.size() < sizeof(struct rtmsg)) {
return nullptr;
}
const struct rtmsg* rtm =
reinterpret_cast<const struct rtmsg*>(payload.data());
auto msg = std::make_unique<RTNLMessage>(kTypeRoute, mode, 0, 0, 0, 0,
rtm->rtm_family);
msg->set_route_status(RouteStatus(
rtm->rtm_dst_len, rtm->rtm_src_len, rtm->rtm_table, rtm->rtm_protocol,
rtm->rtm_scope, rtm->rtm_type, rtm->rtm_flags));
return msg;
}
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeRule(
Mode mode, base::span<const uint8_t> payload) {
if (payload.size() < sizeof(struct rtmsg)) {
return nullptr;
}
const struct rtmsg* rtm =
reinterpret_cast<const struct rtmsg*>(payload.data());
auto msg = std::make_unique<RTNLMessage>(kTypeRule, mode, 0, 0, 0, 0,
rtm->rtm_family);
msg->set_route_status(RouteStatus(
rtm->rtm_dst_len, rtm->rtm_src_len, rtm->rtm_table, rtm->rtm_protocol,
rtm->rtm_scope, rtm->rtm_type, rtm->rtm_flags));
return msg;
}
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeNdUserOption(
Mode mode, base::span<const uint8_t> payload) {
// Parse the nduseroptmsg struct.
if (payload.size() < sizeof(struct nduseroptmsg)) {
return nullptr;
}
const struct nduseroptmsg* nd_user_opt =
reinterpret_cast<const struct nduseroptmsg*>(payload.data());
payload = payload.subspan(sizeof(struct nduseroptmsg));
// Verify IP family.
const int32_t interface_index = nd_user_opt->nduseropt_ifindex;
const sa_family_t family = nd_user_opt->nduseropt_family;
if (FromSAFamily(family) != IPFamily::kIPv6) {
return nullptr;
}
// Parse the option header.
if (payload.size() < sizeof(NDUserOptionHeader)) {
return nullptr;
}
const NDUserOptionHeader* nd_user_option_header =
reinterpret_cast<const NDUserOptionHeader*>(payload.data());
payload = payload.subspan(sizeof(NDUserOptionHeader));
// Verify option length.
// The length field in the header is in units of 8 octets, which indicates the
// size of payload, including the NDUserOptionHeader.
const size_t opt_len = nd_user_option_header->length * 8;
if (opt_len != nd_user_opt->nduseropt_opts_len) {
return nullptr;
}
if (payload.size() < opt_len - sizeof(NDUserOptionHeader)) {
return nullptr;
}
payload = payload.subspan(0, opt_len - sizeof(NDUserOptionHeader));
std::unique_ptr<RTNLMessage> msg;
switch (nd_user_option_header->type) {
case ND_OPT_DNSSL: {
msg = std::make_unique<RTNLMessage>(kTypeDnssl, mode, 0, 0, 0,
interface_index, family);
if (!msg->ParseDnsslOption(payload)) {
LOG(ERROR) << "Invalid DNSSL RTNL packet.";
return nullptr;
}
return msg;
}
case ND_OPT_RDNSS: {
// Parse RNDSS (Recursive DNS Server) option.
msg = std::make_unique<RTNLMessage>(kTypeRdnss, mode, 0, 0, 0,
interface_index, family);
if (!msg->ParseRdnssOption(payload)) {
LOG(ERROR) << "Invalid RDNSS RTNL packet.";
return nullptr;
}
return msg;
}
case ND_OPT_CAPTIVE_PORTAL: {
// Parse Captive portal URI option.
msg = std::make_unique<RTNLMessage>(kTypeCaptivePortal, mode, 0, 0, 0,
interface_index, family);
if (!msg->ParseCaptivePortalOption(payload)) {
LOG(ERROR) << "Invalid captive portal URI RTNL packet.";
return nullptr;
}
return msg;
}
default:
msg = std::make_unique<RTNLMessage>(kTypeNdUserOption, mode, 0, 0, 0,
interface_index, family);
msg->SetNdUserOptionBytes(base::span(
reinterpret_cast<const uint8_t*>(nd_user_option_header), opt_len));
return msg;
}
}
void RTNLMessage::SetNdUserOptionBytes(base::span<const uint8_t> data) {
LOG_IF(DFATAL, data.empty()) << "ND user option data should not be empty";
nd_user_option_.type = data[0];
nd_user_option_.option_bytes.assign(std::begin(data), std::end(data));
}
bool RTNLMessage::ParseDnsslOption(base::span<const uint8_t> data) {
// Section 5.2 of RFC8106.
// The layout of DNSSL option after the type and length field is:
// - Reserved: 2 bytes
// - Lifetime: 4 bytes
// - Domain names of DNS search list: one or more domain name that is encoded
// as Section 3.1 of RFC1035.
if (data.size() < 2 + 4) {
return false;
}
// Skip the reserved field.
data = data.subspan(2);
// Parse the lifetime.
const uint32_t lifetime =
ntohl(*byte_utils::FromBytes<uint32_t>(data.subspan(0, 4)));
data = data.subspan(4);
std::vector<std::string> domains;
std::vector<std::string_view> tokens;
while (!data.empty()) {
const uint8_t token_size = data[0];
data = data.subspan(1);
if (data.size() < token_size) {
return false;
}
if (token_size > 0) {
tokens.push_back(std::string_view(
reinterpret_cast<const char*>(data.data()), token_size));
data = data.subspan(token_size);
} else if (!tokens.empty()) {
domains.push_back(base::JoinString(tokens, "."));
tokens.clear();
}
}
if (!tokens.empty()) {
domains.push_back(base::JoinString(tokens, "."));
}
dnssl_option_.domains = std::move(domains);
dnssl_option_.lifetime = lifetime;
return true;
}
bool RTNLMessage::ParseRdnssOption(base::span<const uint8_t> data) {
// Section 5.1 of RFC8106.
// The layout of RDNSS option after the type and length field is:
// - Reserved: 2 bytes
// - Lifetime: 4 bytes
// - IPv6 Recursive DNS servers: one or more 16-byte IPv6 addresses
const size_t addr_length = IPv6Address::kAddressLength;
if (data.size() < 2 + 4 + addr_length) {
return false;
}
// Skip the reserved field.
data = data.subspan(2);
// Parse the lifetime.
const uint32_t lifetime =
ntohl(*byte_utils::FromBytes<uint32_t>(data.subspan(0, 4)));
data = data.subspan(4);
// Parse the recursive DNS servers.
// Verify data size are multiple of individual address size.
if (data.size() % addr_length != 0) {
return false;
}
// Parse the DNS server addresses.
std::vector<IPv6Address> dns_server_addresses;
while (!data.empty()) {
dns_server_addresses.push_back(
*IPv6Address::CreateFromBytes(data.subspan(0, addr_length)));
data = data.subspan(addr_length);
}
set_rdnss_option(RdnssOption(lifetime, dns_server_addresses));
return true;
}
bool RTNLMessage::ParseCaptivePortalOption(base::span<const uint8_t> data) {
// Section 2.3 of RFC8910.
// The layout of RDNSS option after the type and length field is:
// - URI: padded with 0 to make the total option length a multiple of 8 bytes
if (data.empty()) {
LOG(ERROR) << "Empty payload for captive portal URI";
return false;
}
// The string is not guaranteed to be null terminated, so we need to find the
// length by strnlen().
const char* str = reinterpret_cast<const char*>(data.data());
const std::string_view str_view(str, strnlen(str, data.size()));
const std::optional<HttpUrl> url = HttpUrl::CreateFromString(str_view);
if (!url.has_value() || url->protocol() != HttpUrl::Protocol::kHttps) {
LOG(ERROR) << "Invalid captive portal URI: " << str_view;
return false;
}
set_captive_portal_uri(*url);
return true;
}
std::unique_ptr<RTNLMessage> RTNLMessage::DecodeNeighbor(
Mode mode, base::span<const uint8_t> payload) {
if (payload.size() < sizeof(struct ndmsg)) {
return nullptr;
}
const struct ndmsg* ndm =
reinterpret_cast<const struct ndmsg*>(payload.data());
auto msg = std::make_unique<RTNLMessage>(kTypeNeighbor, mode, 0, 0, 0,
ndm->ndm_ifindex, ndm->ndm_family);
msg->set_neighbor_status(
NeighborStatus(ndm->ndm_state, ndm->ndm_flags, ndm->ndm_type));
return msg;
}
std::vector<uint8_t> RTNLMessage::Encode() const {
if (type_ != kTypeLink && type_ != kTypeAddress && type_ != kTypeRoute &&
type_ != kTypeRule && type_ != kTypeNeighbor) {
return {};
}
RTNLHeader hdr;
hdr.hdr.nlmsg_flags = flags_;
hdr.hdr.nlmsg_seq = seq_;
hdr.hdr.nlmsg_pid = pid_;
switch (type_) {
case kTypeLink:
if (!EncodeLink(&hdr)) {
return {};
}
break;
case kTypeAddress:
if (!EncodeAddress(&hdr)) {
return {};
}
break;
case kTypeRoute:
case kTypeRule:
if (!EncodeRoute(&hdr)) {
return {};
}
break;
case kTypeNeighbor:
if (!EncodeNeighbor(&hdr)) {
return {};
}
break;
default:
NOTREACHED();
}
if (mode_ == kModeGet) {
hdr.hdr.nlmsg_flags |= NLM_F_REQUEST | NLM_F_DUMP;
}
uint32_t header_length = hdr.hdr.nlmsg_len;
const std::vector<uint8_t> attributes = PackAttrs(attributes_);
hdr.hdr.nlmsg_len =
NLMSG_ALIGN(hdr.hdr.nlmsg_len) + static_cast<uint32_t>(attributes.size());
std::vector<uint8_t> packet(reinterpret_cast<uint8_t*>(&hdr),
reinterpret_cast<uint8_t*>(&hdr) + header_length);
packet.insert(packet.end(), attributes.begin(), attributes.end());
return packet;
}
bool RTNLMessage::EncodeLink(RTNLHeader* hdr) const {
switch (mode_) {
case kModeAdd:
hdr->hdr.nlmsg_type = RTM_NEWLINK;
break;
case kModeDelete:
hdr->hdr.nlmsg_type = RTM_DELLINK;
break;
case kModeGet:
case kModeQuery:
hdr->hdr.nlmsg_type = RTM_GETLINK;
break;
default:
NOTIMPLEMENTED();
return false;
}
hdr->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(hdr->ifi));
hdr->ifi.ifi_family = static_cast<unsigned char>(family_);
hdr->ifi.ifi_index = interface_index_;
hdr->ifi.ifi_type = static_cast<uint16_t>(link_status_.type);
hdr->ifi.ifi_flags = link_status_.flags;
hdr->ifi.ifi_change = link_status_.change;
return true;
}
bool RTNLMessage::EncodeAddress(RTNLHeader* hdr) const {
switch (mode_) {
case kModeAdd:
hdr->hdr.nlmsg_type = RTM_NEWADDR;
break;
case kModeDelete:
hdr->hdr.nlmsg_type = RTM_DELADDR;
break;
case kModeGet:
case kModeQuery:
hdr->hdr.nlmsg_type = RTM_GETADDR;
break;
default:
NOTIMPLEMENTED();
return false;
}
hdr->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(hdr->ifa));
hdr->ifa.ifa_family = static_cast<unsigned char>(family_);
hdr->ifa.ifa_prefixlen = address_status_.prefix_len;
hdr->ifa.ifa_flags = address_status_.flags;
hdr->ifa.ifa_scope = address_status_.scope;
hdr->ifa.ifa_index = static_cast<uint32_t>(interface_index_);
return true;
}
bool RTNLMessage::EncodeRoute(RTNLHeader* hdr) const {
// Routes and routing rules are both based on struct rtm
switch (mode_) {
case kModeAdd:
hdr->hdr.nlmsg_type = (type_ == kTypeRoute) ? RTM_NEWROUTE : RTM_NEWRULE;
break;
case kModeDelete:
hdr->hdr.nlmsg_type = (type_ == kTypeRoute) ? RTM_DELROUTE : RTM_DELRULE;
break;
case kModeGet:
case kModeQuery:
hdr->hdr.nlmsg_type = (type_ == kTypeRoute) ? RTM_GETROUTE : RTM_GETRULE;
break;
default:
NOTIMPLEMENTED();
return false;
}
hdr->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(hdr->rtm));
hdr->rtm.rtm_family = static_cast<unsigned char>(family_);
hdr->rtm.rtm_dst_len = route_status_.dst_prefix;
hdr->rtm.rtm_src_len = route_status_.src_prefix;
hdr->rtm.rtm_table = route_status_.table;
hdr->rtm.rtm_protocol = route_status_.protocol;
hdr->rtm.rtm_scope = route_status_.scope;
hdr->rtm.rtm_type = route_status_.type;
hdr->rtm.rtm_flags = route_status_.flags;
return true;
}
bool RTNLMessage::EncodeNeighbor(RTNLHeader* hdr) const {
switch (mode_) {
case kModeAdd:
hdr->hdr.nlmsg_type = RTM_NEWNEIGH;
break;
case kModeDelete:
hdr->hdr.nlmsg_type = RTM_DELNEIGH;
break;
case kModeGet:
case kModeQuery:
hdr->hdr.nlmsg_type = RTM_GETNEIGH;
break;
default:
NOTIMPLEMENTED();
return false;
}
hdr->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(hdr->ndm));
hdr->ndm.ndm_family = static_cast<unsigned char>(family_);
hdr->ndm.ndm_ifindex = interface_index_;
hdr->ndm.ndm_state = neighbor_status_.state;
hdr->ndm.ndm_flags = neighbor_status_.flags;
hdr->ndm.ndm_type = neighbor_status_.type;
return true;
}
uint32_t RTNLMessage::GetUint32Attribute(uint16_t attr) const {
return net_base::byte_utils::FromBytes<uint32_t>(GetAttribute(attr))
.value_or(0);
}
std::string RTNLMessage::GetStringAttribute(uint16_t attr) const {
if (!HasAttribute(attr))
return "";
return net_base::byte_utils::StringFromCStringBytes(GetAttribute(attr));
}
std::string RTNLMessage::GetIflaIfname() const {
return GetStringAttribute(IFLA_IFNAME);
}
std::optional<IPCIDR> RTNLMessage::GetAddress() const {
// Use IFA_LOCAL if it's not empty and fallback to IFA_ADDRESS. According the
// kernel comment (/usr/include/linux/if_addr.h), for a point-to-point link,
// IFA_LOCAL is the local address on the interface while IFA_ADDRESS is the
// peer one. Since IFA_LOCAL won't always be set (e.g., for some IPv6
// addresses), we also need to query IFA_ADDRESS.
std::vector<uint8_t> addr_bytes = HasAttribute(IFA_LOCAL)
? GetAttribute(IFA_LOCAL)
: GetAttribute(IFA_ADDRESS);
return IPCIDR::CreateFromBytesAndPrefix(
addr_bytes, address_status_.prefix_len, FromSAFamily(family_));
}
uint32_t RTNLMessage::GetRtaTable() const {
return GetUint32Attribute(RTA_TABLE);
}
std::optional<IPCIDR> RTNLMessage::GetRtaDst() const {
return IPCIDR::CreateFromBytesAndPrefix(
GetAttribute(RTA_DST), route_status_.dst_prefix, FromSAFamily(family_));
}
std::optional<IPCIDR> RTNLMessage::GetRtaSrc() const {
return IPCIDR::CreateFromBytesAndPrefix(
GetAttribute(RTA_SRC), route_status_.src_prefix, FromSAFamily(family_));
}
std::optional<IPAddress> RTNLMessage::GetRtaGateway() const {
return IPAddress::CreateFromBytes(GetAttribute(RTA_GATEWAY),
FromSAFamily(family_));
}
std::optional<IPAddress> RTNLMessage::GetRtaPrefSrc() const {
return IPAddress::CreateFromBytes(GetAttribute(RTA_PREFSRC),
FromSAFamily(family_));
}
uint32_t RTNLMessage::GetRtaOif() const {
return GetUint32Attribute(RTA_OIF);
}
std::string RTNLMessage::GetRtaOifname() const {
return IndexToName(GetRtaOif());
}
uint32_t RTNLMessage::GetRtaPriority() const {
return GetUint32Attribute(RTA_PRIORITY);
}
uint32_t RTNLMessage::GetFraTable() const {
return GetUint32Attribute(FRA_TABLE);
}
std::string RTNLMessage::GetFraOifname() const {
return GetStringAttribute(FRA_OIFNAME);
}
std::string RTNLMessage::GetFraIifname() const {
return GetStringAttribute(FRA_IIFNAME);
}
std::optional<IPCIDR> RTNLMessage::GetFraSrc() const {
return IPCIDR::CreateFromBytesAndPrefix(
GetAttribute(FRA_SRC), route_status_.src_prefix, FromSAFamily(family_));
}
std::optional<IPCIDR> RTNLMessage::GetFraDst() const {
return IPCIDR::CreateFromBytesAndPrefix(
GetAttribute(FRA_DST), route_status_.dst_prefix, FromSAFamily(family_));
}
uint32_t RTNLMessage::GetFraFwmark() const {
return GetUint32Attribute(FRA_FWMARK);
}
uint32_t RTNLMessage::GetFraFwmask() const {
return GetUint32Attribute(FRA_FWMASK);
}
uint32_t RTNLMessage::GetFraPriority() const {
return GetUint32Attribute(FRA_PRIORITY);
}
void RTNLMessage::SetIflaInfoKind(const std::string& link_kind,
base::span<const uint8_t> info_data) {
// The maximum length of IFLA_INFO_KIND attribute is MODULE_NAME_LEN, defined
// in /include/linux/module.h, as (64 - sizeof(unsigned long)). Set it to a
// fixed value here.
constexpr uint32_t kMaxModuleNameLen = 56;
if (link_kind.length() >= kMaxModuleNameLen) {
LOG(DFATAL) << "link_kind is too long: " << link_kind;
}
link_status_.kind = link_kind;
RTNLAttrMap link_info_map;
link_info_map[IFLA_INFO_KIND] =
net_base::byte_utils::StringToCStringBytes(link_kind.c_str());
if (!info_data.empty()) {
link_info_map[IFLA_INFO_DATA] = {info_data.data(),
info_data.data() + info_data.size()};
}
if (HasAttribute(IFLA_LINKINFO)) {
LOG(DFATAL) << "IFLA_LINKINFO has already been set.";
}
SetAttribute(IFLA_LINKINFO, PackAttrs(link_info_map));
}
// static
std::string RTNLMessage::ModeToString(RTNLMessage::Mode mode) {
switch (mode) {
case RTNLMessage::kModeGet:
return "Get";
case RTNLMessage::kModeAdd:
return "Add";
case RTNLMessage::kModeDelete:
return "Delete";
case RTNLMessage::kModeQuery:
return "Query";
default:
return "UnknownMode";
}
}
// static
std::string RTNLMessage::TypeToString(RTNLMessage::Type type) {
switch (type) {
case RTNLMessage::kTypeLink:
return "Link";
case RTNLMessage::kTypeAddress:
return "Address";
case RTNLMessage::kTypeRoute:
return "Route";
case RTNLMessage::kTypeRule:
return "Rule";
case RTNLMessage::kTypeRdnss:
return "Rdnss";
case RTNLMessage::kTypeDnssl:
return "Dnssl";
case RTNLMessage::kTypeNeighbor:
return "Neighbor";
case RTNLMessage::kTypeNdUserOption:
return "NdUserOption";
default:
return "UnknownType";
}
}
std::string RTNLMessage::ToString() const {
// Include the space separator in |ip_family_str| to avoid double spaces for
// messages with family AF_UNSPEC.
const auto ip_family = FromSAFamily(family());
std::string ip_family_str =
" " + (ip_family ? net_base::ToString(*ip_family) : "unknown");
std::string details;
switch (type()) {
case RTNLMessage::kTypeLink:
ip_family_str = "";
details = base::StringPrintf(
"%s[%d] type %s flags <%s> change %X", GetIflaIfname().c_str(),
interface_index_, GetNetDeviceTypeName(link_status_.type).c_str(),
PrintFlags(link_status_.flags, kNetDeviceFlags, ",").c_str(),
link_status_.change);
if (link_status_.kind.has_value())
details += " kind " + link_status_.kind.value();
break;
case RTNLMessage::kTypeAddress:
if (const auto addr = GetAddress(); addr.has_value()) {
details = base::StringPrintf(
"%s if %s[%d] flags %s scope %d", addr->ToString().c_str(),
IndexToName(static_cast<unsigned int>(interface_index_)).c_str(),
interface_index_,
address_status_.flags
? PrintFlags(address_status_.flags, kIfaFlags).c_str()
: "0",
address_status_.scope);
} else {
LOG(ERROR)
<< "RTNL address message does not have a valid local address";
}
break;
case RTNLMessage::kTypeRoute:
if (const auto addr = GetRtaSrc(); addr.has_value())
details += base::StringPrintf("src %s ", addr->ToString().c_str());
if (const auto addr = GetRtaDst(); addr.has_value())
details += base::StringPrintf("dst %s ", addr->ToString().c_str());
if (const auto addr = GetRtaGateway(); addr.has_value())
details += "via " + addr->ToString() + " ";
if (HasAttribute(RTA_OIF))
details += base::StringPrintf("if %s[%d] ", GetRtaOifname().c_str(),
GetRtaOif());
details += base::StringPrintf(
"table %d priority %d protocol %s type %s", GetRtaTable(),
GetRtaPriority(), GetRouteProtocol(route_status_.protocol).c_str(),
GetRouteTypeName(route_status_.type).c_str());
break;
case RTNLMessage::kTypeRule:
// Rules are serialized via struct fib_rule_hdr which aligns with struct
// rtmsg used for routes such that |type| corresponds to the rule action.
// |protocol| and |scope| are currently unused as of Linux 5.6.
if (HasAttribute(FRA_IIFNAME))
details += "iif " + GetFraIifname() + " ";
if (HasAttribute(FRA_OIFNAME))
details += "oif " + GetFraOifname() = " ";
if (const auto addr = GetFraSrc(); addr.has_value())
details += base::StringPrintf("src %s ", addr->ToString().c_str());
if (const auto addr = GetFraDst(); addr.has_value())
details += base::StringPrintf("dst %s ", addr->ToString().c_str());
if (HasAttribute(FRA_FWMARK))
details += base::StringPrintf("fwmark 0x%X/0x%X ", GetFraFwmark(),
GetFraFwmask());
details += base::StringPrintf(
"table %d priority %d action %s flags %X", GetFraTable(),
GetFraPriority(), GetRuleActionName(route_status_.type).c_str(),
route_status_.flags);
break;
case RTNLMessage::kTypeRdnss:
details = rdnss_option_.ToString();
break;
case RTNLMessage::kTypeDnssl:
details = dnssl_option_.ToString();
break;
case RTNLMessage::kTypeNdUserOption:
details = nd_user_option_.ToString();
break;
case RTNLMessage::kTypeNeighbor:
details = neighbor_status_.ToString();
break;
default:
break;
}
return base::StringPrintf("%s%s %s: %s", ModeToString(mode()).c_str(),
ip_family_str.c_str(), TypeToString(type()).c_str(),
details.c_str());
}
} // namespace net_base