Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 1 | // Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include <time.h> |
| 6 | |
| 7 | #include <unistd.h> |
| 8 | #include <string.h> |
| 9 | #include <sys/socket.h> |
| 10 | #include <arpa/inet.h> |
| 11 | #include <netinet/ether.h> |
| 12 | #include <net/if.h> |
| 13 | #include <net/if_arp.h> |
| 14 | #include <linux/netlink.h> |
| 15 | #include <linux/rtnetlink.h> |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 16 | #include <fcntl.h> |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 17 | #include <string> |
| 18 | |
Chris Masone | 487b8bf | 2011-05-13 16:27:57 -0700 | [diff] [blame] | 19 | #include <base/callback_old.h> |
Chris Masone | 0e1d104 | 2011-05-09 18:07:03 -0700 | [diff] [blame] | 20 | #include <base/hash_tables.h> |
Chris Masone | 487b8bf | 2011-05-13 16:27:57 -0700 | [diff] [blame] | 21 | #include <base/logging.h> |
| 22 | #include <base/memory/scoped_ptr.h> |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 23 | #include <base/stringprintf.h> |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 24 | |
| 25 | #include "shill/control_interface.h" |
Chris Masone | 9be4a9d | 2011-05-16 15:44:09 -0700 | [diff] [blame] | 26 | #include "shill/device.h" |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 27 | #include "shill/device_info.h" |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 28 | #include "shill/ethernet.h" |
| 29 | #include "shill/manager.h" |
Chris Masone | 487b8bf | 2011-05-13 16:27:57 -0700 | [diff] [blame] | 30 | #include "shill/service.h" |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 31 | #include "shill/wifi.h" |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 32 | |
| 33 | using std::string; |
| 34 | |
| 35 | namespace shill { |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 36 | |
| 37 | // static |
| 38 | const char *DeviceInfo::kInterfaceUevent= "/sys/class/net/%s/uevent"; |
| 39 | // static |
| 40 | const char *DeviceInfo::kInterfaceDriver= "/sys/class/net/%s/device/driver"; |
| 41 | // static |
| 42 | const char *DeviceInfo::kModemDrivers[] = { |
| 43 | "gobi", |
| 44 | "QCUSBNet2k", |
| 45 | "GobiNet", |
| 46 | NULL |
| 47 | }; |
| 48 | |
| 49 | |
| 50 | DeviceInfo::DeviceInfo(ControlInterface *control_interface, |
| 51 | EventDispatcher *dispatcher, |
| 52 | Manager *manager) |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 53 | : running_(false), |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 54 | control_interface_(control_interface), |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 55 | dispatcher_(dispatcher), |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 56 | manager_(manager), |
Chris Masone | 0e1d104 | 2011-05-09 18:07:03 -0700 | [diff] [blame] | 57 | rtnl_callback_(NewCallback(this, &DeviceInfo::ParseRTNL)), |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 58 | rtnl_socket_(-1), |
| 59 | request_flags_(0), |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 60 | request_sequence_(0) {} |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 61 | |
| 62 | DeviceInfo::~DeviceInfo() { |
| 63 | Stop(); |
| 64 | } |
| 65 | |
| 66 | void DeviceInfo::Start() |
| 67 | { |
| 68 | struct sockaddr_nl addr; |
| 69 | |
| 70 | if (running_) |
| 71 | return; |
| 72 | |
| 73 | rtnl_socket_ = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); |
| 74 | if (rtnl_socket_ < 0) { |
| 75 | LOG(ERROR) << "Failed to open rtnl socket"; |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | memset(&addr, 0, sizeof(addr)); |
| 80 | addr.nl_family = AF_NETLINK; |
| 81 | addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE; |
| 82 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 83 | if (bind(rtnl_socket_, reinterpret_cast<struct sockaddr *>(&addr), |
| 84 | sizeof(addr)) < 0) { |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 85 | close(rtnl_socket_); |
| 86 | rtnl_socket_ = -1; |
| 87 | LOG(ERROR) << "RTNL socket bind failed"; |
| 88 | return; |
| 89 | } |
| 90 | |
| 91 | rtnl_handler_.reset(dispatcher_->CreateInputHandler(rtnl_socket_, |
Chris Masone | 0e1d104 | 2011-05-09 18:07:03 -0700 | [diff] [blame] | 92 | rtnl_callback_.get())); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 93 | running_ = true; |
| 94 | |
| 95 | request_flags_ = REQUEST_LINK | REQUEST_ADDR | REQUEST_ROUTE; |
| 96 | NextRequest(request_sequence_); |
| 97 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 98 | VLOG(2) << "DeviceInfo started"; |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | void DeviceInfo::Stop() |
| 102 | { |
| 103 | if (!running_) |
| 104 | return; |
| 105 | |
| 106 | rtnl_handler_.reset(NULL); |
| 107 | close(rtnl_socket_); |
| 108 | running_ = false; |
| 109 | } |
| 110 | |
| 111 | void DeviceInfo::NextRequest(uint32_t seq) { |
| 112 | struct rtnl_request { |
| 113 | struct nlmsghdr hdr; |
| 114 | struct rtgenmsg msg; |
| 115 | } req; |
| 116 | struct sockaddr_nl addr; |
| 117 | int flag = 0; |
| 118 | |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 119 | if (seq != request_sequence_) |
| 120 | return; |
| 121 | |
| 122 | request_sequence_++; |
| 123 | memset(&req, 0, sizeof(req)); |
| 124 | |
| 125 | req.hdr.nlmsg_len = sizeof(req); |
| 126 | req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
| 127 | req.hdr.nlmsg_pid = 0; |
| 128 | req.hdr.nlmsg_seq = request_sequence_; |
| 129 | req.msg.rtgen_family = AF_INET; |
| 130 | |
| 131 | if ((request_flags_ & REQUEST_LINK) != 0) { |
| 132 | req.hdr.nlmsg_type = RTM_GETLINK; |
| 133 | flag = REQUEST_LINK; |
| 134 | } else if ((request_flags_ & REQUEST_ADDR) != 0) { |
| 135 | req.hdr.nlmsg_type = RTM_GETADDR; |
| 136 | flag = REQUEST_ADDR; |
| 137 | } else if ((request_flags_ & REQUEST_ROUTE) != 0) { |
| 138 | req.hdr.nlmsg_type = RTM_GETROUTE; |
| 139 | flag = REQUEST_ROUTE; |
| 140 | } else |
| 141 | return; |
| 142 | |
| 143 | memset(&addr, 0, sizeof(addr)); |
| 144 | addr.nl_family = AF_NETLINK; |
| 145 | |
| 146 | if (sendto(rtnl_socket_, &req, sizeof(req), 0, |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 147 | reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0) { |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 148 | LOG(ERROR) << "RTNL sendto failed"; |
| 149 | return; |
| 150 | } |
| 151 | request_flags_ &= ~flag; |
| 152 | } |
| 153 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 154 | Device::Technology DeviceInfo::GetDeviceTechnology(const char *interface_name) { |
| 155 | char contents[1024]; |
| 156 | int length; |
| 157 | int fd; |
| 158 | string uevent_file = StringPrintf(kInterfaceUevent, interface_name); |
| 159 | string driver_file = StringPrintf(kInterfaceDriver, interface_name); |
| 160 | const char *wifi_type; |
| 161 | const char *driver_name; |
| 162 | int modem_idx; |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 163 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 164 | fd = open(uevent_file.c_str(), O_RDONLY); |
| 165 | if (fd < 0) |
| 166 | return Device::kUnknown; |
| 167 | |
| 168 | length = read(fd, contents, sizeof(contents) - 1); |
| 169 | if (length < 0) |
| 170 | return Device::kUnknown; |
| 171 | |
| 172 | /* |
| 173 | * If the "uevent" file contains the string "DEVTYPE=wlan\n" at the |
| 174 | * start of the file or after a newline, we can safely assume this |
| 175 | * is a wifi device. |
| 176 | */ |
| 177 | contents[length] = '\0'; |
| 178 | wifi_type = strstr(contents, "DEVTYPE=wlan\n"); |
| 179 | if (wifi_type != NULL && (wifi_type == contents || wifi_type[-1] == '\n')) |
| 180 | return Device::kWifi; |
| 181 | |
| 182 | length = readlink(driver_file.c_str(), contents, sizeof(contents)-1); |
| 183 | if (length < 0) |
| 184 | return Device::kUnknown; |
| 185 | |
| 186 | contents[length] = '\0'; |
| 187 | driver_name = strrchr(contents, '/'); |
| 188 | if (driver_name != NULL) { |
| 189 | driver_name++; |
| 190 | // See if driver for this interface is in a list of known modem driver names |
| 191 | for (modem_idx = 0; kModemDrivers[modem_idx] != NULL; modem_idx++) |
| 192 | if (strcmp(driver_name, kModemDrivers[modem_idx]) == 0) |
| 193 | return Device::kCellular; |
| 194 | } |
| 195 | |
| 196 | return Device::kEthernet; |
| 197 | } |
| 198 | |
| 199 | void DeviceInfo::AddLinkMsg(struct nlmsghdr *hdr) { |
| 200 | struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr)); |
| 201 | base::hash_map<int, scoped_refptr<Device> >::iterator ndev = |
| 202 | devices_.find(msg->ifi_index); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 203 | int bytes = IFLA_PAYLOAD(hdr); |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 204 | int dev_index = msg->ifi_index; |
| 205 | struct rtattr *rta; |
| 206 | int rta_bytes; |
| 207 | char *link_name = NULL; |
| 208 | Device *device; |
| 209 | Device::Technology technology; |
| 210 | bool is_stub = false; |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 211 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 212 | VLOG(2) << "add link index " << dev_index << " flags " << |
| 213 | msg->ifi_flags; |
| 214 | |
| 215 | if (ndev == devices_.end()) { |
| 216 | rta_bytes = IFLA_PAYLOAD(hdr); |
| 217 | for (rta = IFLA_RTA(msg); RTA_OK(rta, rta_bytes); |
| 218 | rta = RTA_NEXT(rta, rta_bytes)) { |
| 219 | if (rta->rta_type == IFLA_IFNAME) { |
| 220 | link_name = reinterpret_cast<char *>(RTA_DATA(rta)); |
| 221 | break; |
| 222 | } else { |
| 223 | VLOG(2) << " RTA type " << rta->rta_type; |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | if (link_name != NULL) |
| 228 | technology = GetDeviceTechnology(link_name); |
| 229 | |
| 230 | switch (technology) { |
| 231 | case Device::kEthernet: |
| 232 | device = new Ethernet(control_interface_, dispatcher_, |
| 233 | link_name, dev_index); |
| 234 | break; |
| 235 | case Device::kWifi: |
| 236 | device = new WiFi(control_interface_, dispatcher_, link_name, dev_index); |
| 237 | break; |
| 238 | default: |
| 239 | device = new StubDevice(control_interface_, dispatcher_, |
| 240 | link_name, dev_index, technology); |
| 241 | is_stub = true; |
| 242 | } |
| 243 | |
| 244 | devices_[dev_index] = device; |
| 245 | |
| 246 | if (!is_stub) |
| 247 | manager_->RegisterDevice(device); |
| 248 | } else { |
| 249 | device = ndev->second; |
| 250 | } |
| 251 | |
| 252 | // TODO(pstew): Send the the flags change upwards to the device |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 253 | } |
| 254 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 255 | void DeviceInfo::DelLinkMsg(struct nlmsghdr *hdr) { |
| 256 | struct ifinfomsg *msg = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(hdr)); |
| 257 | base::hash_map<int, scoped_refptr<Device> >::iterator ndev = |
| 258 | devices_.find(msg->ifi_index); |
| 259 | int dev_index = msg->ifi_index; |
| 260 | Device *device; |
| 261 | |
| 262 | VLOG(2) << "del link index " << dev_index << " flags " << |
| 263 | msg->ifi_flags; |
| 264 | |
| 265 | if (ndev != devices_.end()) { |
| 266 | device = ndev->second; |
| 267 | devices_.erase(ndev); |
| 268 | manager_->DeregisterDevice(device); |
| 269 | } |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 270 | } |
| 271 | |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 272 | void DeviceInfo::AddAddrMsg(struct nlmsghdr *hdr) { |
| 273 | struct ifaddrmsg *msg = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(hdr)); |
| 274 | VLOG(2) << "add addrmsg family " << (int) msg->ifa_family << " index " << |
| 275 | msg->ifa_index; |
| 276 | } |
| 277 | |
| 278 | void DeviceInfo::DelAddrMsg(struct nlmsghdr *hdr) { |
| 279 | struct ifaddrmsg *msg = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(hdr)); |
| 280 | VLOG(2) << "del addrmsg family " << (int) msg->ifa_family << " index " << |
| 281 | msg->ifa_index; |
| 282 | } |
| 283 | |
| 284 | void DeviceInfo::AddRouteMsg(struct nlmsghdr *hdr) { |
| 285 | struct rtmsg *msg = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(hdr)); |
| 286 | VLOG(2) << "add routemsg family " << (int) msg->rtm_family << " table " << |
| 287 | (int) msg->rtm_table << " proto " << (int) msg->rtm_protocol; |
| 288 | } |
| 289 | |
| 290 | void DeviceInfo::DelRouteMsg(struct nlmsghdr *hdr) { |
| 291 | struct rtmsg *msg = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(hdr)); |
| 292 | VLOG(2) << "del routemsg family " << (int) msg->rtm_family << " table " << |
| 293 | (int) msg->rtm_table << " proto " << (int) msg->rtm_protocol; |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 294 | } |
| 295 | |
| 296 | void DeviceInfo::ParseRTNL(InputData *data) { |
| 297 | unsigned char *buf = data->buf; |
| 298 | unsigned char *end = buf + data->len; |
| 299 | |
| 300 | while (buf < end) { |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 301 | struct nlmsghdr *hdr = reinterpret_cast<struct nlmsghdr *>(buf); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 302 | struct nlmsgerr *err; |
| 303 | |
| 304 | if (!NLMSG_OK(hdr, end - buf)) |
| 305 | break; |
| 306 | |
| 307 | switch (hdr->nlmsg_type) { |
| 308 | case NLMSG_NOOP: |
| 309 | case NLMSG_OVERRUN: |
| 310 | return; |
| 311 | case NLMSG_DONE: |
| 312 | NextRequest(hdr->nlmsg_seq); |
| 313 | return; |
| 314 | case NLMSG_ERROR: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 315 | err = reinterpret_cast<nlmsgerr *>(NLMSG_DATA(hdr)); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 316 | LOG(ERROR) << "error " << -err->error << " (" << strerror(-err->error) << |
| 317 | ")"; |
| 318 | return; |
| 319 | case RTM_NEWLINK: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 320 | AddLinkMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 321 | break; |
| 322 | case RTM_DELLINK: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 323 | DelLinkMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 324 | break; |
| 325 | case RTM_NEWADDR: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 326 | AddAddrMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 327 | break; |
| 328 | case RTM_DELADDR: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 329 | DelAddrMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 330 | break; |
| 331 | case RTM_NEWROUTE: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 332 | AddRouteMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 333 | break; |
| 334 | case RTM_DELROUTE: |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 335 | DelRouteMsg(hdr); |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 336 | break; |
| 337 | } |
| 338 | |
| 339 | buf += hdr->nlmsg_len; |
Paul Stewart | b50f0b9 | 2011-05-16 16:31:42 -0700 | [diff] [blame^] | 340 | } |
Paul Stewart | 0af98bf | 2011-05-10 17:38:08 -0700 | [diff] [blame] | 341 | } |
| 342 | |
| 343 | } // namespace shill |