/*++

Copyright (c) 2001-2002  Microsoft Corporation

Module Name:

    common.c

Abstract:

    This module contains the teredo interface to the IPv6 Helper Service.

Author:

    Mohit Talwar (mohitt) Wed Nov 07 11:27:01 2001

Environment:

    User mode only.

--*/

#include "precomp.h"
#pragma hdrstop

CONST IN6_ADDR TeredoIpv6ServicePrefix = TEREDO_SERVICE_PREFIX;
CONST IN6_ADDR TeredoIpv6MulticastPrefix = TEREDO_MULTICAST_PREFIX;

#define DEVICE_PREFIX L"\\Device\\"

LPGUID TeredoWmiEvent[] = {
    (LPGUID) &GUID_NDIS_NOTIFY_ADAPTER_ARRIVAL,
    (LPGUID) &GUID_NDIS_NOTIFY_ADAPTER_REMOVAL,
    (LPGUID) &GUID_NDIS_NOTIFY_DEVICE_POWER_ON,
    (LPGUID) &GUID_NDIS_NOTIFY_DEVICE_POWER_OFF,
};

HANDLE TeredoTimer;             // Periodic timer started for the service.
HANDLE TeredoTimerEvent;        // Event signalled upon Timer deletion.
HANDLE TeredoTimerEventWait;    // Wait registered for TimerEvent.
ULONG TeredoResolveInterval = TEREDO_RESOLVE_INTERVAL;

ULONG TeredoClientRefreshInterval = TEREDO_REFRESH_INTERVAL;
BOOL TeredoClientEnabled = (TEREDO_DEFAULT_TYPE == TEREDO_CLIENT);
BOOL TeredoServerEnabled = (TEREDO_DEFAULT_TYPE == TEREDO_SERVER);
WCHAR TeredoServerName[NI_MAXHOST] = TEREDO_SERVER_NAME;

BOOL TeredoInitialized = FALSE;


ICMPv6Header *
TeredoParseIpv6Headers (
    IN PUCHAR Buffer,
    IN ULONG Bytes
    )
{
    UCHAR NextHeader = IP_PROTOCOL_V6;
    ULONG Length;

    //
    // Parse up until the ICMPv6 header.
    //
    for (;;) {
        switch (NextHeader) {
        case IP_PROTOCOL_V6:
            if (Bytes < sizeof(IP6_HDR)) {
                return NULL;
            }
            NextHeader = ((PIP6_HDR) Buffer)->ip6_nxt;
            Length = sizeof(IP6_HDR);
            break;
            
        case IP_PROTOCOL_HOP_BY_HOP:
        case IP_PROTOCOL_DEST_OPTS:
        case IP_PROTOCOL_ROUTING:
            if (Bytes < sizeof(ExtensionHeader)) {
                return NULL;
            }
            NextHeader = ((ExtensionHeader *) Buffer)->NextHeader;
            Length = ((ExtensionHeader *) Buffer)->HeaderExtLength * 8 + 8;
            break;

        case IP_PROTOCOL_FRAGMENT:
            if (Bytes < sizeof(FragmentHeader)) {
                return NULL;
            }
            NextHeader = ((FragmentHeader *) Buffer)->NextHeader;
            Length = sizeof(FragmentHeader);
            break;
            
        case IP_PROTOCOL_AH:
            if (Bytes < sizeof(AHHeader)) {
                return NULL;
            }
            NextHeader = ((AHHeader *) Buffer)->NextHeader;
            Length = sizeof(AHHeader) +
                ((AHHeader *) Buffer)->PayloadLen * 4 + 8;
            break;

        case IP_PROTOCOL_ICMPv6:
            if (Bytes < sizeof(ICMPv6Header)) {
                return NULL;
            }
            return (ICMPv6Header *) Buffer;
            
        default:
            return NULL;
        }
        
        if (Bytes < Length) {
            return NULL;
        }
        Buffer += Length;
        Bytes -= Length;
    }
}


__inline
VOID
TeredoStart(
    VOID
    )
{
    //
    // Both client and server should not be enabled on the same node.
    //
    ASSERT(!TeredoClientEnabled || !TeredoServerEnabled);

    if (TeredoClientEnabled) {
        //
        // The service might already be running, but that's alright.
        //
        TeredoStartClient();
    }

    if (TeredoServerEnabled) {
        //
        // The service might already be running, but that's alright.
        //
        TeredoStartServer();
    }
}


__inline
VOID
TeredoStop(
    VOID
    )
{
    //
    // Both client and server should not be enabled on the same node.
    //
    ASSERT(!TeredoClientEnabled || !TeredoServerEnabled);

    if (TeredoClientEnabled) {
        //
        // The service might not be running, but that's all right.
        //
        TeredoStopClient();
    }

    if (TeredoServerEnabled) {
        //
        // The service might not be running, but that's all right.
        //
        TeredoStopServer();
    }
}


DWORD
__inline
TeredoEnableWmiEvent(
    IN LPGUID EventGuid,
    IN BOOLEAN Enable
    )
{
    return WmiNotificationRegistrationW(
        EventGuid,                      // Event Type.
        Enable,                         // Enable or Disable.
        TeredoWmiEventNotification,     // Callback.
        0,                              // Context.
        NOTIFICATION_CALLBACK_DIRECT);  // Notification Flags.
}


VOID
__inline
TeredoDeregisterWmiEventNotification(
    VOID
    )
{
    int i;
    
    for (i = 0; i < (sizeof(TeredoWmiEvent) / sizeof(LPGUID)); i++) {
        (VOID) TeredoEnableWmiEvent(TeredoWmiEvent[i], FALSE);
    }
}


DWORD
__inline
TeredoRegisterWmiEventNotification(
    VOID
    )
{
    DWORD Error;
    int i;
    
    for (i = 0; i < (sizeof(TeredoWmiEvent) / sizeof(LPGUID)); i++) {
        Error = TeredoEnableWmiEvent(TeredoWmiEvent[i], TRUE);
        if (Error != NO_ERROR) {
            Trace2(ANY, L"TeredoEnableWmiEvent(%u): Error(%x)", i, Error);
            goto Bail;
        }
    }

    return NO_ERROR;

Bail:
    TeredoDeregisterWmiEventNotification();
    return Error;
}


VOID
CALLBACK
TeredoTimerCallback(
    IN PVOID Parameter,
    IN BOOLEAN TimerOrWaitFired
    )
/*++

Routine Description:

    Callback routine for TeredoTimer expiration.
    The timer is always active.

Arguments:

    Parameter, TimerOrWaitFired - Ignored.

Return Value:

    None.

--*/
{
    ENTER_API();
    TeredoStart();
    LEAVE_API();
}


VOID
CALLBACK
TeredoTimerCleanup(
    IN PVOID Parameter,
    IN BOOLEAN TimerOrWaitFired
    )
/*++

Routine Description:

    Callback routine for TeredoTimer deletion.

    Deletion is performed asynchronously since we acquire a lock in
    the callback function that we hold when deleting the timer.

Arguments:

    Parameter, TimerOrWaitFired - Ignored.

Return Value:

    None.

--*/
{
    UnregisterWait(TeredoTimerEventWait);
    CloseHandle(TeredoTimerEvent);
    DecEventCount("TeredoCleanupTimer");
}


DWORD
TeredoInitializeTimer(
    VOID
    )
/*++

Routine Description:

    Initializes the timer.

Arguments:

    None.

Return Value:

    NO_ERROR or failure code.

--*/
{
    DWORD Error;
    ULONG ResolveInterval;
    
    TeredoTimerEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (TeredoTimerEvent == NULL) {
        Error = GetLastError();
        return Error;
    }

    if (!RegisterWaitForSingleObject(
            &(TeredoTimerEventWait),
            TeredoTimerEvent,
            TeredoTimerCleanup,
            NULL,
            INFINITE,
            0)) {
        Error = GetLastError();
        CloseHandle(TeredoTimerEvent);
        return Error;
    }

    //
    // If the service is enabled, we attempt to start it every
    // TeredoResolveInterval seconds.  Else we disable its timer.
    //
    ResolveInterval = (TEREDO_DEFAULT_TYPE == TEREDO_DISABLE)
        ? INFINITE_INTERVAL
        : (TeredoResolveInterval * 1000);
    if (!CreateTimerQueueTimer(
            &(TeredoTimer),
            NULL,
            TeredoTimerCallback,
            NULL,
            ResolveInterval,
            ResolveInterval,
            0)) { 
        Error = GetLastError();
        UnregisterWait(TeredoTimerEventWait);
        CloseHandle(TeredoTimerEvent);
        return Error;
    }

    IncEventCount("TeredoInitializeTimer");
    return NO_ERROR;
}


VOID
TeredoUninitializeTimer(
    VOID
    )
/*++

Routine Description:

    Uninitializes the timer.  Typically invoked upon service stop.

Arguments:

    None.

Return Value:

    None.

--*/
{
    DeleteTimerQueueTimer(NULL, TeredoTimer, TeredoTimerEvent);
    TeredoTimer = NULL;
}


DWORD
TeredoInitializeGlobals(
    VOID
    )
/*++

Routine Description:

    Initializes the teredo client and server and attempts to start them.

Arguments:

    None.

Return Value:

    NO_ERROR or failure code.

--*/
{
    DWORD Error;
    BOOL ClientInitialized = FALSE;
    BOOL ServerInitialized = FALSE;
    BOOL TimerInitialized = FALSE;
    
    Error = TeredoInitializeClient();
    if (Error != NO_ERROR) {
        goto Bail;
    }
    ClientInitialized = TRUE;    
    
    Error = TeredoInitializeServer();
    if (Error != NO_ERROR) {
        goto Bail;
    }
    ServerInitialized = TRUE;

    Error = TeredoInitializeTimer();
    if (Error != NO_ERROR) {
        goto Bail;
    }
    TimerInitialized = TRUE;

    Error = TeredoRegisterWmiEventNotification();
    if (Error != NO_ERROR) {
        goto Bail;
    }
    
    TeredoStart();

    TeredoInitialized = TRUE;
    
    return NO_ERROR;

Bail:
    //
    // This can always be safely invoked!
    //
    TeredoDeregisterWmiEventNotification();
    
    if (TimerInitialized) {
        TeredoUninitializeTimer();
    }

    if (ServerInitialized) {
        TeredoUninitializeServer();
    }

    if (ClientInitialized) {
        TeredoUninitializeClient();
    }

    return Error;
}


VOID
TeredoUninitializeGlobals(
    VOID
    )
/*++

Routine Description:

    Uninitializes the teredo client and server.

Arguments:

    None.

Return Value:

    None.
    
--*/
{
    if (!TeredoInitialized) {
        return;
    }

    TeredoDeregisterWmiEventNotification();
    TeredoUninitializeTimer();
    TeredoUninitializeServer();
    TeredoUninitializeClient();

    TeredoInitialized = FALSE;
}


VOID
TeredoAddressChangeNotification(
    IN BOOL Delete,
    IN IN_ADDR Address
    )
/*++

Routine Description:

    Process an address deletion or addition request.

Arguments:

    Delete - Supplies a boolean.  TRUE if the address was deleted, FALSE o/w.

    Address - Supplies the IPv4 address that was deleted or added.
    
Return Value:

    None.
    
Caller LOCK: API.

--*/ 
{
    if (Delete) {
        //
        // Both client and server should not be running on the same node.
        //
        ASSERT((TeredoClient.State == TEREDO_STATE_OFFLINE) ||
               (TeredoServer.State == TEREDO_STATE_OFFLINE));

        if (TeredoClient.State != TEREDO_STATE_OFFLINE) {
            TeredoClientAddressDeletionNotification(Address);
        }
        
        if (TeredoServer.State != TEREDO_STATE_OFFLINE) {
            TeredoServerAddressDeletionNotification(Address);
        }

        return;
    }

    //
    // Address addition.
    // Attempt to start the service (if it is not already running).
    //
    TeredoStart();
}


VOID
TeredoConfigurationChangeNotification(
    VOID
    )
/*++

Routine Description:

    Process an configuration change request.

Arguments:

    None.
    
Return Value:

    None.
    
Caller LOCK: API.

--*/ 
{
    HKEY Key = INVALID_HANDLE_VALUE;
    TEREDO_TYPE Type;
    BOOL EnableClient, EnableServer;
    ULONG RefreshInterval, ResolveInterval;
    WCHAR OldServerName[NI_MAXHOST];
    BOOL IoStateChange = FALSE;
    
    (VOID) RegOpenKeyExW(
        HKEY_LOCAL_MACHINE, KEY_TEREDO, 0, KEY_QUERY_VALUE, &Key);
    //
    // Continue despite errors, reverting to default values.
    //
    
    //
    // Get the new configuration parameters.
    //
    RefreshInterval = GetInteger(
        Key, KEY_TEREDO_REFRESH_INTERVAL, TEREDO_REFRESH_INTERVAL);
    if (RefreshInterval == 0) {
        //
        // Invalid value.  Revert to default.
        //
        RefreshInterval = TEREDO_REFRESH_INTERVAL;
    }
    TeredoClientRefreshInterval = RefreshInterval;
    TeredoClientRefreshIntervalChangeNotification();
    
    Type = GetInteger(Key, KEY_TEREDO_TYPE, TEREDO_DEFAULT_TYPE);
    if ((Type == TEREDO_DEFAULT) || (Type >= TEREDO_MAXIMUM)) {
        //
        // Invalid value.  Revert to default.
        //
        Type = TEREDO_DEFAULT_TYPE;
    }
    EnableClient = (Type == TEREDO_CLIENT);
    EnableServer = (Type == TEREDO_SERVER);

    wcscpy(OldServerName, TeredoServerName);
    GetString(
        Key,
        KEY_TEREDO_SERVER_NAME,
        TeredoServerName,
        NI_MAXHOST,
        TEREDO_SERVER_NAME);
    if (_wcsicmp(TeredoServerName, OldServerName) != 0) {
        IoStateChange = TRUE;
    }

    RegCloseKey(Key);
    
    //
    // Both client and server should not be enabled on the same node.
    //
    ASSERT(!TeredoClientEnabled || !TeredoServerEnabled);

    //
    // Stop / Start / Reconfigure.
    //
    if (!EnableClient && TeredoClientEnabled) {
        TeredoClientEnabled = FALSE;
        TeredoStopClient();
    }
    
    if (!EnableServer && TeredoServerEnabled) {
        TeredoServerEnabled = FALSE;
        TeredoStopServer();
    }

    if (EnableClient) {
        if (TeredoClient.State != TEREDO_STATE_OFFLINE) {
            if (IoStateChange) {
                //
                // Refresh I/O state.
                //
                TeredoClientAddressDeletionNotification(
                    TeredoClient.Io.SourceAddress.sin_addr);
            }
        } else {
            TeredoClientEnabled = TRUE;
            TeredoStartClient();
        }
    }
    
    if (EnableServer) {
        if (TeredoServer.State != TEREDO_STATE_OFFLINE) {
            if (IoStateChange) {
                //
                // Refresh I/O state.
                //
                TeredoServerAddressDeletionNotification(
                    TeredoServer.Io.SourceAddress.sin_addr);
            }
        } else {
            TeredoServerEnabled = TRUE;
            TeredoStartServer();
        }
    }

    //
    // If the service is enabled, we attempt to start it every
    // TeredoResolveInterval seconds.  Else we disable its timer.
    //
    ResolveInterval = (Type == TEREDO_DISABLE)
        ? INFINITE_INTERVAL
        : (TeredoResolveInterval * 1000);
    (VOID) ChangeTimerQueueTimer(
        NULL, TeredoTimer, ResolveInterval, ResolveInterval);
}


VOID
WINAPI
TeredoWmiEventNotification(
    IN PWNODE_HEADER Event,
    IN UINT_PTR Context
    )
/*++

Routine Description:

    Process a WMI event (specifically adapter arrival or removal).
    
Arguments:

    Event - Supplies event specific information.

    Context - Supplies the context registered.
    
Return Value:

    None.
    
--*/ 
{
    PWNODE_SINGLE_INSTANCE Instance = (PWNODE_SINGLE_INSTANCE) Event;
    USHORT AdapterNameLength;
    WCHAR AdapterName[MAX_ADAPTER_NAME_LENGTH], *AdapterGuid;
    PTEREDO_IO Io = NULL;

    if (Instance == NULL) {
        return;
    }
    
    ENTER_API();
    
    TraceEnter("TeredoWmiEventNotification");
    
    //
    // WNODE_SINGLE_INSTANCE is organized thus...
    // +-----------------------------------------------------------+
    // |<--- DataBlockOffset --->| AdapterNameLength | AdapterName |
    // +-----------------------------------------------------------+
    //
    // AdapterName is defined as "\DEVICE\"AdapterGuid
    //
    AdapterNameLength =
        *((PUSHORT) (((PUCHAR) Instance) + Instance->DataBlockOffset));
    RtlCopyMemory(
        AdapterName,
        ((PUCHAR) Instance) + Instance->DataBlockOffset + sizeof(USHORT),
        AdapterNameLength);
    AdapterName[AdapterNameLength / sizeof(WCHAR)] = L'\0';
    AdapterGuid = AdapterName + wcslen(DEVICE_PREFIX);        
    Trace1(ANY, L"TeredoAdapter: %s", AdapterGuid);


    if ((memcmp(
            &(Event->Guid),
            &GUID_NDIS_NOTIFY_ADAPTER_ARRIVAL,
            sizeof(GUID)) == 0) ||
        (memcmp(
            &(Event->Guid),
            &GUID_NDIS_NOTIFY_DEVICE_POWER_ON,
            sizeof(GUID)) == 0)) {
        //
        // Adapter arrival (perhaps TUN).
        // Attempt to start the service (if it is not already running).
        //
        Trace0(ANY, L"Adapter Arrival");
        TeredoStart();
    }

    if ((memcmp(
            &(Event->Guid),
            &GUID_NDIS_NOTIFY_ADAPTER_REMOVAL,
            sizeof(GUID)) == 0) ||
        (memcmp(
            &(Event->Guid),
            &GUID_NDIS_NOTIFY_DEVICE_POWER_OFF,
            sizeof(GUID)) == 0)) {        
        if (TeredoClient.State != TEREDO_STATE_OFFLINE) {
            Io = &(TeredoClient.Io);
        }

        if (TeredoServer.State != TEREDO_STATE_OFFLINE) {
            Io = &(TeredoServer.Io);
        }

        if ((Io != NULL) &&
            (_wcsicmp(Io->TunnelInterface, AdapterGuid) == 0)) {
            //
            // TUN adapter removal.
            // Stop the service if it is running.
            //
            Trace0(ANY, L"Adapter Removal");
            TeredoStop();
        }
    }

    LEAVE_API();
}