/* * Copyright 2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. * Distributed under the terms of the MIT License. */ #include "vfs_net_boot.h" #include #include #include #include #include #include #include #include #include #include #include #include static bool string_starts_with(const char* string, const char* prefix) { size_t stringLen = strlen(string); size_t prefixLen = strlen(prefix); return (stringLen >= prefixLen && strncmp(string, prefix, prefixLen) == 0); } static bool is_net_device(KDiskDevice* device) { const char* path = device->Path(); return (string_starts_with(path, "/dev/disk/virtual/nbd/") || string_starts_with(path, "/dev/disk/virtual/remote_disk/")); } static int compare_partitions_net_devices(const void *_a, const void *_b) { KPartition* a = *(KPartition**)_a; KPartition* b = *(KPartition**)_b; bool aIsNetDevice = is_net_device(a->Device()); bool bIsNetDevice = is_net_device(b->Device()); int compare = (int)aIsNetDevice - (int)bIsNetDevice; if (compare != 0) return compare; return compare_image_boot(_a, _b); } class NetStackInitializer { public: NetStackInitializer(uint64 clientMAC, uint32 clientIP, uint32 netMask) : fSocket(-1), fLinkSocket(-1), fClientMAC(clientMAC), fClientIP(clientIP), fNetMask(netMask), fFoundInterface(false), fConfiguredInterface(false) { } ~NetStackInitializer() { // close control sockets if (fSocket >= 0) close(fSocket); if (fLinkSocket >= 0) close(fLinkSocket); } status_t Init() { // open a control socket for playing with the stack fSocket = socket(AF_INET, SOCK_DGRAM, 0); if (fSocket < 0) { dprintf("NetStackInitializer: Failed to open socket: %s\n", strerror(errno)); return errno; } // ... and a link level socket fLinkSocket = socket(AF_LINK, SOCK_DGRAM, 0); if (fLinkSocket < 0) { dprintf("NetStackInitializer: Failed to open link level socket:" " %s\n", strerror(errno)); return errno; } // now iterate through the existing network devices KPath path; status_t error = path.SetTo("/dev/net"); if (error != B_OK) return error; _ScanDevices(path); return fConfiguredInterface ? B_OK : B_ERROR; } private: void _ScanDevices(KPath& path) { DIR* dir = opendir(path.Path()); if (!dir) { dprintf("NetStackInitializer: Failed to opendir() \"%s\": %s\n", path.Path(), strerror(errno)); return; } status_t error = B_OK; while (dirent* entry = readdir(dir)) { // skip "." and ".." if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } path.Append(entry->d_name); struct stat st; if (stat(path.Path(), &st) == 0) { if (S_ISDIR(st.st_mode)) _ScanDevices(path); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) _ScanDevice(path.Path()); } path.RemoveLeaf(); if (fFoundInterface) break; } closedir(dir); } void _ScanDevice(const char* path) { dprintf("NetStackInitializer: scanning device %s\n", path); // check if this interface is already known ifreq request; if (strlen(path) >= IF_NAMESIZE) return; strcpy(request.ifr_name, path); if (ioctl(fSocket, SIOCGIFINDEX, &request, sizeof(request)) < 0) { // not known yet -- add it request.ifr_parameter.base_name[0] = '\0'; request.ifr_parameter.device[0] = '\0'; request.ifr_parameter.sub_type = 0; if (ioctl(fSocket, SIOCAIFADDR, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: adding interface failed for " "device %s: %s\n", path, strerror(errno)); return; } } // bring the interface up (get flags, add IFF_UP) if (ioctl(fSocket, SIOCGIFFLAGS, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: getting flags failed for interface " "%s: %s\n", path, strerror(errno)); return; } int interfaceFlags = request.ifr_flags; if (!(interfaceFlags & IFF_UP)) { interfaceFlags |= IFF_UP; request.ifr_flags = interfaceFlags; if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: failed to bring interface up " "%s: %s\n", path, strerror(errno)); return; } } // get the MAC address if (ioctl(fLinkSocket, SIOCGIFADDR, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: Getting MAC addresss failed for " "interface %s: %s\n", path, strerror(errno)); return; } sockaddr_dl& link = *(sockaddr_dl*)&request.ifr_addr; if (link.sdl_type != IFT_ETHER) return; if (link.sdl_alen == 0) return; uint8* macBytes = (uint8 *)LLADDR(&link); uint64 macAddress = ((uint64)macBytes[0] << 40) | ((uint64)macBytes[1] << 32) | ((uint64)macBytes[2] << 24) | ((uint64)macBytes[3] << 16) | ((uint64)macBytes[4] << 8) | (uint64)macBytes[5]; dprintf("NetStackInitializer: found ethernet interface with MAC " "address %02x:%02x:%02x:%02x:%02x:%02x; which is%s the one we're " "looking for\n", macBytes[0], macBytes[1], macBytes[2], macBytes[3], macBytes[4], macBytes[5], (macAddress == fClientMAC ? "" : "n't")); if (macAddress != fClientMAC) return; fFoundInterface = true; // configure the interface // set IP address sockaddr_in& address = *(sockaddr_in*)&request.ifr_addr; address.sin_family = AF_INET; address.sin_len = sizeof(sockaddr_in); address.sin_port = 0; address.sin_addr.s_addr = htonl(fClientIP); memset(&address.sin_zero[0], 0, sizeof(address.sin_zero)); if (ioctl(fSocket, SIOCSIFADDR, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: Setting IP addresss failed for " "interface %s: %s\n", path, strerror(errno)); return; } // set net mask address.sin_addr.s_addr = htonl(fNetMask); if (ioctl(fSocket, SIOCSIFNETMASK, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: Setting net mask failed for " "interface %s: %s\n", path, strerror(errno)); return; } // set broadcast address address.sin_addr.s_addr = htonl(fClientIP | ~fNetMask); if (ioctl(fSocket, SIOCSIFBRDADDR, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: Setting broadcast address failed for " "interface %s: %s\n", path, strerror(errno)); return; } // set IFF_BROADCAST if (!(interfaceFlags & IFF_BROADCAST)) { interfaceFlags |= IFF_BROADCAST; request.ifr_flags = interfaceFlags; if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: failed to set IFF_BROADCAST flag " "for interface %s: %s\n", path, strerror(errno)); return; } } // set default route; remove previous one, if any route_entry route; memset(&route, 0, sizeof(route_entry)); route.flags = RTF_STATIC | RTF_DEFAULT; request.ifr_route = route; ioctl(fSocket, SIOCDELRT, &request, sizeof(request)); if (ioctl(fSocket, SIOCADDRT, &request, sizeof(request)) < 0) { dprintf("NetStackInitializer: Failed to set default route: %s\n", strerror(errno)); return; } fConfiguredInterface = true; dprintf("NetStackInitializer: successfully configured boot network " "interface\n"); } private: int fSocket; int fLinkSocket; uint64 fClientMAC; uint32 fClientIP; uint32 fNetMask; bool fFoundInterface; bool fConfiguredInterface; }; // #pragma mark - NetBootMethod NetBootMethod::NetBootMethod(const KMessage& bootVolume, int32 method) : BootMethod(bootVolume, method) { } NetBootMethod::~NetBootMethod() { } status_t NetBootMethod::Init() { // We need to bring up the net stack. status_t status; uint64 clientMAC; uint32 clientIP; uint32 netMask; if (fBootVolume.FindInt64("client MAC", (int64*)&clientMAC) != B_OK || fBootVolume.FindInt32("client IP", (int32*)&clientIP) != B_OK) { panic("no client MAC or IP address or net mask\n"); return B_ERROR; } if (fBootVolume.FindInt32("net mask", (int32*)&netMask) != B_OK) { // choose default netmask depending on the class of the address in_addr_t net = htonl(clientIP); if (IN_CLASSA(net) || (ntohl(net) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) { // class A, or loopback netMask = ntohl(IN_CLASSA_NET); } else if (IN_CLASSB(net)) { // class B netMask = ntohl(IN_CLASSB_NET); } else { // class C and rest netMask = ntohl(IN_CLASSC_NET); } } NetStackInitializer initializer(clientMAC, clientIP, netMask); status = initializer.Init(); if (status != B_OK) return status; // TODO: "net root path" should be used for finding the boot device/FS, // but ATM neither the remote_disk nor the nbd driver are configurable // at this point. const char* rootPath = fBootVolume.GetString("net root path", NULL); dprintf("NetBootMethod::Init(): net stack initialized; root path is: %s\n", rootPath); return B_OK; } bool NetBootMethod::IsBootDevice(KDiskDevice* device, bool strict) { // We support only NBD and RemoteDisk at the moment, so we accept any // device under /dev/disk/virtual/{nbd,remote_disk}/. return is_net_device(device); } bool NetBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure) { // as long as it's BFS, we're fine return (partition->ContentType() && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0); } void NetBootMethod::SortPartitions(KPartition** partitions, int32 count) { qsort(partitions, count, sizeof(KPartition*), compare_partitions_net_devices); }