| // 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_handler.h" |
| |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/if.h> |
| #include <net/if_arp.h> |
| #include <netinet/ether.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <limits> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "shill/logging.h" |
| #include "shill/net/io_handler.h" |
| #include "shill/net/ip_address.h" |
| #include "shill/net/ndisc.h" |
| #include "shill/net/netlink_fd.h" |
| #include "shill/net/rtnl_listener.h" |
| #include "shill/net/rtnl_message.h" |
| #include "shill/net/sockets.h" |
| |
| using base::Bind; |
| using base::Unretained; |
| using std::string; |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kRTNL; |
| static std::string ObjectID(const RTNLHandler* obj) { |
| return "(rtnl_handler)"; |
| } |
| } // namespace Logging |
| |
| const uint32_t RTNLHandler::kRequestLink = 1; |
| const uint32_t RTNLHandler::kRequestAddr = 2; |
| const uint32_t RTNLHandler::kRequestRoute = 4; |
| const uint32_t RTNLHandler::kRequestRule = 8; |
| const uint32_t RTNLHandler::kRequestRdnss = 16; |
| const uint32_t RTNLHandler::kRequestNeighbor = 32; |
| const uint32_t RTNLHandler::kRequestBridgeNeighbor = 64; |
| |
| const int RTNLHandler::kErrorWindowSize = 16; |
| const uint32_t RTNLHandler::kStoredRequestWindowSize = 32; |
| |
| namespace { |
| base::LazyInstance<RTNLHandler>::DestructorAtExit g_rtnl_handler = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // Increasing buffer size to avoid overflows on IPV6 routing events. |
| constexpr int kReceiveBufferBytes = 1024 * 1024; |
| } // namespace |
| |
| RTNLHandler::RTNLHandler() |
| : sockets_(new Sockets()), |
| in_request_(false), |
| rtnl_socket_(Sockets::kInvalidFileDescriptor), |
| request_flags_(0), |
| request_sequence_(0), |
| last_dump_sequence_(0), |
| io_handler_factory_( |
| IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()) { |
| error_mask_window_.resize(kErrorWindowSize); |
| SLOG(this, 2) << "RTNLHandler created"; |
| } |
| |
| RTNLHandler::~RTNLHandler() { |
| SLOG(this, 2) << "RTNLHandler removed"; |
| Stop(); |
| } |
| |
| RTNLHandler* RTNLHandler::GetInstance() { |
| return g_rtnl_handler.Pointer(); |
| } |
| |
| void RTNLHandler::Start(uint32_t netlink_groups_mask) { |
| if (rtnl_socket_ != Sockets::kInvalidFileDescriptor) |
| return; |
| |
| rtnl_socket_ = |
| OpenNetlinkSocketFD(sockets_.get(), NETLINK_ROUTE, netlink_groups_mask); |
| if (rtnl_socket_ < 0) { |
| LOG(ERROR) << "Failed to open rtnl socket"; |
| return; |
| } |
| |
| SetReceiverBufferSize(kReceiveBufferBytes); |
| |
| rtnl_handler_.reset(io_handler_factory_->CreateIOInputHandler( |
| rtnl_socket_, Bind(&RTNLHandler::ParseRTNL, Unretained(this)), |
| Bind(&RTNLHandler::OnReadError, Unretained(this)))); |
| |
| NextRequest(last_dump_sequence_); |
| SLOG(this, 2) << "RTNLHandler started"; |
| } |
| |
| void RTNLHandler::SetReceiverBufferSize(int bytes) { |
| CHECK(rtnl_socket_ != Sockets::kInvalidFileDescriptor) |
| << "Invalid socket descriptor: " << rtnl_socket_; |
| |
| if (sockets_->SetReceiveBuffer(rtnl_socket_, bytes) < 0) |
| PLOG(ERROR) << "Failed to increase receive buffer size"; |
| } |
| |
| void RTNLHandler::Stop() { |
| rtnl_handler_.reset(); |
| // Close the socket if it is currently open. |
| if (rtnl_socket_ != Sockets::kInvalidFileDescriptor) { |
| sockets_->Close(rtnl_socket_); |
| rtnl_socket_ = Sockets::kInvalidFileDescriptor; |
| } |
| in_request_ = false; |
| request_flags_ = 0; |
| SLOG(this, 2) << "RTNLHandler stopped"; |
| } |
| |
| void RTNLHandler::AddListener(RTNLListener* to_add) { |
| listeners_.AddObserver(to_add); |
| SLOG(this, 2) << "RTNLHandler added listener"; |
| } |
| |
| void RTNLHandler::RemoveListener(RTNLListener* to_remove) { |
| listeners_.RemoveObserver(to_remove); |
| SLOG(this, 2) << "RTNLHandler removed listener"; |
| } |
| |
| void RTNLHandler::SetInterfaceFlags(int interface_index, |
| unsigned int flags, |
| unsigned int change) { |
| if (rtnl_socket_ == Sockets::kInvalidFileDescriptor) { |
| LOG(ERROR) << __func__ |
| << " called while not started. " |
| "Assuming we are in unit tests."; |
| return; |
| } |
| |
| auto msg = std::make_unique<RTNLMessage>( |
| RTNLMessage::kTypeLink, RTNLMessage::kModeAdd, NLM_F_REQUEST, |
| 0, // sequence to be filled in by RTNLHandler::SendMessage(). |
| 0, // pid. |
| interface_index, IPAddress::kFamilyUnknown); |
| |
| msg->set_link_status(RTNLMessage::LinkStatus(ARPHRD_VOID, flags, change)); |
| |
| ErrorMask error_mask; |
| if ((flags & IFF_UP) == 0) { |
| error_mask.insert(ENODEV); |
| } |
| |
| SendMessageWithErrorMask(std::move(msg), error_mask, nullptr); |
| } |
| |
| void RTNLHandler::SetInterfaceMTU(int interface_index, unsigned int mtu) { |
| auto msg = std::make_unique<RTNLMessage>( |
| RTNLMessage::kTypeLink, RTNLMessage::kModeAdd, NLM_F_REQUEST, |
| 0, // sequence to be filled in by RTNLHandler::SendMessage(). |
| 0, // pid. |
| interface_index, IPAddress::kFamilyUnknown); |
| |
| msg->SetAttribute(IFLA_MTU, ByteString(reinterpret_cast<unsigned char*>(&mtu), |
| sizeof(mtu))); |
| |
| CHECK(SendMessage(std::move(msg), nullptr)); |
| } |
| |
| void RTNLHandler::SetInterfaceMac(int interface_index, |
| const ByteString& mac_address) { |
| SetInterfaceMac(interface_index, mac_address, ResponseCallback()); |
| } |
| |
| void RTNLHandler::SetInterfaceMac(int interface_index, |
| const ByteString& mac_address, |
| ResponseCallback response_callback) { |
| auto msg = std::make_unique<RTNLMessage>( |
| RTNLMessage::kTypeLink, RTNLMessage::kModeAdd, NLM_F_REQUEST | NLM_F_ACK, |
| 0, // sequence to be filled in by RTNLHandler::SendMessage(). |
| 0, // pid. |
| interface_index, IPAddress::kFamilyUnknown); |
| |
| msg->SetAttribute(IFLA_ADDRESS, mac_address); |
| |
| uint32_t seq; |
| CHECK(SendMessage(std::move(msg), &seq)); |
| if (!response_callback.is_null()) { |
| response_callbacks_[seq] = std::move(response_callback); |
| } |
| } |
| |
| void RTNLHandler::RequestDump(uint32_t request_flags) { |
| if (rtnl_socket_ == Sockets::kInvalidFileDescriptor) { |
| LOG(ERROR) << __func__ |
| << " called while not started. " |
| "Assuming we are in unit tests."; |
| return; |
| } |
| |
| request_flags_ |= request_flags; |
| |
| SLOG(this, 2) << base::StringPrintf("RTNLHandler got request to dump 0x%x", |
| request_flags); |
| |
| if (!in_request_) { |
| NextRequest(last_dump_sequence_); |
| } |
| } |
| |
| void RTNLHandler::DispatchEvent(int type, const RTNLMessage& msg) { |
| for (RTNLListener& listener : listeners_) { |
| listener.NotifyEvent(type, msg); |
| } |
| } |
| |
| void RTNLHandler::NextRequest(uint32_t seq) { |
| uint32_t flag = 0; |
| RTNLMessage::Type type; |
| |
| SLOG(this, 2) << base::StringPrintf("RTNLHandler nextrequest %d %d 0x%x", seq, |
| last_dump_sequence_, request_flags_); |
| |
| if (seq != last_dump_sequence_) |
| return; |
| |
| IPAddress::Family family = IPAddress::kFamilyUnknown; |
| if ((request_flags_ & kRequestAddr) != 0) { |
| type = RTNLMessage::kTypeAddress; |
| flag = kRequestAddr; |
| } else if ((request_flags_ & kRequestRoute) != 0) { |
| type = RTNLMessage::kTypeRoute; |
| flag = kRequestRoute; |
| } else if ((request_flags_ & kRequestRule) != 0) { |
| type = RTNLMessage::kTypeRule; |
| flag = kRequestRule; |
| } else if ((request_flags_ & kRequestLink) != 0) { |
| type = RTNLMessage::kTypeLink; |
| flag = kRequestLink; |
| } else if ((request_flags_ & kRequestNeighbor) != 0) { |
| type = RTNLMessage::kTypeNeighbor; |
| flag = kRequestNeighbor; |
| } else if ((request_flags_ & kRequestBridgeNeighbor) != 0) { |
| type = RTNLMessage::kTypeNeighbor; |
| flag = kRequestBridgeNeighbor; |
| family = AF_BRIDGE; |
| } else { |
| SLOG(this, 2) << "Done with requests"; |
| in_request_ = false; |
| return; |
| } |
| |
| auto msg = std::make_unique<RTNLMessage>(type, RTNLMessage::kModeGet, 0, 0, 0, |
| 0, family); |
| uint32_t msg_seq; |
| CHECK(SendMessage(std::move(msg), &msg_seq)); |
| |
| last_dump_sequence_ = msg_seq; |
| request_flags_ &= ~flag; |
| in_request_ = true; |
| } |
| |
| void RTNLHandler::ParseRTNL(InputData* data) { |
| const unsigned char* buf = data->buf; |
| const unsigned char* end = buf + data->len; |
| |
| while (buf < end) { |
| const struct nlmsghdr* hdr = reinterpret_cast<const struct nlmsghdr*>(buf); |
| if (!NLMSG_OK(hdr, static_cast<unsigned int>(end - buf))) |
| break; |
| |
| SLOG(this, 5) << __func__ << ": received payload (" << end - buf << ")"; |
| |
| RTNLMessage msg; |
| ByteString payload(reinterpret_cast<const unsigned char*>(hdr), |
| hdr->nlmsg_len); |
| SLOG(this, 5) << "RTNL received payload length " << payload.GetLength() |
| << ": \"" << payload.HexEncode() << "\""; |
| |
| // Swapping out of |stored_requests_| here ensures that the RTNLMessage will |
| // be destructed regardless of the control flow below. |
| std::unique_ptr<RTNLMessage> request_msg = PopStoredRequest(hdr->nlmsg_seq); |
| |
| if (!msg.Decode(payload)) { |
| SLOG(this, 5) << __func__ << ": rtnl packet type " << hdr->nlmsg_type |
| << " length " << hdr->nlmsg_len << " sequence " |
| << hdr->nlmsg_seq; |
| |
| switch (hdr->nlmsg_type) { |
| case NLMSG_NOOP: |
| case NLMSG_OVERRUN: |
| break; |
| case NLMSG_DONE: |
| GetAndClearErrorMask(hdr->nlmsg_seq); // Clear any queued error mask. |
| NextRequest(hdr->nlmsg_seq); |
| break; |
| case NLMSG_ERROR: { |
| if (hdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { |
| SLOG(this, 5) << "invalid error message header: length " |
| << hdr->nlmsg_len; |
| break; |
| } |
| |
| int error_number = |
| reinterpret_cast<nlmsgerr*>(NLMSG_DATA(hdr))->error; |
| std::string request_str; |
| if (request_msg) |
| request_str = " (" + request_msg->ToString() + ")"; |
| |
| if (error_number == 0) { |
| SLOG(this, 3) << base::StringPrintf( |
| "sequence %d%s received success", hdr->nlmsg_seq, |
| request_str.c_str()); |
| } else if ((error_number > 0 || |
| error_number == std::numeric_limits<int>::min())) { |
| LOG(ERROR) << base::StringPrintf( |
| "sequence %d%s received invalid error %d", hdr->nlmsg_seq, |
| request_str.c_str(), error_number); |
| } else { |
| error_number = -error_number; |
| std::string error_msg = base::StringPrintf( |
| "sequence %d%s received error %d (%s)", hdr->nlmsg_seq, |
| request_str.c_str(), error_number, strerror(error_number)); |
| if (!base::Contains(GetAndClearErrorMask(hdr->nlmsg_seq), |
| error_number)) { |
| LOG(ERROR) << error_msg; |
| } else { |
| SLOG(this, 3) << error_msg; |
| } |
| } |
| |
| auto response_callback_iter = |
| response_callbacks_.find(hdr->nlmsg_seq); |
| if (response_callback_iter != response_callbacks_.end()) { |
| std::move(response_callback_iter->second).Run(error_number); |
| response_callbacks_.erase(response_callback_iter); |
| } |
| |
| break; |
| } |
| default: |
| LOG(ERROR) << "Unknown NL message type: " << hdr->nlmsg_type; |
| } |
| } else { |
| switch (msg.type()) { |
| case RTNLMessage::kTypeLink: |
| DispatchEvent(kRequestLink, msg); |
| break; |
| case RTNLMessage::kTypeAddress: |
| DispatchEvent(kRequestAddr, msg); |
| break; |
| case RTNLMessage::kTypeRoute: |
| DispatchEvent(kRequestRoute, msg); |
| break; |
| case RTNLMessage::kTypeRule: |
| DispatchEvent(kRequestRule, msg); |
| break; |
| case RTNLMessage::kTypeRdnss: |
| DispatchEvent(kRequestRdnss, msg); |
| break; |
| case RTNLMessage::kTypeNeighbor: |
| DispatchEvent(kRequestNeighbor, msg); |
| break; |
| case RTNLMessage::kTypeDnssl: |
| // DNSSL support is not implemented. Just ignore it. |
| break; |
| default: |
| LOG(ERROR) << "Unknown RTNL message type: " << msg.type(); |
| } |
| } |
| buf += NLMSG_ALIGN(hdr->nlmsg_len); |
| } |
| } |
| |
| bool RTNLHandler::AddressRequest(int interface_index, |
| RTNLMessage::Mode mode, |
| int flags, |
| const IPAddress& local, |
| const IPAddress& broadcast, |
| const IPAddress& peer) { |
| CHECK(local.family() == broadcast.family()); |
| CHECK(local.family() == peer.family()); |
| |
| auto msg = std::make_unique<RTNLMessage>(RTNLMessage::kTypeAddress, mode, |
| NLM_F_REQUEST | flags, 0, 0, |
| interface_index, local.family()); |
| |
| msg->set_address_status(RTNLMessage::AddressStatus(local.prefix(), 0, 0)); |
| |
| msg->SetAttribute(IFA_LOCAL, local.address()); |
| if (!broadcast.IsDefault()) { |
| msg->SetAttribute(IFA_BROADCAST, broadcast.address()); |
| } |
| if (!peer.IsDefault()) { |
| msg->SetAttribute(IFA_ADDRESS, peer.address()); |
| } |
| |
| return SendMessage(std::move(msg), nullptr); |
| } |
| |
| bool RTNLHandler::AddInterfaceAddress(int interface_index, |
| const IPAddress& local, |
| const IPAddress& broadcast, |
| const IPAddress& peer) { |
| return AddressRequest(interface_index, RTNLMessage::kModeAdd, |
| NLM_F_CREATE | NLM_F_EXCL | NLM_F_ECHO, local, |
| broadcast, peer); |
| } |
| |
| bool RTNLHandler::RemoveInterfaceAddress(int interface_index, |
| const IPAddress& local) { |
| return AddressRequest(interface_index, RTNLMessage::kModeDelete, NLM_F_ECHO, |
| local, IPAddress(local.family()), |
| IPAddress(local.family())); |
| } |
| |
| bool RTNLHandler::RemoveInterface(int interface_index) { |
| auto msg = std::make_unique<RTNLMessage>( |
| RTNLMessage::kTypeLink, RTNLMessage::kModeDelete, NLM_F_REQUEST, 0, 0, |
| interface_index, IPAddress::kFamilyUnknown); |
| return SendMessage(std::move(msg), nullptr); |
| } |
| |
| int RTNLHandler::GetInterfaceIndex(const string& interface_name) { |
| if (interface_name.empty()) { |
| LOG(ERROR) << "Empty interface name -- unable to obtain index."; |
| return -1; |
| } |
| struct ifreq ifr; |
| if (interface_name.size() >= sizeof(ifr.ifr_name)) { |
| LOG(ERROR) << "Interface name too long: " << interface_name.size() |
| << " >= " << sizeof(ifr.ifr_name); |
| return -1; |
| } |
| int socket = sockets_->Socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); |
| if (socket < 0) { |
| PLOG(ERROR) << "Unable to open INET socket"; |
| return -1; |
| } |
| ScopedSocketCloser socket_closer(sockets_.get(), socket); |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name)); |
| if (sockets_->Ioctl(socket, SIOCGIFINDEX, &ifr) < 0) { |
| PLOG(ERROR) << "SIOCGIFINDEX error for " << interface_name; |
| return -1; |
| } |
| return ifr.ifr_ifindex; |
| } |
| |
| bool RTNLHandler::SendMessage(std::unique_ptr<RTNLMessage> message, |
| uint32_t* msg_seq) { |
| ErrorMask error_mask; |
| if (message->mode() == RTNLMessage::kModeAdd) { |
| error_mask = {EEXIST}; |
| } else if (message->mode() == RTNLMessage::kModeDelete) { |
| error_mask = {ESRCH, ENODEV}; |
| if (message->type() == RTNLMessage::kTypeAddress) { |
| error_mask.insert(EADDRNOTAVAIL); |
| } |
| } |
| return SendMessageWithErrorMask(std::move(message), error_mask, msg_seq); |
| } |
| |
| bool RTNLHandler::SendMessageWithErrorMask(std::unique_ptr<RTNLMessage> message, |
| const ErrorMask& error_mask, |
| uint32_t* msg_seq) { |
| SLOG(this, 5) << __func__ << " sequence " << request_sequence_ |
| << " message type " << message->type() << " mode " |
| << message->mode() << " with error mask size " |
| << error_mask.size(); |
| |
| SetErrorMask(request_sequence_, error_mask); |
| message->set_seq(request_sequence_); |
| ByteString msgdata = message->Encode(); |
| |
| if (msgdata.GetLength() == 0) { |
| return false; |
| } |
| |
| SLOG(this, 5) << "RTNL sending payload with request sequence " |
| << request_sequence_ << ", length " << msgdata.GetLength() |
| << ": \"" << msgdata.HexEncode() << "\""; |
| |
| request_sequence_++; |
| |
| if (sockets_->Send(rtnl_socket_, msgdata.GetConstData(), msgdata.GetLength(), |
| 0) < 0) { |
| PLOG(ERROR) << "RTNL send failed"; |
| return false; |
| } |
| |
| if (msg_seq) |
| *msg_seq = message->seq(); |
| StoreRequest(std::move(message)); |
| return true; |
| } |
| |
| void RTNLHandler::OnReadError(const string& error_msg) { |
| LOG(FATAL) << "RTNL Socket read returns error: " << error_msg; |
| } |
| |
| bool RTNLHandler::IsSequenceInErrorMaskWindow(uint32_t sequence) { |
| return (request_sequence_ - sequence) < kErrorWindowSize; |
| } |
| |
| void RTNLHandler::SetErrorMask(uint32_t sequence, const ErrorMask& error_mask) { |
| if (IsSequenceInErrorMaskWindow(sequence)) { |
| error_mask_window_[sequence % kErrorWindowSize] = error_mask; |
| } |
| } |
| |
| RTNLHandler::ErrorMask RTNLHandler::GetAndClearErrorMask(uint32_t sequence) { |
| ErrorMask error_mask; |
| if (IsSequenceInErrorMaskWindow(sequence)) { |
| error_mask.swap(error_mask_window_[sequence % kErrorWindowSize]); |
| } |
| return error_mask; |
| } |
| |
| void RTNLHandler::StoreRequest(std::unique_ptr<RTNLMessage> request) { |
| auto seq = request->seq(); |
| |
| if (stored_requests_.empty()) { |
| oldest_request_sequence_ = seq; |
| } |
| |
| // Note that this will update an existing stored request of the same sequence |
| // number, removing the original RTNLMessage. |
| stored_requests_[seq] = std::move(request); |
| while (CalculateStoredRequestWindowSize() > kStoredRequestWindowSize) { |
| auto old_request = PopStoredRequest(oldest_request_sequence_); |
| CHECK(old_request) << "PopStoredRequest returned nullptr but " |
| << "the calculated window size is greater than 0. " |
| << "This is a bug in RTNLHandler."; |
| SLOG(this, 1) << "Removing stored RTNLMessage of sequence " |
| << old_request->seq() << " (" << old_request->ToString() |
| << ") without receiving a response for this sequence"; |
| } |
| } |
| |
| std::unique_ptr<RTNLMessage> RTNLHandler::PopStoredRequest(uint32_t seq) { |
| auto seq_request = stored_requests_.find(seq); |
| if (seq_request == stored_requests_.end()) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<RTNLMessage> res; |
| res.swap(seq_request->second); |
| if (seq == oldest_request_sequence_) { |
| auto next_oldest_seq_request = std::next(seq_request); |
| // Seq overflow could have occurred between the oldest and second oldest |
| // stored requests. |
| if (next_oldest_seq_request == stored_requests_.end()) { |
| next_oldest_seq_request = stored_requests_.begin(); |
| } |
| // Note that this condition means |oldest_request_sequence_| will not be |
| // changed when the last stored request is popped. This does not pose any |
| // correctness issues. |
| if (next_oldest_seq_request != seq_request) { |
| oldest_request_sequence_ = next_oldest_seq_request->first; |
| } |
| } |
| stored_requests_.erase(seq_request); |
| return res; |
| } |
| |
| uint32_t RTNLHandler::CalculateStoredRequestWindowSize() { |
| if (stored_requests_.size() <= 1) { |
| return stored_requests_.size(); |
| } |
| |
| auto seq_request = stored_requests_.begin(); |
| if (seq_request->first != oldest_request_sequence_) { |
| // If we overflowed, the sequence of the newest request is the |
| // greatest sequence less than |oldest_request_sequence_|. |
| seq_request = std::prev(stored_requests_.find(oldest_request_sequence_)); |
| } else { |
| seq_request = std::prev(stored_requests_.end()); |
| } |
| return seq_request->first - oldest_request_sequence_ + 1; |
| } |
| |
| } // namespace shill |