⛏️ index : haiku.git

/*
 * Copyright 2006-2012, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel DΓΆrfler, axeld@pinc-software.de
 *		Alexander von Gluck, kallisti5@unixzen.com
 */


#include "AutoconfigLooper.h"

#include <errno.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <NetworkInterface.h>
#include <NetworkNotifications.h>

#include "DHCPClient.h"
#include "NetServer.h"


static const uint32 kMsgReadyToRun = 'rdyr';


AutoconfigLooper::AutoconfigLooper(BMessenger target, const char* device)
	: BLooper(device),
	fTarget(target),
	fDevice(device),
	fCurrentClient(NULL),
	fLastMediaStatus(0),
	fJoiningNetwork(false)
{
	BMessage ready(kMsgReadyToRun);
	PostMessage(&ready);
}


AutoconfigLooper::~AutoconfigLooper()
{
	_RemoveClient();
}


void
AutoconfigLooper::_RemoveClient()
{
	if (fCurrentClient == NULL)
		return;

	delete fCurrentClient;
	fCurrentClient = NULL;
}


void
AutoconfigLooper::_ConfigureIPv4()
{
	// start with DHCP

	if (fCurrentClient == NULL) {
		fCurrentClient = new DHCPClient(fTarget, fDevice.String());
		AddHandler(fCurrentClient);
	}

	// set IFF_CONFIGURING flag on interface

	BNetworkInterface interface(fDevice.String());
	int32 flags = interface.Flags() & ~IFF_AUTO_CONFIGURED;
	interface.SetFlags(flags | IFF_CONFIGURING);

	if (fCurrentClient->Start() == B_OK)
		return;

	_ConfigureIPv4Failed();
}


void
AutoconfigLooper::_ConfigureIPv4Failed()
{
	_RemoveClient();

	puts("DHCP failed miserably!");

	// DHCP obviously didn't work out, take some default values for now
	// TODO: have a look at zeroconf
	// TODO: this could also be done add-on based

	BNetworkInterface interface(fDevice.String());
	if ((interface.Flags() & IFF_CONFIGURING) == 0) {
		// Someone else configured the interface in the mean time
		return;
	}

	BMessage message(kMsgConfigureInterface);
	message.AddString("device", fDevice.String());
	message.AddBool("auto_configured", true);

	BNetworkAddress link;
	uint8 last = 56;
	if (interface.GetHardwareAddress(link) == B_OK) {
		// choose IP address depending on the MAC address, if available
		uint8* mac = link.LinkLevelAddress();
		last = mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5];
		if (last > 253)
			last = 253;
		else if (last == 0)
			last = 1;
	}

	// IANA defined the default autoconfig network (for when a DHCP request
	// fails for some reason) as being 169.254.0.0/255.255.0.0. We are only
	// generating the last octet but we could also use the 2 last octets if
	// wanted.
	char string[64];
	snprintf(string, sizeof(string), "169.254.0.%u", last);

	BMessage address;
	address.AddInt32("family", AF_INET);
	address.AddString("address", string);
	message.AddMessage("address", &address);

	fTarget.SendMessage(&message);
}


void
AutoconfigLooper::_ReadyToRun()
{
	start_watching_network(
		B_WATCH_NETWORK_LINK_CHANGES | B_WATCH_NETWORK_WLAN_CHANGES, this);

	BNetworkInterface interface(fDevice.String());
	if (interface.HasLink()) {
		_ConfigureIPv4();
		//_ConfigureIPv6();	// TODO: router advertisement and dhcpv6

		// Also make sure we don't spuriously try to configure again from
		// a link changed notification that might race us.
		fLastMediaStatus |= IFM_ACTIVE;
	}
}


void
AutoconfigLooper::_NetworkMonitorNotification(BMessage* message)
{
	int32 opcode;
	BString device;
	if (message->FindString("device", &device) != B_OK) {
		if (message->FindString("interface", &device) != B_OK)
			return;

		// TODO: Clean this mess up. Wireless devices currently use their
		// "device_name" in the interface field. First of all the
		// joins/leaves/scans should be device, not interface specific, so
		// the field should be changed. Then the device_name as seen by the
		// driver is missing the "/dev" part, as it is a relative path within
		// "/dev". On the other hand the net stack uses names that include
		// "/dev" as it uses them to open the fds, hence a full absolute path.
		// Note that the wpa_supplicant does the same workaround as we do here
		// to build an interface name, so that has to be changed as well when
		// this is fixed.
		device.Prepend("/dev/");
	}

	if (device != fDevice || message->FindInt32("opcode", &opcode) != B_OK)
		return;

	switch (opcode) {
		case B_NETWORK_DEVICE_LINK_CHANGED:
		{
			int32 media;
			if (message->FindInt32("media", &media) != B_OK)
				break;

			if ((fLastMediaStatus & IFM_ACTIVE) == 0 && (media & IFM_ACTIVE) != 0) {
				// Reconfigure the interface when we have a link again
				_ConfigureIPv4();
				//_ConfigureIPv6();	// TODO: router advertisement and dhcpv6
			} else if ((media & IFM_ACTIVE) == 0) {
				_RemoveClient();
			}

			fLastMediaStatus = media;
			break;
		}

		case B_NETWORK_WLAN_SCANNED:
		{
			if (fJoiningNetwork || (fLastMediaStatus & IFM_ACTIVE) != 0) {
				// We already have a link or are already joining.
				break;
			}

			fJoiningNetwork = true;
				// TODO: For now we never reset this flag. We can only do that
				// after infrastructure has been added to discern a scan reason.
				// If we would always auto join we would possibly interfere
				// with active scans in the process of connecting to an AP
				// either for the initial connection, or after connection loss
				// to re-establish the link.

			BMessage message(kMsgAutoJoinNetwork);
			message.AddString("device", fDevice);
			fTarget.SendMessage(&message);
			break;
		}
	}
}


void
AutoconfigLooper::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgReadyToRun:
			_ReadyToRun();
			break;

		case kMsgAutoConfigureFailed:
			_ConfigureIPv4Failed();
			break;

		case B_NETWORK_MONITOR:
			_NetworkMonitorNotification(message);
			break;

		default:
			BLooper::MessageReceived(message);
			break;
	}
}