haiku/src/servers/net/NetServer.cpp
Axel Dörfler fb81684f81 The beginnings of automatic configuration of network devices using DHCP; this is
currently only triggered when there is no configuration file - it can't be configured
this way yet.
All DHCP currently does is to send a UDP broadcast DHCP discover message. More to come.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19437 a95241bf-73f2-0310-859d-f6bbb57e9c96
2006-12-06 19:00:00 +00:00

681 lines
16 KiB
C++

/*
* Copyright 2006, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
*/
#include "AutoconfigLooper.h"
#include "NetServer.h"
#include "Settings.h"
#include <Alert.h>
#include <Application.h>
#include <Directory.h>
#include <Entry.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <TextView.h>
#include <arpa/inet.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <map>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
typedef std::map<std::string, BLooper*> LooperMap;
class NetServer : public BApplication {
public:
NetServer();
virtual void AboutRequested();
virtual void ReadyToRun();
virtual void MessageReceived(BMessage* message);
private:
bool _TestForInterface(int socket, const char* name);
status_t _ConfigureInterface(int socket, BMessage& interface);
bool _QuitLooperForDevice(const char* device);
BLooper* _LooperForDevice(const char* device);
status_t _ConfigureDevice(int socket, const char* path);
void _ConfigureDevices(int socket, const char* path);
void _ConfigureInterfaces(int socket);
void _BringUpInterfaces();
Settings fSettings;
LooperMap fDeviceMap;
};
struct address_family {
int family;
const char* name;
const char* identifiers[4];
bool (*parse_address)(const char* string, sockaddr* _address);
};
// AF_INET family
static bool inet_parse_address(const char* string, sockaddr* address);
static const address_family kFamilies[] = {
{
AF_INET,
"inet",
{"AF_INET", "inet", "ipv4", NULL},
inet_parse_address
},
{ -1, NULL, {NULL}, NULL }
};
static bool
inet_parse_address(const char* string, sockaddr* _address)
{
in_addr inetAddress;
if (inet_aton(string, &inetAddress) != 1)
return false;
sockaddr_in& address = *(sockaddr_in *)_address;
address.sin_family = AF_INET;
address.sin_len = sizeof(struct sockaddr_in);
address.sin_port = 0;
address.sin_addr = inetAddress;
memset(&address.sin_zero[0], 0, sizeof(address.sin_zero));
return true;
}
// #pragma mark -
static bool
get_family_index(const char* name, int32& familyIndex)
{
for (int32 i = 0; kFamilies[i].family >= 0; i++) {
for (int32 j = 0; kFamilies[i].identifiers[j]; j++) {
if (!strcmp(name, kFamilies[i].identifiers[j])) {
// found a match
familyIndex = i;
return true;
}
}
}
// defaults to AF_INET
familyIndex = 0;
return false;
}
static bool
parse_address(int32 familyIndex, const char* argument, struct sockaddr& address)
{
if (argument == NULL)
return false;
return kFamilies[familyIndex].parse_address(argument, &address);
}
bool
prepare_request(ifreq& request, const char* name)
{
if (strlen(name) > IF_NAMESIZE)
return false;
strcpy(request.ifr_name, name);
return true;
}
status_t
get_mac_address(const char* device, uint8* address)
{
int socket = ::socket(AF_LINK, SOCK_DGRAM, 0);
if (socket < 0)
return errno;
ifreq request;
if (!prepare_request(request, device)) {
close(socket);
return B_ERROR;
}
if (ioctl(socket, SIOCGIFADDR, &request, sizeof(struct ifreq)) < 0) {
close(socket);
return errno;
}
close(socket);
sockaddr_dl &link = *(sockaddr_dl *)&request.ifr_addr;
if (link.sdl_type != IFT_ETHER)
return B_BAD_TYPE;
if (link.sdl_alen == 0)
return B_ENTRY_NOT_FOUND;
uint8 *mac = (uint8 *)LLADDR(&link);
memcpy(address, mac, 6);
return B_OK;
}
// #pragma mark -
NetServer::NetServer()
: BApplication("application/x-vnd.haiku-net_server")
{
}
void
NetServer::AboutRequested()
{
BAlert *alert = new BAlert("about", "Networking Server\n"
"\tCopyright " B_UTF8_COPYRIGHT "2006, Haiku.\n", "Ok");
BTextView *view = alert->TextView();
BFont font;
view->SetStylable(true);
view->GetFont(&font);
font.SetSize(18);
font.SetFace(B_BOLD_FACE);
view->SetFontAndColor(0, 17, &font);
alert->Go(NULL);
}
void
NetServer::ReadyToRun()
{
fSettings.StartMonitoring(this);
_BringUpInterfaces();
}
void
NetServer::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_NODE_MONITOR:
fSettings.Update(message);
break;
case kMsgInterfaceSettingsUpdated:
{
// we need a socket to talk to the networking stack
int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket < 0)
break;
_ConfigureInterfaces(socket);
close(socket);
break;
}
case kMsgConfigureInterface:
{
if (!message->ReturnAddress().IsTargetLocal()) {
// for now, we only accept this message from add-ons
break;
}
// we need a socket to talk to the networking stack
int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket < 0)
break;
_ConfigureInterface(socket, *message);
close(socket);
break;
}
default:
BApplication::MessageReceived(message);
return;
}
}
bool
NetServer::_TestForInterface(int socket, const char* name)
{
// get a list of all interfaces
ifconf config;
config.ifc_len = sizeof(config.ifc_value);
if (ioctl(socket, SIOCGIFCOUNT, &config, sizeof(struct ifconf)) < 0)
return false;
uint32 count = (uint32)config.ifc_value;
if (count == 0) {
// there are no interfaces yet
return false;
}
void *buffer = malloc(count * sizeof(struct ifreq));
if (buffer == NULL) {
fprintf(stderr, "%s: Out of memory.\n", Name());
return false;
}
config.ifc_len = count * sizeof(struct ifreq);
config.ifc_buf = buffer;
if (ioctl(socket, SIOCGIFCONF, &config, sizeof(struct ifconf)) < 0)
return false;
ifreq *interface = (ifreq *)buffer;
int32 nameLength = strlen(name);
bool success = false;
for (uint32 i = 0; i < count; i++) {
if (!strncmp(interface->ifr_name, name, nameLength)) {
success = true;
break;
}
interface = (ifreq *)((addr_t)interface + IF_NAMESIZE + interface->ifr_addr.sa_len);
}
free(buffer);
return success;
}
status_t
NetServer::_ConfigureInterface(int socket, BMessage& interface)
{
const char *device;
if (interface.FindString("device", &device) != B_OK)
return B_BAD_VALUE;
ifreq request;
if (!prepare_request(request, device))
return B_ERROR;
int32 flags;
if (interface.FindInt32("flags", &flags) < B_OK)
flags = IFF_UP;
int32 mtu;
if (interface.FindInt32("mtu", &mtu) < B_OK)
mtu = -1;
int32 metric;
if (interface.FindInt32("metric", &metric) < B_OK)
metric = -1;
BMessage addressMessage;
for (int32 index = 0; interface.FindMessage("address", index, &addressMessage) == B_OK;
index++) {
const char* family;
if (addressMessage.FindString("family", &family) < B_OK)
continue;
int32 familyIndex;
if (!get_family_index(family, familyIndex)) {
// we don't support this family
continue;
}
int familySocket = socket;
if (kFamilies[familyIndex].family != AF_INET)
socket = ::socket(kFamilies[familyIndex].family, SOCK_DGRAM, 0);
if (socket < 0) {
// the family is not available in this environment
continue;
}
uint32 interfaceIndex = 0;
if (ioctl(socket, SIOCGIFINDEX, &request, sizeof(request)) >= 0)
interfaceIndex = request.ifr_index;
if (interfaceIndex == 0) {
// we need to create the interface first
request.ifr_parameter.base_name[0] = '\0';
request.ifr_parameter.device[0] = '\0';
request.ifr_parameter.sub_type = 0;
// the default device is okay for us
if (ioctl(socket, SIOCAIFADDR, &request, sizeof(request)) < 0) {
fprintf(stderr, "%s: Could not add interface: %s\n", Name(),
strerror(errno));
return errno;
}
}
// retrieve addresses
bool hasAddress = false, hasMask = false, hasPeer = false, hasBroadcast = false;
struct sockaddr address, mask, peer, broadcast, gateway;
const char* string;
if (addressMessage.FindString("address", &string) == B_OK
&& parse_address(familyIndex, string, address)) {
hasAddress = true;
if (addressMessage.FindString("mask", &string) == B_OK
&& parse_address(familyIndex, string, mask))
hasMask = true;
}
if (addressMessage.FindString("peer", &string) == B_OK
&& parse_address(familyIndex, string, peer))
hasPeer = true;
if (addressMessage.FindString("broadcast", &string) == B_OK
&& parse_address(familyIndex, string, broadcast))
hasBroadcast = true;
// add gateway route, if we're asked for it
if (addressMessage.FindString("gateway", &string) == B_OK
&& parse_address(familyIndex, string, gateway)) {
route_entry route;
memset(&route, 0, sizeof(route_entry));
route.flags = RTF_STATIC | RTF_DEFAULT | RTF_GATEWAY;
route.gateway = &gateway;
request.ifr_route = route;
ioctl(socket, SIOCDELRT, &request, sizeof(request));
// Try to remove a previous default route, doesn't matter
// if it fails.
if (ioctl(socket, SIOCADDRT, &request, sizeof(request)) < 0) {
fprintf(stderr, "%s: Could not add route for %s: %s\n",
Name(), device, strerror(errno));
}
}
// set addresses
if (hasAddress) {
memcpy(&request.ifr_addr, &address, address.sa_len);
if (ioctl(familySocket, SIOCSIFADDR, &request, sizeof(struct ifreq)) < 0) {
fprintf(stderr, "%s: Setting address failed: %s\n", Name(), strerror(errno));
continue;
}
}
if (ioctl(familySocket, SIOCGIFFLAGS, &request, sizeof(struct ifreq)) < 0) {
fprintf(stderr, "%s: Getting flags failed: %s\n", Name(), strerror(errno));
continue;
}
int32 currentFlags = request.ifr_flags;
if (!hasMask && hasAddress && kFamilies[familyIndex].family == AF_INET
&& ioctl(familySocket, SIOCGIFNETMASK, &request, sizeof(struct ifreq)) == 0
&& request.ifr_mask.sa_family == AF_UNSPEC) {
// generate standard netmask if it doesn't have one yet
sockaddr_in *netmask = (sockaddr_in *)&mask;
netmask->sin_len = sizeof(sockaddr_in);
netmask->sin_family = AF_INET;
// choose default netmask depending on the class of the address
in_addr_t net = ((sockaddr_in *)&address)->sin_addr.s_addr;
if (IN_CLASSA(net)
|| (ntohl(net) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) {
// class A, or loopback
netmask->sin_addr.s_addr = IN_CLASSA_NET;
} if (IN_CLASSB(net)) {
// class B
netmask->sin_addr.s_addr = IN_CLASSB_NET;
} else {
// class C and rest
netmask->sin_addr.s_addr = IN_CLASSC_NET;
}
hasMask = true;
}
if (hasMask) {
memcpy(&request.ifr_mask, &mask, mask.sa_len);
if (ioctl(familySocket, SIOCSIFNETMASK, &request, sizeof(struct ifreq)) < 0) {
fprintf(stderr, "%s: Setting subnet mask failed: %s\n", Name(), strerror(errno));
continue;
}
}
if (!hasBroadcast && hasAddress && (currentFlags & IFF_BROADCAST)
&& kFamilies[familyIndex].family == AF_INET
&& ioctl(familySocket, SIOCGIFBRDADDR, &request, sizeof(struct ifreq)) == 0
&& request.ifr_mask.sa_family == AF_UNSPEC) {
// generate standard broadcast address if it doesn't have one yet
sockaddr_in *broadcastAddr = (sockaddr_in *)&broadcast;
uint32 maskValue = ((sockaddr_in *)&mask)->sin_addr.s_addr;
uint32 broadcastValue = ((sockaddr_in *)&address)->sin_addr.s_addr;
broadcastValue = (broadcastValue & maskValue) | ~maskValue;
broadcastAddr->sin_len = sizeof(sockaddr_in);
broadcastAddr->sin_family = AF_INET;
broadcastAddr->sin_addr.s_addr = broadcastValue;
hasBroadcast = true;
}
if (hasBroadcast) {
memcpy(&request.ifr_broadaddr, &broadcast, broadcast.sa_len);
if (ioctl(familySocket, SIOCSIFBRDADDR, &request, sizeof(struct ifreq)) < 0) {
fprintf(stderr, "%s: Setting broadcast address failed: %s\n", Name(), strerror(errno));
continue;
}
}
if (hasPeer) {
memcpy(&request.ifr_dstaddr, &peer, peer.sa_len);
if (ioctl(familySocket, SIOCSIFDSTADDR, &request, sizeof(struct ifreq)) < 0) {
fprintf(stderr, "%s: Setting peer address failed: %s\n", Name(), strerror(errno));
continue;
}
}
// set flags
if (flags != 0) {
request.ifr_flags = currentFlags | flags;
if (ioctl(familySocket, SIOCSIFFLAGS, &request, sizeof(struct ifreq)) < 0)
fprintf(stderr, "%s: Setting flags failed: %s\n", Name(), strerror(errno));
}
// set options
if (mtu != -1) {
request.ifr_mtu = mtu;
if (ioctl(familySocket, SIOCSIFMTU, &request, sizeof(struct ifreq)) < 0)
fprintf(stderr, "%s: Setting MTU failed: %s\n", Name(), strerror(errno));
}
if (metric != -1) {
request.ifr_metric = metric;
if (ioctl(familySocket, SIOCSIFMETRIC, &request, sizeof(struct ifreq)) < 0)
fprintf(stderr, "%s: Setting metric failed: %s\n", Name(), strerror(errno));
}
}
return B_OK;
}
bool
NetServer::_QuitLooperForDevice(const char* device)
{
LooperMap::iterator iterator = fDeviceMap.find(device);
if (iterator == fDeviceMap.end())
return false;
// there is a looper for this device - quit it
iterator->second->Lock();
iterator->second->Quit();
fDeviceMap.erase(iterator);
return true;
}
BLooper*
NetServer::_LooperForDevice(const char* device)
{
LooperMap::const_iterator iterator = fDeviceMap.find(device);
if (iterator == fDeviceMap.end())
return NULL;
return iterator->second;
}
status_t
NetServer::_ConfigureDevice(int socket, const char* path)
{
_QuitLooperForDevice(path);
// bring interface up, but don't configure it just yet
BMessage interface;
interface.AddString("device", path);
BMessage address;
address.AddString("family", "inet");
interface.AddMessage("address", &address);
status_t status = _ConfigureInterface(socket, interface);
if (status < B_OK)
return status;
// add a default route to make the interface accessible, even without an address
route_entry route;
memset(&route, 0, sizeof(route_entry));
route.flags = RTF_STATIC | RTF_DEFAULT;
ifreq request;
if (!prepare_request(request, path))
return B_ERROR;
request.ifr_route = route;
if (ioctl(socket, SIOCADDRT, &request, sizeof(request)) < 0) {
fprintf(stderr, "%s: Could not add route for %s: %s\n",
Name(), path, strerror(errno));
}
AutoconfigLooper* looper = new AutoconfigLooper(this, path);
looper->Run();
fDeviceMap[path] = looper;
return B_OK;
}
void
NetServer::_ConfigureDevices(int socket, const char* startPath)
{
BDirectory directory(startPath);
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
char name[B_FILE_NAME_LENGTH];
struct stat stat;
BPath path;
if (entry.GetName(name) != B_OK
|| !strcmp(name, "stack")
|| entry.GetPath(&path) != B_OK
|| entry.GetStat(&stat) != B_OK)
continue;
if (S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode))
_ConfigureDevice(socket, path.Path());
else if (entry.IsDirectory())
_ConfigureDevices(socket, path.Path());
}
}
void
NetServer::_ConfigureInterfaces(int socket)
{
BMessage interface;
uint32 cookie = 0;
while (fSettings.GetNextInterface(cookie, interface) == B_OK) {
const char *device;
if (interface.FindString("device", &device) != B_OK)
continue;
if (!strncmp(device, "/dev/net/", 9)) {
// it's a kernel device, check if it's present
BEntry entry(device);
if (!entry.Exists())
continue;
}
_ConfigureInterface(socket, interface);
}
}
void
NetServer::_BringUpInterfaces()
{
// we need a socket to talk to the networking stack
int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket < 0) {
fprintf(stderr, "%s: The networking stack doesn't seem to be available.\n",
Name());
Quit();
return;
}
// First, we look into the settings, and try to bring everything up from there
_ConfigureInterfaces(socket);
// check configuration
if (!_TestForInterface(socket, "loop")) {
// there is no loopback interface, create one
BMessage interface;
interface.AddString("device", "loop");
BMessage address;
address.AddString("family", "inet");
address.AddString("address", "127.0.0.1");
interface.AddMessage("address", &address);
_ConfigureInterface(socket, interface);
}
if (!_TestForInterface(socket, "/dev/net/")) {
// there is no driver configured - see if there is one and try to use it
_ConfigureDevices(socket, "/dev/net");
}
close(socket);
}
// #pragma mark -
int
main()
{
NetServer app;
app.Run();
return 0;
}