shill: Adds docs on how to build, send, and receive netlink messages.
BUG=None
TEST=None
Change-Id: I50d85c29b2d344a2b9d4334d31dd8966042bdec9
Reviewed-on: https://chromium-review.googlesource.com/176167
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/NETLINK_MESSAGES.txt b/NETLINK_MESSAGES.txt
new file mode 100644
index 0000000..c4d8723
--- /dev/null
+++ b/NETLINK_MESSAGES.txt
@@ -0,0 +1,308 @@
+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 =
+ dynamic_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);
+