/* * Copyright 2010-2015, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include extern "C" { # include # include # include } //#define TRACE_DEVICE #ifdef TRACE_DEVICE # define TRACE(x, ...) printf(x, __VA_ARGS__); #else # define TRACE(x, ...) ; #endif namespace { struct ie_data { uint8 type; uint8 length; uint8 data[1]; }; // #pragma mark - private functions (code shared with net_server) static status_t get_80211(const char* name, int32 type, void* data, int32& length) { FileDescriptorCloser socket(::socket(AF_INET, SOCK_DGRAM, 0)); if (!socket.IsSet()) return errno; struct ieee80211req ireq; strlcpy(ireq.i_name, name, IF_NAMESIZE); ireq.i_type = type; ireq.i_val = 0; ireq.i_len = length; ireq.i_data = data; if (ioctl(socket.Get(), SIOCG80211, &ireq, sizeof(struct ieee80211req)) < 0) return errno; length = ireq.i_len; return B_OK; } static status_t set_80211(const char* name, int32 type, void* data, int32 length = 0, int32 value = 0) { FileDescriptorCloser socket(::socket(AF_INET, SOCK_DGRAM, 0)); if (!socket.IsSet()) return errno; struct ieee80211req ireq; strlcpy(ireq.i_name, name, IF_NAMESIZE); ireq.i_type = type; ireq.i_val = value; ireq.i_len = length; ireq.i_data = data; if (ioctl(socket.Get(), SIOCS80211, &ireq, sizeof(struct ieee80211req)) < 0) return errno; return B_OK; } template status_t do_request(T& request, const char* name, int option) { FileDescriptorCloser socket(::socket(AF_LINK, SOCK_DGRAM, 0)); if (!socket.IsSet()) return errno; strlcpy(((struct ifreq&)request).ifr_name, name, IF_NAMESIZE); if (ioctl(socket.Get(), option, &request, sizeof(T)) < 0) return errno; return B_OK; } template<> status_t do_request(ieee80211req& request, const char* name, int option) { FileDescriptorCloser socket(::socket(AF_INET, SOCK_DGRAM, 0)); if (!socket.IsSet()) return errno; strlcpy(((struct ieee80211req&)request).i_name, name, IFNAMSIZ); if (ioctl(socket.Get(), option, &request, sizeof(request)) < 0) return errno; return B_OK; } //! Read a 16 bit little endian value static uint16 read_le16(uint8*& data, int32& length) { uint16 value = B_LENDIAN_TO_HOST_INT16(*(uint16*)data); data += 2; length -= 2; return value; } //! Read a 32 bit little endian value static uint32 read_le32(uint8*& data, int32& length) { uint32 value = B_LENDIAN_TO_HOST_INT32(*(uint32*)data); data += 4; length -= 4; return value; } static uint32 from_rsn_cipher(uint32 cipher) { if ((cipher & 0xffffff) != RSN_OUI) return B_NETWORK_CIPHER_CCMP; switch (cipher >> 24) { case RSN_CSE_NULL: return B_NETWORK_CIPHER_NONE; case RSN_CSE_WEP40: return B_NETWORK_CIPHER_WEP_40; case RSN_CSE_WEP104: return B_NETWORK_CIPHER_WEP_104; case RSN_CSE_TKIP: return B_NETWORK_CIPHER_TKIP; default: case RSN_CSE_CCMP: return B_NETWORK_CIPHER_CCMP; case RSN_CSE_WRAP: return B_NETWORK_CIPHER_AES_128_CMAC; } } static uint32 from_rsn_key_mode(uint32 mode) { if ((mode & 0xffffff) != RSN_OUI) return B_KEY_MODE_IEEE802_1X; switch (mode >> 24) { default: case RSN_ASE_8021X_UNSPEC: return B_KEY_MODE_IEEE802_1X; case RSN_ASE_8021X_PSK: return B_KEY_MODE_PSK; // the following are currently not defined in net80211 case 3: return B_KEY_MODE_FT_IEEE802_1X; case 4: return B_KEY_MODE_FT_PSK; case 5: return B_KEY_MODE_IEEE802_1X_SHA256; case 6: return B_KEY_MODE_PSK_SHA256; } } //! Parse RSN/WPA information elements common data static void parse_ie_rsn_wpa(wireless_network& network, uint8*& data, int32& length) { if (length >= 4) { // parse group cipher network.group_cipher = from_rsn_cipher(read_le32(data, length)); } else if (length > 0) return; if (length >= 2) { // parse unicast cipher uint16 count = read_le16(data, length); network.cipher = 0; for (uint16 i = 0; i < count; i++) { if (length < 4) return; network.cipher |= from_rsn_cipher(read_le32(data, length)); } } else if (length > 0) return; if (length >= 2) { // parse key management mode uint16 count = read_le16(data, length); network.key_mode = 0; for (uint16 i = 0; i < count; i++) { if (length < 4) return; network.key_mode |= from_rsn_key_mode(read_le32(data, length)); } } else if (length > 0) return; // TODO: capabilities, and PMKID following in case of RSN } //! Parse RSN (Robust Security Network) information element. static void parse_ie_rsn(wireless_network& network, ie_data* ie) { network.authentication_mode = B_NETWORK_AUTHENTICATION_WPA2; network.cipher = B_NETWORK_CIPHER_CCMP; network.group_cipher = B_NETWORK_CIPHER_CCMP; network.key_mode = B_KEY_MODE_IEEE802_1X; int32 length = ie->length; if (length < 2) return; uint8* data = ie->data; uint16 version = read_le16(data, length); if (version != RSN_VERSION) return; parse_ie_rsn_wpa(network, data, length); } //! Parse WPA information element. static bool parse_ie_wpa(wireless_network& network, ie_data* ie) { int32 length = ie->length; if (length < 6) return false; uint8* data = ie->data; uint32 oui = read_le32(data, length); TRACE(" oui: %" B_PRIx32 "\n", oui); if (oui != ((WPA_OUI_TYPE << 24) | WPA_OUI)) return false; uint16 version = read_le16(data, length); if (version != WPA_VERSION) return false; network.authentication_mode = B_NETWORK_AUTHENTICATION_WPA; network.cipher = B_NETWORK_CIPHER_TKIP; network.group_cipher = B_NETWORK_CIPHER_TKIP; network.key_mode = B_KEY_MODE_IEEE802_1X; parse_ie_rsn_wpa(network, data, length); return true; } //! Parse information elements. static void parse_ie(wireless_network& network, uint8* _ie, int32 ieLength) { struct ie_data* ie = (ie_data*)_ie; bool hadRSN = false; bool hadWPA = false; while (ieLength > 1) { TRACE("ie type %u\n", ie->type); switch (ie->type) { case IEEE80211_ELEMID_SSID: strlcpy(network.name, (char*)ie->data, min_c(ie->length + 1, (int)sizeof(network.name))); break; case IEEE80211_ELEMID_RSN: parse_ie_rsn(network, ie); hadRSN = true; break; case IEEE80211_ELEMID_VENDOR: if (!hadRSN && parse_ie_wpa(network, ie)) hadWPA = true; break; } ieLength -= 2 + ie->length; ie = (ie_data*)((uint8*)ie + 2 + ie->length); } if (hadRSN || hadWPA) { // Determine authentication mode if ((network.key_mode & (B_KEY_MODE_IEEE802_1X_SHA256 | B_KEY_MODE_PSK_SHA256)) != 0) { network.authentication_mode = B_NETWORK_AUTHENTICATION_WPA2; } else if ((network.key_mode & (B_KEY_MODE_IEEE802_1X | B_KEY_MODE_PSK | B_KEY_MODE_FT_IEEE802_1X | B_KEY_MODE_FT_PSK)) != 0) { if (!hadRSN) network.authentication_mode = B_NETWORK_AUTHENTICATION_WPA; } else if ((network.key_mode & B_KEY_MODE_NONE) != 0) { if ((network.cipher & (B_NETWORK_CIPHER_WEP_40 | B_NETWORK_CIPHER_WEP_104)) != 0) network.authentication_mode = B_NETWORK_AUTHENTICATION_WEP; else network.authentication_mode = B_NETWORK_AUTHENTICATION_NONE; } } } static void parse_ie(wireless_network& network, struct ieee80211req_sta_info& info) { parse_ie(network, (uint8*)&info + info.isi_ie_off, info.isi_ie_len); } static void parse_ie(wireless_network& network, struct ieee80211req_scan_result& result) { parse_ie(network, (uint8*)&result + result.isr_ie_off + result.isr_ssid_len + result.isr_meshid_len, result.isr_ie_len); } static bool get_ssid_from_ie(char* name, uint8* _ie, int32 ieLength) { struct ie_data* ie = (ie_data*)_ie; while (ieLength > 1) { switch (ie->type) { case IEEE80211_ELEMID_SSID: strlcpy(name, (char*)ie->data, min_c(ie->length + 1, 32)); return true; } ieLength -= 2 + ie->length; ie = (ie_data*)((uint8*)ie + 2 + ie->length); } return false; } static bool get_ssid_from_ie(char* name, struct ieee80211req_sta_info& info) { return get_ssid_from_ie(name, (uint8*)&info + info.isi_ie_off, info.isi_ie_len); } static void fill_wireless_network(wireless_network& network, struct ieee80211req_sta_info& info) { network.name[0] = '\0'; network.address.SetToLinkLevel(info.isi_macaddr, IEEE80211_ADDR_LEN); network.signal_strength = info.isi_rssi; network.noise_level = info.isi_noise; network.flags |= (info.isi_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0 ? B_NETWORK_IS_ENCRYPTED : 0; network.authentication_mode = 0; network.cipher = 0; network.group_cipher = 0; network.key_mode = 0; parse_ie(network, info); } static void fill_wireless_network(wireless_network& network, const char* networkName, struct ieee80211req_scan_result& result) { strlcpy(network.name, networkName, sizeof(network.name)); network.address.SetToLinkLevel(result.isr_bssid, IEEE80211_ADDR_LEN); network.signal_strength = result.isr_rssi; network.noise_level = result.isr_noise; network.flags = (result.isr_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0 ? B_NETWORK_IS_ENCRYPTED : 0; network.authentication_mode = 0; network.cipher = 0; network.group_cipher = 0; network.key_mode = 0; parse_ie(network, result); } static status_t get_scan_results(const char* device, wireless_network*& networks, uint32& count) { if (networks != NULL) return B_BAD_VALUE; // TODO: Find some way to reduce code duplication with the following function! const size_t kBufferSize = 65535; uint8* buffer = (uint8*)malloc(kBufferSize); if (buffer == NULL) return B_NO_MEMORY; MemoryDeleter deleter(buffer); int32 length = kBufferSize; status_t status = get_80211(device, IEEE80211_IOC_SCAN_RESULTS, buffer, length); if (status != B_OK) return status; BObjectList networksList(true); int32 bytesLeft = length; uint8* entry = buffer; while (bytesLeft > (int32)sizeof(struct ieee80211req_scan_result)) { ieee80211req_scan_result* result = (ieee80211req_scan_result*)entry; char networkName[32]; strlcpy(networkName, (char*)(result + 1), min_c((int)sizeof(networkName), result->isr_ssid_len + 1)); wireless_network* network = new wireless_network; fill_wireless_network(*network, networkName, *result); networksList.AddItem(network); entry += result->isr_len; bytesLeft -= result->isr_len; } count = 0; if (!networksList.IsEmpty()) { networks = new wireless_network[networksList.CountItems()]; for (int32 i = 0; i < networksList.CountItems(); i++) { networks[i] = *networksList.ItemAt(i); count++; } } return B_OK; } static status_t get_scan_result(const char* device, wireless_network& network, uint32 index, const BNetworkAddress* address, const char* name) { if (address != NULL && address->Family() != AF_LINK) return B_BAD_VALUE; // TODO: Find some way to reduce code duplication with the preceding function! const size_t kBufferSize = 65535; uint8* buffer = (uint8*)malloc(kBufferSize); if (buffer == NULL) return B_NO_MEMORY; MemoryDeleter deleter(buffer); int32 length = kBufferSize; status_t status = get_80211(device, IEEE80211_IOC_SCAN_RESULTS, buffer, length); if (status != B_OK) return status; int32 bytesLeft = length; uint8* entry = buffer; uint32 count = 0; while (bytesLeft > (int32)sizeof(struct ieee80211req_scan_result)) { ieee80211req_scan_result* result = (ieee80211req_scan_result*)entry; char networkName[32]; strlcpy(networkName, (char*)(result + 1), min_c((int)sizeof(networkName), result->isr_ssid_len + 1)); if (index == count || (address != NULL && !memcmp( address->LinkLevelAddress(), result->isr_bssid, IEEE80211_ADDR_LEN)) || (name != NULL && !strcmp(networkName, name))) { // Fill wireless_network with scan result data fill_wireless_network(network, networkName, *result); return B_OK; } entry += result->isr_len; bytesLeft -= result->isr_len; count++; } return B_ENTRY_NOT_FOUND; } static status_t get_station(const char* device, wireless_network& network, uint32 index, const BNetworkAddress* address, const char* name) { if (address != NULL && address->Family() != AF_LINK) return B_BAD_VALUE; const size_t kBufferSize = 65535; uint8* buffer = (uint8*)malloc(kBufferSize); if (buffer == NULL) return B_NO_MEMORY; MemoryDeleter deleter(buffer); struct ieee80211req_sta_req& request = *(ieee80211req_sta_req*)buffer; if (address != NULL) { memcpy(request.is_u.macaddr, address->LinkLevelAddress(), IEEE80211_ADDR_LEN); } else memset(request.is_u.macaddr, 0xff, IEEE80211_ADDR_LEN); int32 length = kBufferSize; status_t status = get_80211(device, IEEE80211_IOC_STA_INFO, &request, length); if (status != B_OK) return status; int32 bytesLeft = length; uint8* entry = (uint8*)&request.info[0]; uint32 count = 0; while (bytesLeft > (int32)sizeof(struct ieee80211req_sta_info)) { ieee80211req_sta_info* info = (ieee80211req_sta_info*)entry; char networkName[32]; get_ssid_from_ie(networkName, *info); if (index == count || address != NULL || (name != NULL && !strcmp(networkName, name))) { fill_wireless_network(network, *info); return B_OK; } entry += info->isi_len; bytesLeft -= info->isi_len; count++; } return B_ENTRY_NOT_FOUND; } static status_t get_network(const char* device, wireless_network& network, uint32 index, const BNetworkAddress* address, const char* name) { status_t status = get_station(device, network, index, address, name); if (status != B_OK) return get_scan_result(device, network, index, address, name); return B_OK; } } // namespace // #pragma mark - BNetworkDevice::BNetworkDevice() { Unset(); } BNetworkDevice::BNetworkDevice(const char* name) { SetTo(name); } BNetworkDevice::~BNetworkDevice() { } void BNetworkDevice::Unset() { fName[0] = '\0'; } void BNetworkDevice::SetTo(const char* name) { strlcpy(fName, name, IF_NAMESIZE); } const char* BNetworkDevice::Name() const { return fName; } bool BNetworkDevice::Exists() const { ifreq request; return do_request(request, Name(), SIOCGIFINDEX) == B_OK; } uint32 BNetworkDevice::Index() const { ifreq request; if (do_request(request, Name(), SIOCGIFINDEX) != B_OK) return 0; return request.ifr_index; } uint32 BNetworkDevice::Flags() const { ifreq request; if (do_request(request, Name(), SIOCGIFFLAGS) != B_OK) return 0; return request.ifr_flags; } bool BNetworkDevice::HasLink() const { return (Flags() & IFF_LINK) != 0; } int32 BNetworkDevice::Media() const { ifreq request; if (do_request(request, Name(), SIOCGIFMEDIA) != B_OK) return -1; return request.ifr_media; } status_t BNetworkDevice::SetMedia(int32 media) { ifreq request; request.ifr_media = media; return do_request(request, Name(), SIOCSIFMEDIA); } status_t BNetworkDevice::GetHardwareAddress(BNetworkAddress& address) { ifreq request; status_t status = do_request(request, Name(), SIOCGIFADDR); if (status != B_OK) return status; address.SetTo(request.ifr_addr); return B_OK; } bool BNetworkDevice::IsEthernet() { return IFM_TYPE(Media()) == IFM_ETHER; } bool BNetworkDevice::IsWireless() { return IFM_TYPE(Media()) == IFM_IEEE80211; } status_t BNetworkDevice::Control(int option, void* request) { switch (IFM_TYPE(Media())) { case IFM_ETHER: return do_request(*reinterpret_cast(request), &fName[0], option); case IFM_IEEE80211: return do_request(*reinterpret_cast(request), &fName[0], option); default: return B_ERROR; } } status_t BNetworkDevice::Scan(bool wait, bool forceRescan) { // Network status listener for change notifications class ScanListener : public BLooper { public: ScanListener(BString iface) : fInterface(iface) { start_watching_network(B_WATCH_NETWORK_WLAN_CHANGES, this); } virtual ~ScanListener() { stop_watching_network(this); } protected: virtual void MessageReceived(BMessage *message) { if (message->what != B_NETWORK_MONITOR) { BLooper::MessageReceived(message); return; } BString interfaceName; if (message->FindString("interface", &interfaceName) != B_OK) return; // See comment in AutoconfigLooper::_NetworkMonitorNotification // for the reason as to why we use FindFirst instead of ==. if (fInterface.FindFirst(interfaceName) < 0) return; if (message->FindInt32("opcode") != B_NETWORK_WLAN_SCANNED) return; Lock(); Quit(); } private: BString fInterface; }; ScanListener* listener = NULL; if (wait) listener = new ScanListener(Name()); // Trigger the scan struct ieee80211_scan_req request; memset(&request, 0, sizeof(request)); request.sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_BGSCAN | IEEE80211_IOC_SCAN_NOPICK | IEEE80211_IOC_SCAN_ONCE | (forceRescan ? IEEE80211_IOC_SCAN_FLUSH : 0); request.sr_duration = IEEE80211_IOC_SCAN_FOREVER; request.sr_nssid = 0; status_t status = set_80211(Name(), IEEE80211_IOC_SCAN_REQ, &request, sizeof(request)); // If there are no VAPs running, the net80211 layer will return ENXIO. // Try to bring up the interface (which should start a VAP) and try again. if (status == ENXIO) { struct ieee80211req dummy; status = set_80211(Name(), IEEE80211_IOC_HAIKU_COMPAT_WLAN_UP, &dummy, sizeof(dummy)); if (status != B_OK) return status; status = set_80211(Name(), IEEE80211_IOC_SCAN_REQ, &request, sizeof(request)); } // If there is already a scan currently running, it's probably an "infinite" // one, which we of course don't want to wait for. So just return immediately // if that's the case. if (status == EINPROGRESS) { delete listener; return B_OK; } if (!wait || status != B_OK) { delete listener; return status; } while (wait_for_thread(listener->Run(), NULL) == B_INTERRUPTED) ; return B_OK; } status_t BNetworkDevice::GetNetworks(wireless_network*& networks, uint32& count) { return get_scan_results(Name(), networks, count); } status_t BNetworkDevice::GetNetwork(const char* name, wireless_network& network) { if (name == NULL || name[0] == '\0') return B_BAD_VALUE; return get_network(Name(), network, UINT32_MAX, NULL, name); } status_t BNetworkDevice::GetNetwork(const BNetworkAddress& address, wireless_network& network) { if (address.Family() != AF_LINK) return B_BAD_VALUE; return get_network(Name(), network, UINT32_MAX, &address, NULL); } status_t BNetworkDevice::JoinNetwork(const char* name, const char* password) { if (name == NULL || name[0] == '\0') return B_BAD_VALUE; BMessage message(kMsgJoinNetwork); status_t status = message.AddString("device", Name()); if (status == B_OK) status = message.AddString("name", name); if (status == B_OK && password != NULL) status = message.AddString("password", password); if (status != B_OK) return status; // Send message to the net_server BMessenger networkServer(kNetServerSignature); BMessage reply; status = networkServer.SendMessage(&message, &reply); if (status == B_OK) reply.FindInt32("status", &status); return status; } status_t BNetworkDevice::JoinNetwork(const wireless_network& network, const char* password) { return JoinNetwork(network.address, password); } status_t BNetworkDevice::JoinNetwork(const BNetworkAddress& address, const char* password) { if (address.InitCheck() != B_OK) return B_BAD_VALUE; BMessage message(kMsgJoinNetwork); status_t status = message.AddString("device", Name()); if (status == B_OK) { status = message.AddFlat("address", const_cast(&address)); } if (status == B_OK && password != NULL) status = message.AddString("password", password); if (status != B_OK) return status; // Send message to the net_server BMessenger networkServer(kNetServerSignature); BMessage reply; status = networkServer.SendMessage(&message, &reply); if (status == B_OK) reply.FindInt32("status", &status); return status; } status_t BNetworkDevice::LeaveNetwork(const char* name) { BMessage message(kMsgLeaveNetwork); status_t status = message.AddString("device", Name()); if (status == B_OK) status = message.AddString("name", name); if (status == B_OK) status = message.AddInt32("reason", IEEE80211_REASON_UNSPECIFIED); if (status != B_OK) return status; BMessenger networkServer(kNetServerSignature); BMessage reply; status = networkServer.SendMessage(&message, &reply); if (status == B_OK) reply.FindInt32("status", &status); return status; } status_t BNetworkDevice::LeaveNetwork(const wireless_network& network) { return LeaveNetwork(network.address); } status_t BNetworkDevice::LeaveNetwork(const BNetworkAddress& address) { BMessage message(kMsgLeaveNetwork); status_t status = message.AddString("device", Name()); if (status == B_OK) { status = message.AddFlat("address", const_cast(&address)); } if (status == B_OK) status = message.AddInt32("reason", IEEE80211_REASON_UNSPECIFIED); if (status != B_OK) return status; BMessenger networkServer(kNetServerSignature); BMessage reply; status = networkServer.SendMessage(&message, &reply); if (status == B_OK) reply.FindInt32("status", &status); return status; } status_t BNetworkDevice::GetNextAssociatedNetwork(uint32& cookie, wireless_network& network) { BNetworkAddress address; status_t status = GetNextAssociatedNetwork(cookie, address); if (status != B_OK) return status; return GetNetwork(address, network); } status_t BNetworkDevice::GetNextAssociatedNetwork(uint32& cookie, BNetworkAddress& address) { // We currently support only a single associated network if (cookie != 0) return B_ENTRY_NOT_FOUND; uint8 mac[IEEE80211_ADDR_LEN]; int32 length = IEEE80211_ADDR_LEN; status_t status = get_80211(Name(), IEEE80211_IOC_BSSID, mac, length); if (status != B_OK) return status; if (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 && mac[4] == 0 && mac[5] == 0) { return B_ENTRY_NOT_FOUND; } address.SetToLinkLevel(mac, IEEE80211_ADDR_LEN); cookie++; return B_OK; }