blob: ca205874b2a7cf7b403a7d892c48d2ea41b7c7dc [file] [log] [blame]
// Copyright 2014 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/wake_on_wifi.h"
#include <stdio.h>
#include <linux/if_ether.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <vector>
#include "shill/byte_string.h"
#include "shill/error.h"
#include "shill/ip_address.h"
#include "shill/nl80211_message.h"
using std::vector;
namespace shill {
namespace WakeOnWifi {
void SetMask(ByteString *mask, uint32_t pattern_len, uint32_t offset) {
// Round up number of bytes required for the mask.
int result_mask_len = (pattern_len + 8 - 1) / 8;
vector<unsigned char> result_mask(result_mask_len, 0);
// Set mask bits from offset to (pattern_len - 1)
int mask_index;
for (uint32_t curr_mask_bit = offset; curr_mask_bit < pattern_len;
++curr_mask_bit) {
mask_index = curr_mask_bit / 8;
result_mask[mask_index] |= 1 << (curr_mask_bit % 8);
}
mask->Clear();
mask->Append(ByteString(result_mask));
}
bool CreateIPAddressPatternAndMask(const IPAddress &ip_addr,
ByteString *pattern, ByteString *mask) {
if (ip_addr.family() == IPAddress::kFamilyIPv4) {
CreateIPV4PatternAndMask(ip_addr, pattern, mask);
return true;
} else if (ip_addr.family() == IPAddress::kFamilyIPv6) {
CreateIPV6PatternAndMask(ip_addr, pattern, mask);
return true;
} else {
LOG(ERROR) << "Unrecognized IP Address type.";
return false;
}
}
void CreateIPV4PatternAndMask(const IPAddress &ip_addr, ByteString *pattern,
ByteString *mask) {
struct {
struct ethhdr eth_hdr;
struct iphdr ipv4_hdr;
} __attribute__((__packed__)) pattern_bytes;
memset(&pattern_bytes, 0, sizeof(pattern_bytes));
CHECK_EQ(sizeof(pattern_bytes.ipv4_hdr.saddr), ip_addr.GetLength());
memcpy(&pattern_bytes.ipv4_hdr.saddr, ip_addr.GetConstData(),
ip_addr.GetLength());
int src_ip_offset =
reinterpret_cast<unsigned char *>(&pattern_bytes.ipv4_hdr.saddr) -
reinterpret_cast<unsigned char *>(&pattern_bytes);
int pattern_len = src_ip_offset + ip_addr.GetLength();
pattern->Clear();
pattern->Append(ByteString(
reinterpret_cast<const unsigned char *>(&pattern_bytes), pattern_len));
WakeOnWifi::SetMask(mask, pattern_len, src_ip_offset);
}
void CreateIPV6PatternAndMask(const IPAddress &ip_addr, ByteString *pattern,
ByteString *mask) {
struct {
struct ethhdr eth_hdr;
struct ip6_hdr ipv6_hdr;
} __attribute__((__packed__)) pattern_bytes;
memset(&pattern_bytes, 0, sizeof(pattern_bytes));
CHECK_EQ(sizeof(pattern_bytes.ipv6_hdr.ip6_src), ip_addr.GetLength());
memcpy(&pattern_bytes.ipv6_hdr.ip6_src, ip_addr.GetConstData(),
ip_addr.GetLength());
int src_ip_offset =
reinterpret_cast<unsigned char *>(&pattern_bytes.ipv6_hdr.ip6_src) -
reinterpret_cast<unsigned char *>(&pattern_bytes);
int pattern_len = src_ip_offset + ip_addr.GetLength();
pattern->Clear();
pattern->Append(ByteString(
reinterpret_cast<const unsigned char *>(&pattern_bytes), pattern_len));
WakeOnWifi::SetMask(mask, pattern_len, src_ip_offset);
}
bool ConfigureWiphyIndex(SetWakeOnPacketConnMessage *msg, int32_t index) {
if (!msg->attributes()->CreateU32Attribute(NL80211_ATTR_WIPHY,
"WIPHY index")) {
return false;
}
if (!msg->attributes()->SetU32AttributeValue(NL80211_ATTR_WIPHY, index)) {
return false;
}
return true;
}
bool ConfigureRemoveAllWakeOnPacketMsg(SetWakeOnPacketConnMessage *msg,
uint32_t wiphy_index, Error *error) {
if (!WakeOnWifi::ConfigureWiphyIndex(msg, wiphy_index)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Failed to configure Wiphy index.");
return false;
}
return true;
}
bool ConfigureAddWakeOnPacketMsg(SetWakeOnPacketConnMessage *msg,
const IPAddress &ip_addr, uint32_t wiphy_index,
Error *error) {
ByteString pattern;
ByteString mask;
WakeOnWifi::CreateIPAddressPatternAndMask(ip_addr, &pattern, &mask);
if (!WakeOnWifi::ConfigureWiphyIndex(msg, wiphy_index)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Failed to configure Wiphy index.");
return false;
}
if (!(*msg).attributes()->CreateNestedAttribute(NL80211_ATTR_WOWLAN_TRIGGERS,
"WoWLAN Triggers")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not create nested attribute "
"NL80211_ATTR_WOWLAN_TRIGGERS for "
"SetWakeOnPacketConnMessage.");
return false;
}
if (!(*msg).attributes()->SetNestedAttributeHasAValue(
NL80211_ATTR_WOWLAN_TRIGGERS)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set nested attribute "
"NL80211_ATTR_WOWLAN_TRIGGERS for "
"SetWakeOnPacketConnMessage.");
return false;
}
AttributeListRefPtr triggers;
if (!(*msg).attributes()->GetNestedAttributeList(NL80211_ATTR_WOWLAN_TRIGGERS,
&triggers)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not get nested attribute list "
"NL80211_ATTR_WOWLAN_TRIGGERS for "
"SetWakeOnPacketConnMessage.");
return false;
}
if (!triggers->CreateNestedAttribute(NL80211_WOWLAN_TRIG_PKT_PATTERN,
"Pattern trigger")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not create nested attribute "
"NL80211_WOWLAN_TRIG_PKT_PATTERN for "
"SetWakeOnPacketConnMessage.");
return false;
}
if (!triggers->SetNestedAttributeHasAValue(NL80211_WOWLAN_TRIG_PKT_PATTERN)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set nested attribute "
"NL80211_WOWLAN_TRIG_PKT_PATTERN for "
"SetWakeOnPacketConnMessage.");
return false;
}
AttributeListRefPtr patterns;
if (!triggers->GetNestedAttributeList(NL80211_WOWLAN_TRIG_PKT_PATTERN,
&patterns)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not get nested attribute list "
"NL80211_WOWLAN_TRIG_PKT_PATTERN for "
"SetWakeOnPacketConnMessage.");
return false;
}
// This additional level of attribute nesting on an attribute indexed by
// 1 is necessary to get the packet accepted. It is unclear what this
// attribute is (wowlan.c in the iw source code references it as "patnum")
// TODO(samueltan): identify this nested attribute
uint8_t patnum = 1;
if (!patterns->CreateNestedAttribute(patnum, "Pattern info")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not create nested attribute "
"patnum for SetWakeOnPacketConnMessage.");
return false;
}
if (!patterns->SetNestedAttributeHasAValue(patnum)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set nested attribute "
"patnum for SetWakeOnPacketConnMessage.");
return false;
}
AttributeListRefPtr pattern_info;
if (!patterns->GetNestedAttributeList(patnum, &pattern_info)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not get nested attribute list "
"patnum for SetWakeOnPacketConnMessage.");
return false;
}
// Add mask
if (!pattern_info->CreateRawAttribute(NL80211_PKTPAT_MASK, "Mask")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not add attribute NL80211_PKTPAT_MASK to "
"pattern_info.");
return false;
}
if (!pattern_info->SetRawAttributeValue(NL80211_PKTPAT_MASK, mask)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set attribute NL80211_PKTPAT_MASK in "
"pattern_info.");
return false;
}
// Add pattern
if (!pattern_info->CreateRawAttribute(NL80211_PKTPAT_PATTERN, "Pattern")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not add attribute NL80211_PKTPAT_PATTERN to "
"pattern_info.");
return false;
}
if (!pattern_info->SetRawAttributeValue(NL80211_PKTPAT_PATTERN, pattern)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set attribute NL80211_PKTPAT_PATTERN in "
"pattern_info.");
return false;
}
// Add offset
if (!pattern_info->CreateU32Attribute(NL80211_PKTPAT_OFFSET, "Offset")) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not add attribute NL80211_PKTPAT_OFFSET to "
"pattern_info.");
return false;
}
if (!pattern_info->SetU32AttributeValue(NL80211_PKTPAT_OFFSET, 0)) {
error->PopulateAndLog(error, Error::kOperationFailed,
"Could not set attribute NL80211_PKTPAT_OFFSET in "
"pattern_info.");
return false;
}
return true;
}
} // namespace WakeOnWifi
} // namespace shill