Private/Runtime.ps1

function New-OspfSocket {
    param([ipaddress]$LocalAddress)
    $s = [System.Net.Sockets.Socket]::new([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Raw, [System.Net.Sockets.ProtocolType]89)
    try {
        $s.Bind([System.Net.IPEndPoint]::new($LocalAddress, 0))
        $ip = [System.Net.Sockets.SocketOptionLevel]::IP
        $s.SetSocketOption($ip, [System.Net.Sockets.SocketOptionName]::MulticastTimeToLive, 1)
        $s.SetSocketOption($ip, [System.Net.Sockets.SocketOptionName]::MulticastLoopback, $false)
        $s.SetSocketOption($ip, [System.Net.Sockets.SocketOptionName]::MulticastInterface, $LocalAddress.GetAddressBytes())
        $s.Ttl = 1
        foreach ($g in $script:AllSpfRouters, $script:AllDRouters) {
            $s.SetSocketOption($ip, [System.Net.Sockets.SocketOptionName]::AddMembership, [System.Net.Sockets.MulticastOption]::new($g, $LocalAddress))
        }
        try { $s.SetSocketOption($ip, [System.Net.Sockets.SocketOptionName]::TypeOfService, 0xC0) } catch { }
    }
    catch { $s.Dispose(); throw }
    $s
}

function Send-OspfOutbox {
    param($Session)
    $item = $null
    while ($Session.Outbox.TryDequeue([ref]$item)) {
        if ($Session.Socket) {
            try { $null = $Session.Socket.SendTo([byte[]]$item.Bytes, [System.Net.IPEndPoint]::new($item.Destination, 0)) }
            catch { Invoke-OspfLock $Session { Add-OspfEvent -Session $Session -Direction 'Info' -Type 'SocketError' -Message "Send failed: $($_.Exception.Message)" } }
        }
    }
}

function Invoke-OspfRuntime {
    # Runs inside a background runspace: receives packets, drives timers, drains the outbox.
    param($Session)
    $buf = [byte[]]::new(65535)
    while (-not $Session.Cancel) {
        try {
            if ($Session.Socket.Poll(50000, [System.Net.Sockets.SelectMode]::SelectRead)) {
                $ep = [System.Net.EndPoint][System.Net.IPEndPoint]::new([ipaddress]::Any, 0)
                $n = $Session.Socket.ReceiveFrom($buf, [ref]$ep)
                $data = [byte[]]$buf[0..($n - 1)]
                $src = $ep.Address
                Invoke-OspfLock $Session { Invoke-OspfReceive -Session $Session -Data $data -Source $src -HasIpHeader }
            }
            Invoke-OspfLock $Session { Invoke-OspfTick -Session $Session }
        }
        catch {
            Invoke-OspfLock $Session { Add-OspfEvent -Session $Session -Direction 'Info' -Type 'Error' -Message $_.Exception.Message }
            Start-Sleep -Milliseconds 100
        }
        Send-OspfOutbox $Session
    }
    Send-OspfOutbox $Session
}

function Start-OspfRuntime {
    param($Session, [switch]$NoNetwork)
    $now = [datetime]::UtcNow
    Invoke-OspfLock $Session {
        $Session.Status = 'Running'
        $Session.Cancel = $false
        $Session.Neighbors = @{}
        $Session.Dr = [long]0
        $Session.Bdr = [long]0
        $Session.NextHello = $now
        if ($Session.NetworkType -eq 'PointToPoint') { $Session.InterfaceState = 'PointToPoint' }
        elseif ($Session.Priority -eq 0) { $Session.InterfaceState = 'DROther' }
        else { $Session.InterfaceState = 'Waiting'; $Session.WaitDeadline = $now.AddSeconds($Session.DeadInterval) }
        Add-OspfEvent -Session $Session -Direction 'Info' -Type 'Session' -Message "Started: $($Session.InterfaceState)"
        Update-OspfOwnLsas -Session $Session -Now $now -Force
    }
    if ($NoNetwork) { return }
    try { $Session.Socket = New-OspfSocket -LocalAddress $Session.LocalAddress }
    catch {
        $Session.Status = 'Stopped'
        $Session.InterfaceState = 'Down'
        throw "Cannot open a raw IP protocol 89 socket on $($Session.LocalAddress): $($_.Exception.Message) Raw sockets need elevation (Administrator on Windows; root or CAP_NET_RAW on Linux), and the address must exist on this host."
    }
    $rs = [runspacefactory]::CreateRunspace()
    $rs.Open()
    $ps = [powershell]::Create()
    $ps.Runspace = $rs
    $null = $ps.AddScript({
            param($manifest, $state)
            Import-Module $manifest -Force
            & (Get-Module PowerOSPF) { param($s) Invoke-OspfRuntime -Session $s } $state
        }).AddArgument($script:ModuleManifest).AddArgument($Session)
    $Session.Runspace = $rs
    $Session.PowerShell = $ps
    $Session.Handle = $ps.BeginInvoke()
}

function Stop-OspfRuntime {
    param($Session, [switch]$Silent)
    Invoke-OspfLock $Session {
        if ($Session.Status -eq 'Running' -and -not $Silent) { Send-OspfPacket -Session $Session -Type 1 -Destination $script:AllSpfRouters -Info 'goodbye' `
                -Body (Get-OspfHelloBody -Mask $Session.Mask -HelloInterval $Session.HelloInterval -Options $Session.Options -Priority $Session.Priority -DeadInterval $Session.DeadInterval -Dr 0 -Bdr 0) }
    }
    $Session.Cancel = $true
    if ($Session.PowerShell) {
        try { $null = $Session.Handle.AsyncWaitHandle.WaitOne(3000); $Session.PowerShell.Stop() } catch { }
        $Session.PowerShell.Dispose()
        $Session.Runspace.Dispose()
    }
    elseif ($Session.Socket) { Send-OspfOutbox $Session }
    if ($Session.Socket) { $Session.Socket.Dispose() }
    $Session.PowerShell = $null; $Session.Runspace = $null; $Session.Handle = $null; $Session.Socket = $null
    Invoke-OspfLock $Session {
        $Session.Status = 'Stopped'
        $Session.InterfaceState = 'Down'
        $Session.Neighbors = @{}
        $Session.Dr = [long]0
        $Session.Bdr = [long]0
        $Session.Lsdb = @{}
        $Session.Originated = @{}
        Add-OspfEvent -Session $Session -Direction 'Info' -Type 'Session' -Message 'Stopped'
    }
}

function Get-OspfSessionState {
    param([string]$Name, [switch]$All)
    if ($Name) {
        if (-not $script:OspfSessions.ContainsKey($Name)) { throw "No OSPF session named '$Name'" }
        return $script:OspfSessions[$Name]
    }
    @($script:OspfSessions.Values)
}