blob: 91fa4334ee9a11c65b396e6454064f9d6adc5ecc [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/net/rtnl_message.h"
#include <net/if.h> // NB: order matters; this conflicts with <linux/if.h>
#include <arpa/inet.h>
#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 "shill/net/ndisc.h"
namespace shill {
namespace {
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(uint16_t 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(struct rtattr* data, int len) {
const auto* attr_data = reinterpret_cast<const char*>(data);
int attr_len = len;
RTNLAttrMap attrs;
while (data && RTA_OK(data, len)) {
attrs[data->rta_type] = ByteString(
reinterpret_cast<unsigned char*>(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 <"
<< ByteString(attr_data, attr_len).HexEncode()
<< ">, 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(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;
};
};
std::string RTNLMessage::NeighborStatus::ToString() const {
return base::StringPrintf("NeighborStatus state %d flags %X type %d", state,
flags, type);
}
std::string RTNLMessage::RdnssOption::ToString() const {
return base::StringPrintf("RdnssOption lifetime %d", lifetime);
}
ByteString RTNLMessage::PackAttrs(const RTNLAttrMap& attrs) {
ByteString attributes;
for (const auto& pair : attrs) {
size_t len = RTA_LENGTH(pair.second.GetLength());
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,
};
ByteString header(reinterpret_cast<unsigned char*>(&rt_attr),
sizeof(rt_attr));
header.Resize(RTA_ALIGN(header.GetLength()));
attributes.Append(header);
ByteString data(pair.second);
data.Resize(RTA_ALIGN(data.GetLength()));
attributes.Append(data);
}
return attributes;
}
RTNLMessage::RTNLMessage()
: type_(kTypeUnknown),
mode_(kModeUnknown),
flags_(0),
seq_(0),
pid_(0),
interface_index_(0),
family_(IPAddress::kFamilyUnknown) {}
RTNLMessage::RTNLMessage(Type type,
Mode mode,
uint16_t flags,
uint32_t seq,
uint32_t pid,
int32_t interface_index,
IPAddress::Family family)
: type_(type),
mode_(mode),
flags_(flags),
seq_(seq),
pid_(pid),
interface_index_(interface_index),
family_(family) {}
bool RTNLMessage::Decode(const ByteString& data) {
return Decode(data.GetConstData(), data.GetLength());
}
bool RTNLMessage::Decode(const uint8_t* data, size_t length) {
bool ret = DecodeInternal(data, length);
if (!ret) {
Reset();
}
return ret;
}
bool RTNLMessage::DecodeInternal(const uint8_t* data, size_t length) {
const RTNLHeader* hdr = reinterpret_cast<const RTNLHeader*>(data);
if (length < sizeof(hdr->hdr) || length < hdr->hdr.nlmsg_len ||
hdr->hdr.nlmsg_len < sizeof(hdr->hdr)) {
return false;
}
mode_ = kModeUnknown;
switch (hdr->hdr.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 false;
}
rtattr* attr_data = nullptr;
int attr_length = 0;
switch (hdr->hdr.nlmsg_type) {
case RTM_NEWLINK:
case RTM_DELLINK:
if (!DecodeLink(hdr, &attr_data, &attr_length))
return false;
break;
case RTM_NEWADDR:
case RTM_DELADDR:
if (!DecodeAddress(hdr, &attr_data, &attr_length))
return false;
break;
case RTM_NEWROUTE:
case RTM_DELROUTE:
if (!DecodeRoute(hdr, &attr_data, &attr_length))
return false;
break;
case RTM_NEWRULE:
case RTM_DELRULE:
if (!DecodeRule(hdr, &attr_data, &attr_length))
return false;
break;
case RTM_NEWNDUSEROPT:
if (!DecodeNdUserOption(hdr, &attr_data, &attr_length))
return false;
break;
case RTM_NEWNEIGH:
case RTM_DELNEIGH:
if (!DecodeNeighbor(hdr, &attr_data, &attr_length))
return false;
break;
default:
NOTREACHED();
}
flags_ = hdr->hdr.nlmsg_flags;
seq_ = hdr->hdr.nlmsg_seq;
pid_ = hdr->hdr.nlmsg_pid;
std::unique_ptr<RTNLAttrMap> attrs = ParseAttrs(attr_data, attr_length);
if (!attrs) {
attributes_.clear();
return false;
}
for (const auto& pair : *attrs) {
SetAttribute(pair.first, pair.second);
}
return true;
}
bool RTNLMessage::DecodeLink(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->ifi))) {
return false;
}
*attr_data = IFLA_RTA(NLMSG_DATA(&hdr->hdr));
*attr_length = IFLA_PAYLOAD(&hdr->hdr);
type_ = kTypeLink;
family_ = hdr->ifi.ifi_family;
interface_index_ = hdr->ifi.ifi_index;
std::unique_ptr<RTNLAttrMap> attrs = ParseAttrs(*attr_data, *attr_length);
if (!attrs)
return false;
std::optional<std::string> kind_option;
if (base::Contains(*attrs, IFLA_LINKINFO)) {
ByteString& bytes = attrs->find(IFLA_LINKINFO)->second;
struct rtattr* link_data =
reinterpret_cast<struct rtattr*>(bytes.GetData());
size_t link_len = bytes.GetLength();
std::unique_ptr<RTNLAttrMap> linkinfo = ParseAttrs(link_data, link_len);
if (linkinfo && base::Contains(*linkinfo, IFLA_INFO_KIND)) {
ByteString& kindBytes = linkinfo->find(IFLA_INFO_KIND)->second;
const char* kind = reinterpret_cast<const char*>(kindBytes.GetData());
std::string kind_string(kind, strnlen(kind, kindBytes.GetLength()));
if (base::IsStringASCII(kind_string))
kind_option = kind_string;
else
LOG(ERROR) << base::StringPrintf(
"Invalid kind <%s>, interface index %d",
kindBytes.HexEncode().c_str(), interface_index_);
}
}
set_link_status(LinkStatus(hdr->ifi.ifi_type, hdr->ifi.ifi_flags,
hdr->ifi.ifi_change, kind_option));
return true;
}
bool RTNLMessage::DecodeAddress(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->ifa))) {
return false;
}
*attr_data = IFA_RTA(NLMSG_DATA(&hdr->hdr));
*attr_length = IFA_PAYLOAD(&hdr->hdr);
type_ = kTypeAddress;
family_ = hdr->ifa.ifa_family;
interface_index_ = hdr->ifa.ifa_index;
set_address_status(AddressStatus(hdr->ifa.ifa_prefixlen, hdr->ifa.ifa_flags,
hdr->ifa.ifa_scope));
return true;
}
bool RTNLMessage::DecodeRoute(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->rtm))) {
return false;
}
*attr_data = RTM_RTA(NLMSG_DATA(&hdr->hdr));
*attr_length = RTM_PAYLOAD(&hdr->hdr);
type_ = kTypeRoute;
family_ = hdr->rtm.rtm_family;
set_route_status(RouteStatus(hdr->rtm.rtm_dst_len, hdr->rtm.rtm_src_len,
hdr->rtm.rtm_table, hdr->rtm.rtm_protocol,
hdr->rtm.rtm_scope, hdr->rtm.rtm_type,
hdr->rtm.rtm_flags));
return true;
}
bool RTNLMessage::DecodeRule(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->rtm))) {
return false;
}
*attr_data = RTM_RTA(NLMSG_DATA(&hdr->hdr));
*attr_length = RTM_PAYLOAD(&hdr->hdr);
type_ = kTypeRule;
family_ = hdr->rtm.rtm_family;
set_route_status(RouteStatus(hdr->rtm.rtm_dst_len, hdr->rtm.rtm_src_len,
hdr->rtm.rtm_table, hdr->rtm.rtm_protocol,
hdr->rtm.rtm_scope, hdr->rtm.rtm_type,
hdr->rtm.rtm_flags));
return true;
}
bool RTNLMessage::DecodeNdUserOption(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
size_t min_payload_len = sizeof(hdr->nd_user_opt) +
sizeof(struct nduseroptmsg) +
sizeof(NDUserOptionHeader);
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(min_payload_len)) {
return false;
}
interface_index_ = hdr->nd_user_opt.nduseropt_ifindex;
family_ = hdr->nd_user_opt.nduseropt_family;
// Verify IP family.
if (family_ != IPAddress::kFamilyIPv6) {
return false;
}
// Verify message must at-least contain the option header.
if (hdr->nd_user_opt.nduseropt_opts_len < sizeof(NDUserOptionHeader)) {
return false;
}
// Parse the option header.
const NDUserOptionHeader* nd_user_option_header =
reinterpret_cast<const NDUserOptionHeader*>(
reinterpret_cast<const uint8_t*>(&hdr->nd_user_opt) +
sizeof(struct nduseroptmsg));
uint32_t lifetime = ntohl(nd_user_option_header->lifetime);
// Verify option length.
// The length field in the header is in units of 8 octets.
int opt_len = static_cast<int>(nd_user_option_header->length) * 8;
if (opt_len != hdr->nd_user_opt.nduseropt_opts_len) {
return false;
}
// Determine option data pointer and data length.
const uint8_t* option_data =
reinterpret_cast<const uint8_t*>(nd_user_option_header + 1);
int data_len = opt_len - sizeof(NDUserOptionHeader);
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(min_payload_len + data_len)) {
return false;
}
if (nd_user_option_header->type == ND_OPT_DNSSL) {
// TODO(zqiu): Parse DNSSL (DNS Search List) option.
type_ = kTypeDnssl;
return true;
} else if (nd_user_option_header->type == ND_OPT_RDNSS) {
// Parse RNDSS (Recursive DNS Server) option.
type_ = kTypeRdnss;
return ParseRdnssOption(option_data, data_len, lifetime);
}
return false;
}
bool RTNLMessage::ParseRdnssOption(const uint8_t* data,
int length,
uint32_t lifetime) {
const int addr_length = IPAddress::GetAddressLength(IPAddress::kFamilyIPv6);
// Verify data size are multiple of individual address size.
if (length % addr_length != 0) {
return false;
}
// Parse the DNS server addresses.
std::vector<IPAddress> dns_server_addresses;
while (length > 0) {
dns_server_addresses.push_back(
IPAddress(IPAddress::kFamilyIPv6, ByteString(data, addr_length)));
length -= addr_length;
data += addr_length;
}
set_rdnss_option(RdnssOption(lifetime, dns_server_addresses));
return true;
}
bool RTNLMessage::DecodeNeighbor(const RTNLHeader* hdr,
rtattr** attr_data,
int* attr_length) {
if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->ndm))) {
return false;
}
interface_index_ = hdr->ndm.ndm_ifindex;
family_ = hdr->ndm.ndm_family;
type_ = kTypeNeighbor;
*attr_data = RTM_RTA(NLMSG_DATA(&hdr->hdr));
*attr_length = RTM_PAYLOAD(&hdr->hdr);
set_neighbor_status(NeighborStatus(hdr->ndm.ndm_state, hdr->ndm.ndm_flags,
hdr->ndm.ndm_type));
return true;
}
ByteString RTNLMessage::Encode() const {
if (type_ != kTypeLink && type_ != kTypeAddress && type_ != kTypeRoute &&
type_ != kTypeRule && type_ != kTypeNeighbor) {
return ByteString();
}
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 ByteString();
}
break;
case kTypeAddress:
if (!EncodeAddress(&hdr)) {
return ByteString();
}
break;
case kTypeRoute:
case kTypeRule:
if (!EncodeRoute(&hdr)) {
return ByteString();
}
break;
case kTypeNeighbor:
if (!EncodeNeighbor(&hdr)) {
return ByteString();
}
break;
default:
NOTREACHED();
}
if (mode_ == kModeGet) {
hdr.hdr.nlmsg_flags |= NLM_F_REQUEST | NLM_F_DUMP;
}
size_t header_length = hdr.hdr.nlmsg_len;
ByteString attributes = PackAttrs(attributes_);
hdr.hdr.nlmsg_len = NLMSG_ALIGN(hdr.hdr.nlmsg_len) + attributes.GetLength();
ByteString packet(reinterpret_cast<unsigned char*>(&hdr), header_length);
packet.Append(attributes);
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 = family_;
hdr->ifi.ifi_index = interface_index_;
hdr->ifi.ifi_type = 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 = 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 = 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 = 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 = 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;
}
void RTNLMessage::Reset() {
type_ = kTypeUnknown;
mode_ = kModeUnknown;
flags_ = 0;
seq_ = 0;
pid_ = 0;
interface_index_ = 0;
family_ = IPAddress::kFamilyUnknown;
link_status_ = LinkStatus();
address_status_ = AddressStatus();
route_status_ = RouteStatus();
neighbor_status_ = NeighborStatus();
rdnss_option_ = RdnssOption();
attributes_.clear();
}
uint32_t RTNLMessage::GetUint32Attribute(uint16_t attr) const {
uint32_t val = 0;
GetAttribute(attr).ConvertToCPUUInt32(&val);
return val;
}
std::string RTNLMessage::GetStringAttribute(uint16_t attr) const {
if (!HasAttribute(attr))
return "";
ByteString bytes = GetAttribute(attr);
size_t len = strnlen(bytes.GetConstCString(), bytes.GetLength());
return std::string(bytes.GetConstCString(), len);
}
std::string RTNLMessage::GetIflaIfname() const {
return GetStringAttribute(IFLA_IFNAME);
}
IPAddress RTNLMessage::GetIfaAddress() const {
return IPAddress(family_, GetAttribute(IFA_ADDRESS),
address_status_.prefix_len);
}
uint32_t RTNLMessage::GetRtaTable() const {
return GetUint32Attribute(RTA_TABLE);
}
IPAddress RTNLMessage::GetRtaDst() const {
return IPAddress(family_, GetAttribute(RTA_DST), route_status_.dst_prefix);
}
IPAddress RTNLMessage::GetRtaSrc() const {
return IPAddress(family_, GetAttribute(RTA_SRC), route_status_.src_prefix);
}
IPAddress RTNLMessage::GetRtaGateway() const {
return IPAddress(family_, GetAttribute(RTA_GATEWAY));
}
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);
}
IPAddress RTNLMessage::GetFraSrc() const {
return IPAddress(family_, GetAttribute(FRA_SRC), route_status_.src_prefix);
}
IPAddress RTNLMessage::GetFraDst() const {
return IPAddress(family_, GetAttribute(FRA_DST), route_status_.dst_prefix);
}
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,
const ByteString& 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] = ByteString{link_kind, true};
if (!info_data.IsEmpty()) {
link_info_map[IFLA_INFO_DATA] = info_data;
}
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";
default:
return "UnknownType";
}
}
std::string RTNLMessage::ToString() const {
// Include the space separator in |ip_family| to avoid double spaces for
// messages with family AF_UNSPEC.
std::string ip_family = " " + IPAddress::GetAddressFamilyName(family());
std::string details;
switch (type()) {
case RTNLMessage::kTypeLink:
ip_family = "";
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:
details = base::StringPrintf(
"%s/%d if %s[%d] flags %s scope %d",
GetIfaAddress().ToString().c_str(), address_status_.prefix_len,
IndexToName(interface_index_).c_str(), interface_index_,
address_status_.flags
? PrintFlags(address_status_.flags, kIfaFlags).c_str()
: "0",
address_status_.scope);
break;
case RTNLMessage::kTypeRoute:
if (HasAttribute(RTA_SRC))
details +=
base::StringPrintf("src %s/%d ", GetRtaSrc().ToString().c_str(),
route_status_.src_prefix);
if (HasAttribute(RTA_DST))
details +=
base::StringPrintf("dst %s/%d ", GetRtaDst().ToString().c_str(),
route_status_.dst_prefix);
if (HasAttribute(RTA_GATEWAY))
details += "via " + GetRtaGateway().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 (HasAttribute(FRA_SRC))
details +=
base::StringPrintf("src %s/%d ", GetFraSrc().ToString().c_str(),
route_status_.src_prefix);
if (HasAttribute(FRA_DST))
details +=
base::StringPrintf("dst %s/%d ", GetFraDst().ToString().c_str(),
route_status_.dst_prefix);
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:
case RTNLMessage::kTypeDnssl:
details = rdnss_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.c_str(), TypeToString(type()).c_str(),
details.c_str());
}
} // namespace shill