Private/BfdRuntime.ps1
|
# Session registry, runspace plumbing and event log. The registry lives in AppDomain data so every runspace shares it. function Get-BfdRegistry { $r = [AppDomain]::CurrentDomain.GetData($script:BfdRegistryKey) if (-not $r) { $r = [hashtable]::Synchronized(@{ Bfd = [hashtable]::Synchronized(@{}) }) [AppDomain]::CurrentDomain.SetData($script:BfdRegistryKey, $r) } $r } function Start-BfdRunspace { # Runs Invoke-BfdSessionLoop in its own runspace. The runspace imports the module this code lives in, so the # same code works unchanged when embedded in another module. param([Parameter(Mandatory)]$State) $mod = $ExecutionContext.SessionState.Module $manifest = Join-Path $mod.ModuleBase ($mod.Name + '.psd1') if (-not (Test-Path $manifest)) { $manifest = $mod.Path } $rs = [runspacefactory]::CreateRunspace() $rs.Open() $ps = [powershell]::Create() $ps.Runspace = $rs $null = $ps.AddScript({ param($manifest, $state) $mod = Import-Module $manifest -PassThru & $mod { param($s) Invoke-BfdSessionLoop $s } $state }).AddArgument($manifest).AddArgument($State) $State.PowerShell = $ps $State.Handle = $ps.BeginInvoke() } function Stop-BfdRunspace { param($State, [int]$TimeoutSeconds = 5) $State.Stop = $true if ($State.Handle) { $null = $State.Handle.AsyncWaitHandle.WaitOne($TimeoutSeconds * 1000) } if ($State.PowerShell) { try { $State.PowerShell.Runspace.Dispose(); $State.PowerShell.Dispose() } catch { } } $State.PowerShell = $null; $State.Handle = $null } function Assert-BfdAuth { param([string]$AuthType, [string]$AuthKey) if (-not $AuthType -or $AuthType -eq 'None') { return } if (-not $AuthKey) { throw "-AuthKey is required with -AuthType $AuthType." } $max = $script:BfdAuthTypes[$AuthType].MaxKey if ([Text.Encoding]::UTF8.GetByteCount($AuthKey) -gt $max) { throw "-AuthKey can be at most $max bytes for $AuthType." } } function Wait-BfdReady { param($State, [int]$TimeoutSeconds = 10) $deadline = (Get-Date).AddSeconds($TimeoutSeconds) while (-not $State.Ready -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 20 } } function Add-BfdEvent { param($State, [string]$Type, [string]$Summary, [string]$Direction, [byte[]]$Bytes, $Data) $State.Events.Enqueue([pscustomobject]@{ PSTypeName = 'PowerBFD.BfdEvent' Time = Get-Date Session = $State.Name Type = $Type Direction = $Direction Summary = $Summary Data = $Data Bytes = $Bytes }) $dummy = $null while ($State.Events.Count -gt 500) { $null = $State.Events.TryDequeue([ref]$dummy) } } function Publish-BfdChange { # Drains state changes from the engine context: updates the session, logs events and calls the notification hook. param($S, $Ctx, $Hook) foreach ($ch in @($Ctx.Changes)) { $S.State = $ch.NewState $S.Diagnostic = $ch.Diagnostic $S.LastStateChange = $ch.Time $S.UpSince = if ($ch.NewState -eq 'Up') { $ch.Time } else { $null } $S.History.Enqueue(('{0:HH:mm:ss.fff} {1} -> {2}' -f $ch.Time, $ch.OldState, $ch.NewState)) $dummy = $null while ($S.History.Count -gt 100) { $null = $S.History.TryDequeue([ref]$dummy) } $evt = [pscustomobject]@{ PSTypeName = 'PowerBFD.StateChange' Time = $ch.Time Session = $S.Name Remote = $S.Config.RemoteAddress OldState = $ch.OldState NewState = $ch.NewState Diagnostic = [int]$ch.Diagnostic DiagnosticName = $script:BfdDiags[[int]$ch.Diagnostic] LocalDiscriminator = $Ctx.MyDisc RemoteDiscriminator = $Ctx.YourDisc IsUp = $ch.NewState -eq 'Up' } $note = '{0} -> {1} ({2})' -f $ch.OldState, $ch.NewState, $evt.DiagnosticName Add-BfdEvent -State $S -Type 'StateChange' -Summary $note -Data $evt if ($Hook) { try { & $Hook $evt $S.Config.HookContext } catch { Add-BfdEvent -State $S -Type 'HookError' -Summary $_.Exception.Message } } } $Ctx.Changes.Clear() } |