//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "shill/net/netlink_socket.h"
#include <string>
#include <linux/if_packet.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <base/logging.h>
#include "shill/net/netlink_message.h"
#include "shill/net/sockets.h"
// This is from a version of linux/socket.h that we don't have.
#define SOL_NETLINK 270
namespace shill {
// Keep this large enough to avoid overflows on IPv6 SNM routing update spikes
const int NetlinkSocket::kReceiveBufferSize = 512 * 1024;
NetlinkSocket::NetlinkSocket() : sequence_number_(0), file_descriptor_(-1) {}
NetlinkSocket::~NetlinkSocket() {
if (sockets_ && (file_descriptor_ >= 0)) {
sockets_->Close(file_descriptor_);
}
}
bool NetlinkSocket::Init() {
// Allows for a test to set |sockets_| before calling |Init|.
if (sockets_) {
LOG(INFO) << "|sockets_| already has a value -- this must be a test.";
} else {
sockets_.reset(new Sockets);
}
// The following is stolen directly from RTNLHandler.
// TODO(wdg): refactor this and RTNLHandler together to use common code.
// crbug.com/221940
file_descriptor_ = sockets_->Socket(PF_NETLINK, SOCK_DGRAM, NETLINK_GENERIC);
if (file_descriptor_ < 0) {
LOG(ERROR) << "Failed to open netlink socket";
return false;
}
if (sockets_->SetReceiveBuffer(file_descriptor_, kReceiveBufferSize)) {
LOG(ERROR) << "Failed to increase receive buffer size";
}
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
if (sockets_->Bind(file_descriptor_,
reinterpret_cast<struct sockaddr*>(&addr),
sizeof(addr)) < 0) {
sockets_->Close(file_descriptor_);
file_descriptor_ = -1;
LOG(ERROR) << "Netlink socket bind failed";
return false;
}
VLOG(2) << "Netlink socket started";
return true;
}
bool NetlinkSocket::RecvMessage(ByteString* message) {
if (!message) {
LOG(ERROR) << "Null |message|";
return false;
}
// Determine the amount of data currently waiting.
const size_t kDummyReadByteCount = 1;
ByteString dummy_read(kDummyReadByteCount);
ssize_t result;
result = sockets_->RecvFrom(
file_descriptor_,
dummy_read.GetData(),
dummy_read.GetLength(),
MSG_TRUNC | MSG_PEEK,
nullptr,
nullptr);
if (result < 0) {
PLOG(ERROR) << "Socket recvfrom failed.";
return false;
}
// Read the data that was waiting when we did our previous read.
message->Resize(result);
result = sockets_->RecvFrom(
file_descriptor_,
message->GetData(),
message->GetLength(),
0,
nullptr,
nullptr);
if (result < 0) {
PLOG(ERROR) << "Second socket recvfrom failed.";
return false;
}
return true;
}
bool NetlinkSocket::SendMessage(const ByteString& out_msg) {
ssize_t result = sockets_->Send(file_descriptor(), out_msg.GetConstData(),
out_msg.GetLength(), 0);
if (!result) {
PLOG(ERROR) << "Send failed.";
return false;
}
if (result != static_cast<ssize_t>(out_msg.GetLength())) {
LOG(ERROR) << "Only sent " << result << " bytes out of "
<< out_msg.GetLength() << ".";
return false;
}
return true;
}
bool NetlinkSocket::SubscribeToEvents(uint32_t group_id) {
int err = setsockopt(file_descriptor_, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&group_id, sizeof(group_id));
if (err < 0) {
PLOG(ERROR) << "setsockopt didn't work.";
return false;
}
return true;
}
uint32_t NetlinkSocket::GetSequenceNumber() {
if (++sequence_number_ == NetlinkMessage::kBroadcastSequenceNumber)
++sequence_number_;
return sequence_number_;
}
} // namespace shill.