Netlink message handling in Shill. 1.0 INTRODUCTION. Shill uses netlink sockets (described in RFC 3549) to communicate with software in kernel space. Messages that are passed across netlink sockets take a specific format that includes a netlink header, a sub-domain-specific header, and attributes. Shill defines a NetlinkManager class for dealing with the netlink sockets and NetlinkMessage (and its children such as ControlNetlinkMessage and Nl80211Message) and NetlinkAttribute (and its children) classes for dealing with the messages passed across the netlink sockets. 2.0 SENDING A MESSAGE. This section describes how to send a netlink message in Shill. The steps, described below, are: o Create a message. o Make Response and Error Handlers. o Send the Message. 2.1 Create the message. Start by declaring a message object. This will be a message-specific child class of the NetlinkMessage type. For example: TriggerScanMessage trigger_scan; 2.1.1 Add attributes to the message. You'll want to set values for all the message's attributes. The message object should have all of its legal attributes pre-instantiated so all you should have to do is set their values (if an attribute is optional, don't set the value -- only the attibutes that have been explicitly set will be sent in the message). A message's attributes are accessed through the message's |attributes| or |const_attributes| methods. 2.1.1.1 Regular attributes. Netlink attributes are typed (e.g., String, U32, etc.). In order to set the value of an attribute you use the SetXxxAttributeValue method (where Xxx is the type of the attribute. For example, you may want to set the value of the NL80211_ATTR_IFINDEX attribute: if (trigger_scan.attributes()->SetU32AttributeValue( NL80211_ATTR_IFINDEX, wifi_interface_index_)) { // ... If the message hasn't pre-instantiated the attribute you want to use, the 'SetXxxAttributeValue' call will return false. This can be for one of three reasons: a) a message of this type may not be expecting this kind of attribute, b) the data type of the attribute may not agree with the setter you used, or c) the definition of the specific message class is incomplete (that is, the attribute hasn't been, but should be, added to the message type). You can check the kernel code to determine the attributes each message is expecting and the type of those attributes. a) Find the command (NL80211_CMD_TRIGGER_SCAN, in the case of TriggerScanMessage) in the |nl80211_ops| array in the kernel sources (.../src/third_party/kernel/files/net/wireless/nl80211.c in the ChromeOS sources). b) Find the name of the command handler (in the |.doit| structure member) in that structure. Find that handler. In the case of NL80211_CMD_TRIGGER_SCAN, the handler is |nl80211_trigtger_scan|. c) Look for handling of the attribute in question. It will be an offset into the |info->attrs[]| array. You can also see the data type expected for the attribute. If the kernel expects the attribute, modify the message's constructor (probably in one of the message handling source files, like nl80211_message.cc) to create the attribute: attributes()->CreateAttribute( NL80211_ATTR_IFINDEX, Bind(&NetlinkAttribute::NewNl80211AttributeFromId)); 2.1.1.2 Nested attributes. So, this is all fun and games until someone needs a nested attribute. A nested attribute contains a number of other attributes (like a structure) or a list of identically-typed attributes (like an array). To set a nested attribute, declare an AttributeListRefPtr, and fill it with the attribute in question: AttributeListRefPtr nested; if (!trigger_scan.attributes()->GetNestedAttributeList( NL80211_ATTR_SCAN_FREQUENCIES, &nested) || !nested) { LOG(FATAL) << "Couldn't get NL80211_ATTR_SCAN_FREQUENCIES."; } Set the 'has a value' trait of the nested attribute: trigger_scan.attributes()->SetNestedAttributeHasAValue( NL80211_ATTR_SCAN_FREQUENCIES); Now, create and set the nested attributes within AttributeList. You can create an array: int i = 0; for (const auto freq : scan_frequencies) { nested->CreateU32Attribute(i, StringPrintf("Frequency-%d", i).c_str()); nested->SetU32AttributeValue(i, freq); ++i; } Or you can just create and add ordinary named attributes: nested->CreateStringAttribute(type, kSsidString); nested->SetStringAttributeValue(type, "Foo"); You can even nest nested attributes inside nested attributes: nested->CreateNestedAttribute(type, kRatesString); AttributeListRefPtr nested_nested; if (!nested->GetNestedAttributeList(type, &nested_nested) || !nested_nested) { LOG(ERROR) << "Couldn't get attribute " << attribute_name << " which we just created."; return; } for (size_t i = 0; i < payload_bytes; ++i) { string rate_name = StringPrintf("Rate-%zu", i); nested_nested->CreateU8Attribute(i, rate_name.c_str()); nested_nested->SetU8AttributeValue(i, payload[i]); } nested->SetNestedAttributeHasAValue(type); 2.2 Make Response and Error Handlers. Make some sort of handler for the response message. class Foo { // ... private: // More on this, later. void OnTriggerScanResponse(const Nl80211Message& response) { // Do whatever you want with the response. return; } void OnTriggerScanErrorResponse( NetlinkManager::AuxilliaryMessageType type, const NetlinkMessage* netlink_message) { switch (type) { case NetlinkManager::kErrorFromKernel: { if (!netlink_message) { LOG(ERROR) << __func__ << ": Message failed: NetlinkManager Error."; break; } if (netlink_message->message_type() != ErrorAckMessage::GetMessageType()) { LOG(ERROR) << __func__ << ": Message failed: Not an error."; break; } const ErrorAckMessage* error_ack_message = static_cast<const ErrorAckMessage*>(netlink_message); if (error_ack_message->error()) { LOG(ERROR) << __func__ << ": Message failed: " << error_ack_message->ToString(); } else { SLOG(WiFi, 6) << __func__ << ": Message ACKed"; } } break; case NetlinkManager::kUnexpectedResponseType: LOG(ERROR) << "Message not handled by regular message handler:"; if (netlink_message) { netlink_message->Print(0, 0); } found_error_ = true; on_scan_failed_.Run(); break; case NetlinkManager::kTimeoutWaitingForResponse: // Handle this one. break; default: LOG(ERROR) << "Unexpected auxiliary message type: " << type; found_error_ = true; on_scan_failed_.Run(); break; } } } 2.3 Send the Message. Send the message with the handlers for the various cases. NetlinkManager::GetInstance()->SendNl80211Message( &trigger_scan, Bind(&Foo::OnTriggerScanResponse, weak_ptr_factory_.GetWeakPtr()), Bind(&Foo::OnTriggerScanErrorResponse, weak_ptr_factory_.GetWeakPtr())); 3.0 RECEIVING A NETLINK MESSAGE. 3.1 Build a Message Handler (to which I've alluded, above). The message handler should take a single parameter of the type of message you want to handle. For example: void NetlinkManager::OnNewFamilyMessage(const ControlNetlinkMessage& message) { You'll probably want to look for some attributes: uint16_t family_id; if (!message.const_attributes()->GetU16AttributeValue(CTRL_ATTR_FAMILY_ID, &family_id)) { LOG(ERROR) << __func__ << ": Couldn't get family_id attribute"; return; } string family_name; if (!message.const_attributes()->GetStringAttributeValue( CTRL_ATTR_FAMILY_NAME, &family_name)) { LOG(ERROR) << __func__ << ": Couldn't get family_name attribute"; return; } And, some of these attributes may be nested. In this example, we've got an array of structures that looks sort-of like (this isn't the way the data is stored, it just _logically_ looks like this): struct { u32 ignored; // CTRL_ATTR_MCAST_GRP_UNSPEC; string group_name; // CTRL_ATTR_MCAST_GRP_NAME; u32 group_id; // CTRL_ATTR_MCAST_GRP_ID; } multicast_groups[]; But the actual code for reading this array is as follows: AttributeListConstRefPtr multicast_groups; if (message.const_attributes()->ConstGetNestedAttributeList( CTRL_ATTR_MCAST_GROUPS, &multicast_groups)) { AttributeListConstRefPtr current_group; for (int i = 1; multicast_groups->ConstGetNestedAttributeList(i, ¤t_group); ++i) { string group_name; if (!current_group->GetStringAttributeValue(CTRL_ATTR_MCAST_GRP_NAME, &group_name)) { LOG(WARNING) << "Expected CTRL_ATTR_MCAST_GRP_NAME, found none"; continue; } uint32_t group_id; if (!current_group->GetU32AttributeValue(CTRL_ATTR_MCAST_GRP_ID, &group_id)) { LOG(WARNING) << "Expected CTRL_ATTR_MCAST_GRP_ID, found none"; continue; } SLOG(WiFi, 3) << " Adding group '" << group_name << "' = " << group_id; message_types_[family_name].groups[group_name] = group_id; } } 3.2 Install the Message Handler. The message you're handling can either be a broadcast message or a response (I've not seen a case where the kernel sends a message directly to us but, I'd imagine it's possible. This case could be handled as a broadcast message followed by a hasty name change fot that method). 3.2.1 Install a Broadcast Message Handler. Broadcast handlers are installed to the NetlinkManager as follows: NetlinkManager::GetInstance()->AddBroadcastHandler(handler); Where 'handler' is the handler described, above. Broadcast messaes just handle generic NetlinkMessages rather than a specific kind. 3.2.2 Install a Unicast (i.e., a Response) Message Handler. Otherwise, the handler is installed as the response handler for a message. For example: ControlNetlinkMessage message; // Build the message. NetlinkManager::GetInstance()->SendControlMessage(&message, &message_handler, &error_handler);