29 Commits

Author SHA1 Message Date
0c7f736d73 Update src/WinBGP-API.ps1 2025-11-07 22:14:39 +01:00
Alexandre JARDON
c1dbeeea1c Add service signing 2025-03-15 11:31:17 +01:00
Alexandre JARDON
1e221eafe7 Review build 2025-03-15 09:51:17 +01:00
Alexandre JARDON
ca7224391d Review build 2025-03-15 09:49:43 +01:00
Alexandre JARDON
25ed7571a1 Review build 2025-03-15 09:48:11 +01:00
Alexandre JARDON
9f36ca794e Review build 2025-03-15 09:43:53 +01:00
Alexandre JARDON
ea4006b290 Review build 2025-03-15 09:42:58 +01:00
Alexandre JARDON
cb99fc2470 Review build 2025-03-15 09:40:18 +01:00
Alexandre JARDON
76c9ed376c Review build 2025-03-15 09:38:48 +01:00
Alexandre JARDON
e01fc6c7c9 Review build 2025-03-15 09:37:21 +01:00
Alexandre JARDON
30a3360f72 Review build 2025-03-15 09:35:10 +01:00
Alexandre JARDON
1fb7093032 Review build 2025-03-15 09:32:57 +01:00
Alexandre JARDON
00e27a77de Review build 2025-03-15 09:28:11 +01:00
Alexandre JARDON
b4b06f361e Review build 2025-03-15 09:26:26 +01:00
Alexandre JARDON
f682432188 Review build 2025-03-15 09:24:43 +01:00
Alexandre JARDON
838a206ed3 Review build 2025-03-15 09:20:40 +01:00
Alexandre JARDON
cfcd5550f2 Review build 2025-03-14 15:59:09 +01:00
Alexandre JARDON
2e01af7957 Merge pull request #4 from webalexeu/fix/api_gracefull_stop
Improve jobs management
2025-01-13 15:22:03 +01:00
WebalexEU
1c72182a73 Typo 2025-01-13 15:20:03 +01:00
WebalexEU
74f15debbc Improve jobs management 2025-01-13 14:37:43 +01:00
Alexandre JARDON
01384741c2 Merge pull request #3 from webalexeu/fix/engine_name
Fix process name
2025-01-07 21:14:13 +01:00
WebalexEU
54964e71c2 Fix process name 2025-01-07 21:12:54 +01:00
Alexandre JARDON
5f438bfb03 Merge pull request #2 from webalexeu/fix/process_name
Fix internal process name
2025-01-06 21:58:30 +01:00
WebalexEU
bd14f39105 Fix process name 2025-01-06 21:55:04 +01:00
Alexandre JARDON
1fa3e07920 Update CHANGELOG.md 2024-12-30 23:03:33 +01:00
Alexandre JARDON
75cc5c22d0 Update LICENCE 2024-12-28 10:53:32 +01:00
Alexandre JARDON
9a7fa4a219 Update LICENCE 2024-12-28 10:53:03 +01:00
WebalexEU
e9ec652205 Add condition for windows server 2024-12-20 22:55:55 +01:00
WebalexEU
52731ed50a Fix manufacturer 2024-12-20 20:51:17 +01:00
10 changed files with 526 additions and 231 deletions

7
.gitignore vendored
View File

@@ -1,6 +1,13 @@
# Service builder executable outputs # Service builder executable outputs
service/*.exe service/*.exe
# Signed code
engine/*.*
# Installer outputs # Installer outputs
builder/*.msi builder/*.msi
builder/*.wixpdb builder/*.wixpdb
# MSI Release
release/*.msi

View File

@@ -2,6 +2,56 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## Release 1.1.4 (2025-01-13)
[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.3...v1.1.4)
**Features**
- Improve API graceful shutdown
- Add control on HealtChecks jobs through CLI
**Bugfixes**
## Release 1.1.3 (2025-01-07)
[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.2...v1.1.3)
**Features**
- Add service start if feature is installed and routing enabled
**Bugfixes**
- [API restart cause failure of the engine](https://github.com/webalexeu/winbgp/issues/1)
## Release 1.1.2 (2025-01-06)
[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.1...v1.1.2)
**Features**
- Sign API/Engine/HealthCheck/CLI binaries
- Force process to exit when a failure occurs
**Bugfixes**
**Known Issues**
- API restart cause failure of the engine
## Release 1.1.1 (2024-12-30)
[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.0...v1.1.1)
**Features**
- Sign MSI installer
**Bugfixes**
**Known Issues**
## Release 1.1.0 (2024-12-20) ## Release 1.1.0 (2024-12-20)
**Features** **Features**

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright {yyyy} {name of copyright owner} Copyright 2024 Alexandre JARDON (WebalexEU)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -4,7 +4,11 @@ Param (
[String] $Version, [String] $Version,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[ValidateSet("amd64", "arm64")] [ValidateSet("amd64", "arm64")]
[String] $Arch = "amd64" [String] $Arch = "amd64",
[Parameter(ParameterSetName='Signing', Mandatory = $false)]
[Switch] $Sign = $false,
[Parameter(ParameterSetName='Signing', Mandatory = $false)]
[String] $CertificateThumbprint
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -17,12 +21,43 @@ Trap {
# Reset working dir on error # Reset working dir on error
Pop-Location Pop-Location
} }
# If signing, get the certificate
if ($Sign) {
$cert=Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Where-Object { $_.Thumbprint -eq $CertificateThumbprint }
}
# Building service executable
Write-Output "Building WinBGP service"
& "..\service\WinBGP-Service.ps1" -Build
Move-Item -Path "..\builder\WinBGP-Service.exe" -Destination "..\service\WinBGP-Service.exe" -Force
if ($Sign) {
& "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" sign /sha1 $CertificateThumbprint /tr http://time.certum.pl/ /td sha256 /fd sha256 /v "..\service\WinBGP-Service.exe"
}
Write-Verbose "Creating winbgp-${Version}-${Arch}.msi" # Building engine
Write-Output "Building engine"
New-Item -ItemType Directory -Path "..\engine" -Force | Out-Null
Get-ChildItem -Path '..\src' | Where-Object {$_.Extension -eq '.ps1'} | ForEach-Object {
Copy-Item -Path $_.FullName -Destination "..\engine" -Force
if ($Sign) {
Set-AuthenticodeSignature -FilePath "..\engine\$($_.Name)" -TimestampServer 'http://time.certum.pl' -Certificate $cert
}
}
Write-Output "Building winbgp-${Version}-${Arch}.msi"
$wixArch = @{"amd64" = "x64"; "arm64" = "arm64"}[$Arch] $wixArch = @{"amd64" = "x64"; "arm64" = "arm64"}[$Arch]
Invoke-Expression "wix build -arch $wixArch -o .\WinBGP-$($Version)-$($Arch).msi .\files.wxs .\main.wxs -d ProductName=WinBGP -d Version=$($MsiVersion) -ext WixToolset.Firewall.wixext -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext" Invoke-Expression "wix build -arch $wixArch -o .\WinBGP-$($Version)-$($Arch).msi .\files.wxs .\main.wxs -d ProductName=WinBGP -d Version=$($MsiVersion) -ext WixToolset.Firewall.wixext -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext"
Write-Verbose "Done!" Write-Output "Build complete !"
Pop-Location
# Clean temporary build folder
Remove-Item -Path "..\engine\*"
Write-Output "Release build"
New-Item -ItemType Directory -Path "..\release" -Force | Out-Null
Copy-Item -Path "WinBGP-$($Version)-$($Arch).msi" -Destination "..\release" -Force
if ($Sign) {
& "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" sign /sha1 $CertificateThumbprint /tr http://time.certum.pl/ /td sha256 /fd sha256 /v "..\release\WinBGP-$($Version)-$($Arch).msi"
}

View File

@@ -20,7 +20,7 @@
<Component Transitive="yes"> <Component Transitive="yes">
<File Id="winbgp_service" Source="..\service\WinBGP-Service.exe" KeyPath="yes" Vital="yes" Checksum="yes" /> <File Id="winbgp_service" Source="..\service\WinBGP-Service.exe" KeyPath="yes" Vital="yes" Checksum="yes" />
<ServiceInstall <ServiceInstall
Id="InstallExporterService" Id="InstallWinBGPService"
Name="WinBGP" Name="WinBGP"
DisplayName="WinBGP Engine" DisplayName="WinBGP Engine"
Description="The BGP swiss army knife of networking on Windows" Description="The BGP swiss army knife of networking on Windows"
@@ -39,10 +39,10 @@
<ServiceDependency Id="RemoteAccess" /> <ServiceDependency Id="RemoteAccess" />
</ServiceInstall> </ServiceInstall>
<ServiceControl <ServiceControl
Id="MyServiceControl" Id="WinBGPServiceControl"
Name="WinBGP" Name="WinBGP"
Stop="both"
Remove="both" Remove="both"
Stop="both"
Wait="yes" /> Wait="yes" />
</Component> </Component>
</DirectoryRef> </DirectoryRef>

View File

@@ -29,8 +29,8 @@
xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns="http://wixtoolset.org/schemas/v4/wxs"
> >
<Package UpgradeCode="0889d60f-67d5-4bf5-8918-2d5e810f888d" Name="$(var.ProductName)" Version="$(var.Version)" <Package UpgradeCode="0889d60f-67d5-4bf5-8918-2d5e810f888d" Name="$(var.ProductName)" Version="$(var.Version)"
Manufacturer="Webalex System" Language="1033" Scope="perMachine"> Manufacturer="WebalexEU" Language="1033" Scope="perMachine">
<SummaryInformation Manufacturer="Webalex System" Description="$(var.ProductName) $(var.Version) installer" /> <SummaryInformation Manufacturer="WebalexEU" Description="$(var.ProductName) $(var.Version) installer" />
<CustomAction Id="GenerateConfig" <CustomAction Id="GenerateConfig"
Execute="deferred" Execute="deferred"
@@ -74,6 +74,14 @@
ExeCommand="powershell.exe -ExecutionPolicy Bypass -Command &quot;if ((Get-RemoteAccess).RoutingStatus -ne 'Installed') { Write-Host 'Enabling routing (WinBGP prerequisite)'; Install-RemoteAccess -VpnType RoutingOnly }&quot;" ExeCommand="powershell.exe -ExecutionPolicy Bypass -Command &quot;if ((Get-RemoteAccess).RoutingStatus -ne 'Installed') { Write-Host 'Enabling routing (WinBGP prerequisite)'; Install-RemoteAccess -VpnType RoutingOnly }&quot;"
Return="check" Return="check"
/> />
<CustomAction Id="ServiceStart"
Execute="immediate"
Impersonate="no"
Directory="TARGETDIR"
ExeCommand="powershell.exe -ExecutionPolicy Bypass -Command &quot;if ((Get-RemoteAccess).RoutingStatus -eq 'Installed') { Write-Host 'Starting WinBGP service'; Start-Service -Name WinBGP -ErrorAction SilentlyContinue }&quot;"
Return="ignore"
/>
<CustomAction Id="RemoveConfig" <CustomAction Id="RemoveConfig"
Execute="deferred" Execute="deferred"
@@ -88,10 +96,11 @@
<InstallExecuteSequence> <InstallExecuteSequence>
<Custom Action="GenerateConfig" After="InstallFiles" Condition="NOT Installed AND NOT WIX_UPGRADE_DETECTED AND NOT WIX_DOWNGRADE_DETECTED" /> <Custom Action="GenerateConfig" After="InstallFiles" Condition="NOT Installed AND NOT WIX_UPGRADE_DETECTED AND NOT WIX_DOWNGRADE_DETECTED" />
<Custom Action="EnableWindowsFeature" After="InstallFiles" Condition="&amp;Features=3 AND NOT Installed" /> <Custom Action="EnableWindowsFeature" After="InstallFiles" Condition="&amp;Features=3 AND NOT Installed AND (MsiNTProductType=2 OR MsiNTProductType=3)" />
<Custom Action="EnableWindowsFeatureRSAT" After="EnableWindowsFeature" Condition="&amp;Features=3 AND NOT Installed" /> <Custom Action="EnableWindowsFeatureRSAT" After="EnableWindowsFeature" Condition="&amp;Features=3 AND NOT Installed AND (MsiNTProductType=2 OR MsiNTProductType=3)" />
<Custom Action="EnableRouting" After="EnableWindowsFeatureRSAT" Condition="&amp;Features=3 AND NOT Installed" /> <!-- TO CHECK to use Condition="MAINTENANCE" for upgrade --> <Custom Action="EnableRouting" After="EnableWindowsFeatureRSAT" Condition="&amp;Features=3 AND NOT Installed AND (MsiNTProductType=2 OR MsiNTProductType=3)" />
<ScheduleReboot After="InstallFinalize" Condition="&amp;Features=3 AND NOT Installed" /> <!-- TO CHECK <Condition>REBOOT_REQUIRED</Condition> --> <Custom Action="ServiceStart" After="InstallFinalize" Condition='NOT REMOVE="ALL" AND (MsiNTProductType=2 OR MsiNTProductType=3)' />
<ScheduleReboot After="InstallFinalize" Condition="&amp;Features=3 AND NOT Installed AND NOT (WIX_UPGRADE_DETECTED OR UPGRADINGPRODUCTCODE) AND (MsiNTProductType=2 OR MsiNTProductType=3)" /> <!-- TO CHECK <Condition>REBOOT_REQUIRED</Condition> -->
<Custom Action='RemoveConfig' Before='RemoveFiles' Condition='Installed AND (REMOVE="ALL") AND NOT (WIX_UPGRADE_DETECTED OR UPGRADINGPRODUCTCODE)' /> <Custom Action='RemoveConfig' Before='RemoveFiles' Condition='Installed AND (REMOVE="ALL") AND NOT (WIX_UPGRADE_DETECTED OR UPGRADINGPRODUCTCODE)' />
</InstallExecuteSequence> </InstallExecuteSequence>
@@ -127,7 +136,7 @@
Id="Features" Id="Features"
Level="1" Level="1"
Title="Routing features" Title="Routing features"
Description="Enable required windows features for routing capabilities" Description="Enable required windows features for routing capabilities (Only available on Windows Server)"
Display="expand" Display="expand"
AllowAdvertise="no" AllowAdvertise="no"
InstallDefault="local" InstallDefault="local"
@@ -167,13 +176,13 @@
<StandardDirectory Id="ProgramFiles64Folder"> <StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="APPLICATIONFOLDER" Name="WinBGP"> <Directory Id="APPLICATIONFOLDER" Name="WinBGP">
<Component Id="winbgp_engine" Guid="a7b5748a-16c9-4594-85ad-de032fb3f39c"> <Component Id="winbgp_engine" Guid="a7b5748a-16c9-4594-85ad-de032fb3f39c">
<File Id="winbgp_engine" Source="..\src\WinBGP-Engine.ps1" KeyPath="yes" Vital="yes" Checksum="yes" /> <File Id="winbgp_engine" Source="..\engine\WinBGP-Engine.ps1" KeyPath="yes" Vital="yes" Checksum="yes" />
</Component> </Component>
<Component Id="winbgp_healthcheck" Guid="8a356315-8307-4508-b19d-00eb05e59428"> <Component Id="winbgp_healthcheck" Guid="8a356315-8307-4508-b19d-00eb05e59428">
<File Id="winbgp_healthcheck" Source="..\src\WinBGP-HealthCheck.ps1" KeyPath="yes" Vital="yes" Checksum="yes" /> <File Id="winbgp_healthcheck" Source="..\engine\WinBGP-HealthCheck.ps1" KeyPath="yes" Vital="yes" Checksum="yes" />
</Component> </Component>
<Component Id="winbgp_api" Guid="77de0ab1-0e53-4967-9469-142f49b397a4"> <Component Id="winbgp_api" Guid="77de0ab1-0e53-4967-9469-142f49b397a4">
<File Id="winbgp_api" Source="..\src\WinBGP-API.ps1" KeyPath="yes" Vital="yes" Checksum="yes" /> <File Id="winbgp_api" Source="..\engine\WinBGP-API.ps1" KeyPath="yes" Vital="yes" Checksum="yes" />
</Component> </Component>
<Component Id="winbgp_config" Guid="03063c39-1063-4126-b974-5cad888d5804" NeverOverwrite="yes" Permanent="yes" > <Component Id="winbgp_config" Guid="03063c39-1063-4126-b974-5cad888d5804" NeverOverwrite="yes" Permanent="yes" >
<File Id="winbgp_config" Name="winbgp.json" Source="..\src\winbgp.json.example" KeyPath="yes" Checksum="yes" /> <File Id="winbgp_config" Name="winbgp.json" Source="..\src\winbgp.json.example" KeyPath="yes" Checksum="yes" />
@@ -182,7 +191,7 @@
<File Id="winbgp_config_example" Source="..\src\winbgp.json.example" KeyPath="yes" Checksum="yes" /> <File Id="winbgp_config_example" Source="..\src\winbgp.json.example" KeyPath="yes" Checksum="yes" />
</Component> </Component>
<Component Id="winbgp_cli" Guid="b74d4be4-3de4-4006-a271-b56d078d2bcc"> <Component Id="winbgp_cli" Guid="b74d4be4-3de4-4006-a271-b56d078d2bcc">
<File Id="winbgp_cli" Source="..\src\WinBGP.ps1" KeyPath="yes" Vital="yes" Checksum="yes" /> <File Id="winbgp_cli" Source="..\engine\WinBGP.ps1" KeyPath="yes" Vital="yes" Checksum="yes" />
</Component> </Component>
</Directory> </Directory>
</StandardDirectory> </StandardDirectory>

View File

@@ -897,10 +897,10 @@ if ($Build) { # Install the service
# Generate the service .EXE from the C# source embedded in this script # Generate the service .EXE from the C# source embedded in this script
# Overwrite for builder # Overwrite for builder
$exeFullName=".\$exeName" $exeFullName=$exeName
try { try {
Write-Verbose "Compiling $exeFullName" Write-Output "Compiling $exeFullName"
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $exeFullName -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly $exeFullName -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false
} catch { } catch {
$msg = $_.Exception.Message $msg = $_.Exception.Message

View File

@@ -23,7 +23,7 @@ Param (
$Configuration=$false $Configuration=$false
) )
$scriptVersion = '1.1.1' $scriptVersion = '1.1.2'
# Create detailled log for WinBGP-API # Create detailled log for WinBGP-API
# New-EventLog LogName Application Source 'WinBGP-API' -ErrorAction SilentlyContinue # New-EventLog LogName Application Source 'WinBGP-API' -ErrorAction SilentlyContinue
@@ -316,6 +316,10 @@ if ($Configuration) {
} }
# Starting listerner # Starting listerner
$listener.Start() $listener.Start()
# Flag to control the loop
$keepListening = $true
# Output listeners # Output listeners
foreach ($ListenerPrefixe in $ListenerPrefixes) { foreach ($ListenerPrefixe in $ListenerPrefixes) {
[String]$Protocol = $ListenerPrefixe.Split('://')[0] [String]$Protocol = $ListenerPrefixe.Split('://')[0]
@@ -324,11 +328,12 @@ if ($Configuration) {
Write-Log -Message "API started - Listening on '$($IP):$($Port)' (Protocol: $Protocol)" Write-Log -Message "API started - Listening on '$($IP):$($Port)' (Protocol: $Protocol)"
} }
while ($listener.IsListening) { while (($listener.IsListening) -and ($keepListening)) {
# Default return # Default return
$statusCode = [System.Net.HttpStatusCode]::OK $statusCode = [System.Net.HttpStatusCode]::OK
$commandOutput = [string]::Empty $commandOutput = [string]::Empty
$outputHeader = @{} $outputHeader = @{}
# Accept incoming request
$context = $listener.GetContext() $context = $listener.GetContext()
$request = $context.Request $request = $context.Request
[string]$RequestHost=$request.RemoteEndPoint [string]$RequestHost=$request.RemoteEndPoint
@@ -358,185 +363,282 @@ if ($Configuration) {
# Write-Log "API request received: $FullRequest" -EventLogSource 'WinBGP-API' # Write-Log "API request received: $FullRequest" -EventLogSource 'WinBGP-API'
$FullPath=($request.RawUrl).substring(1) $FullPath=($request.RawUrl).substring(1)
$Path=$FullPath.Split('?')[0] $Path=$FullPath.Split('?')[0]
switch ($request.HttpMethod) { switch -wildcard ($FullPath) {
'GET' { 'api*' {
if ($FullPath -eq 'api') { switch ($request.HttpMethod) {
$commandOutput = ConvertTo-Json -InputObject @{'message'='WinBGP API running'} 'GET' {
$statusCode = [System.Net.HttpStatusCode]::OK if ($FullPath -eq 'api') {
} elseif ($FullPath -like 'api/*') { $commandOutput = ConvertTo-Json -InputObject @{'message'='WinBGP API running'}
$Path=$Path.replace('api/','') $statusCode = [System.Net.HttpStatusCode]::OK
$shortPath=$Path.Split('/')[0] } elseif ($FullPath -like 'api/*') {
switch ($shortPath) { $Path=$Path.replace('api/','')
'config' { $shortPath=$Path.Split('/')[0]
if (($Path -eq 'config') -or ($Path -eq 'config/')) { switch ($shortPath) {
$commandOutput = WinBGP -Config | ConvertTo-JSON 'config' {
$outputHeader.Add('Content-Type', 'application/json') if (($Path -eq 'config') -or ($Path -eq 'config/')) {
$statusCode = [System.Net.HttpStatusCode]::OK $commandOutput = WinBGP -Config | ConvertTo-JSON
} else { $outputHeader.Add('Content-Type', 'application/json')
$SubConfig=$Path.Split('/')[1] $statusCode = [System.Net.HttpStatusCode]::OK
$commandOutput = (WinBGP -Config).$SubConfig } else {
if ($commandOutput) { $SubConfig=$Path.Split('/')[1]
$commandOutput = $commandOutput | ConvertTo-JSON $commandOutput = (WinBGP -Config).$SubConfig
if ($commandOutput) {
$commandOutput = $commandOutput | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::NotFound
}
}
}
'logs' {
$Last = $request.QueryString.Item("Last")
if (!($Last)) { $Last = 10 }
$commandOutput = WinBGP -Logs -Last $Last | Select-Object Index,TimeGenerated,@{Label='EntryType';Expression={($_.EntryType).ToString()}},Message,RouteName | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json') $outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK $statusCode = [System.Net.HttpStatusCode]::OK
} else { }
$statusCode = [System.Net.HttpStatusCode]::NotFound 'peers' {
if (($Path -eq 'peers') -or ($Path -eq 'peers/')) {
$commandOutput = ConvertTo-Json -InputObject @(Get-BgpPeer | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}) #Using @() as inputobject to always return an array
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$PeerName=$Path.Split('/')[1]
$commandOutput = Get-BgpPeer | Where-Object {$_.PeerName -eq $PeerName} | Select-Object PeerName,LocalIPAddress,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}
if ($commandOutput) {
$commandOutput = $commandOutput | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::NotFound
}
}
}
'router' {
$commandOutput = Get-BgpRouter | Select-Object BgpIdentifier,LocalASN,PeerName,PolicyName | ConvertTo-JSON
$statusCode = [System.Net.HttpStatusCode]::OK
}
'routes' {
if (($Path -eq 'routes') -or ($Path -eq 'routes/')) {
$commandOutput = ConvertTo-Json -InputObject @(WinBGP | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}}) #Using @() as inputobject to always return an array
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$RouteName=$Path.Split('/')[1]
$commandOutput = WinBGP | Where-Object {$_.Name -eq $RouteName} | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}}
if ($commandOutput) {
$commandOutput = $commandOutput | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::NotFound
}
}
}
'statistics' {
$commandOutput=(Invoke-CimMethod -ClassName "PS_BgpStatistics" -Namespace 'ROOT\Microsoft\Windows\RemoteAccess' -MethodName Get -OperationTimeoutSec 5).cmdletoutput | Select-Object PeerName,TcpConnectionEstablished,TcpConnectionClosed,@{Label='OpenMessage';Expression={$_.OpenMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='NotificationMessage';Expression={$_.NotificationMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='KeepAliveMessage';Expression={$_.KeepAliveMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='RouteRefreshMessage';Expression={$_.RouteRefreshMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='UpdateMessage';Expression={$_.UpdateMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='IPv4Route';Expression={$_.IPv4Route.CimInstanceProperties | Select-Object Name,Value}},@{Label='IPv6Route';Expression={$_.IPv6Route.CimInstanceProperties | Select-Object Name,Value}} | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
'status' {
[string]$status = WinBGP -Status
$commandOutput = ConvertTo-Json -InputObject @{'service'=$status}
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
'version' {
$commandOutput = WinBGP -Version | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} }
} }
'logs' { }
$Last = $request.QueryString.Item("Last") 'POST' {
if (!($Last)) { $Last = 10 } $RouteName = $request.QueryString.Item("RouteName")
$commandOutput = WinBGP -Logs -Last $Last | Select-Object Index,TimeGenerated,@{Label='EntryType';Expression={($_.EntryType).ToString()}},Message,RouteName | ConvertTo-JSON $Path=$Path.replace('api/','')
$outputHeader.Add('Content-Type', 'application/json') Write-Log "API received POST request '$Path' from '$RequestUser' - Source IP: '$RequestHost'" -AdditionalFields $RouteName
$statusCode = [System.Net.HttpStatusCode]::OK switch ($Path) {
} 'Reload' {
'peers' { [string]$ActionOutput=WinBGP -Reload
if (($Path -eq 'peers') -or ($Path -eq 'peers/')) { $commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
$commandOutput = ConvertTo-Json -InputObject @(Get-BgpPeer | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}) #Using @() as inputobject to always return an array
$outputHeader.Add('Content-Type', 'application/json') $outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK }
} else { 'StartMaintenance' {
$PeerName=$Path.Split('/')[1] [string]$ActionOutput=WinBGP -RouteName "$RouteName" -StartMaintenance
$commandOutput = Get-BgpPeer | Where-Object {$_.PeerName -eq $PeerName} | Select-Object PeerName,LocalIPAddress,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}} $commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
if ($commandOutput) { $outputHeader.Add('Content-Type', 'application/json')
$commandOutput = $commandOutput | ConvertTo-JSON }
$outputHeader.Add('Content-Type', 'application/json') 'StartRoute' {
$statusCode = [System.Net.HttpStatusCode]::OK [string]$ActionOutput=WinBGP -RouteName "$RouteName" -StartRoute
} else { $commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
$statusCode = [System.Net.HttpStatusCode]::NotFound $outputHeader.Add('Content-Type', 'application/json')
} }
'StopMaintenance' {
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StopMaintenance
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
$outputHeader.Add('Content-Type', 'application/json')
}
'StopRoute' {
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StopRoute
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
$outputHeader.Add('Content-Type', 'application/json')
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} }
'router' { switch ($commandOutput.output) {
$commandOutput = Get-BgpRouter | Select-Object BgpIdentifier,LocalASN,PeerName,PolicyName | ConvertTo-JSON 'Success' { $statusCode = [System.Net.HttpStatusCode]::OK }
$statusCode = [System.Net.HttpStatusCode]::OK 'WinBGP not ready' { $statusCode = [System.Net.HttpStatusCode]::InternalServerError }
}
'routes' {
if (($Path -eq 'routes') -or ($Path -eq 'routes/')) {
$commandOutput = ConvertTo-Json -InputObject @(WinBGP | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}}) #Using @() as inputobject to always return an array
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$RouteName=$Path.Split('/')[1]
$commandOutput = WinBGP | Where-Object {$_.Name -eq $RouteName} | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}}
if ($commandOutput) {
$commandOutput = $commandOutput | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::NotFound
}
}
}
'statistics' {
$commandOutput=(Invoke-CimMethod -ClassName "PS_BgpStatistics" -Namespace 'ROOT\Microsoft\Windows\RemoteAccess' -MethodName Get -OperationTimeoutSec 5).cmdletoutput | Select-Object PeerName,TcpConnectionEstablished,TcpConnectionClosed,@{Label='OpenMessage';Expression={$_.OpenMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='NotificationMessage';Expression={$_.NotificationMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='KeepAliveMessage';Expression={$_.KeepAliveMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='RouteRefreshMessage';Expression={$_.RouteRefreshMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='UpdateMessage';Expression={$_.UpdateMessage.CimInstanceProperties | Select-Object Name,Value}},@{Label='IPv4Route';Expression={$_.IPv4Route.CimInstanceProperties | Select-Object Name,Value}},@{Label='IPv6Route';Expression={$_.IPv6Route.CimInstanceProperties | Select-Object Name,Value}} | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
'status' {
[string]$status = WinBGP -Status
$commandOutput = ConvertTo-Json -InputObject @{'service'=$status}
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
'version' {
$commandOutput = WinBGP -Version | ConvertTo-JSON
$outputHeader.Add('Content-Type', 'application/json')
$statusCode = [System.Net.HttpStatusCode]::OK
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} }
} elseif ($FullPath -eq 'metrics') { Default {
# Define WinBGP Prometheus metrics $statusCode = [System.Net.HttpStatusCode]::NotImplemented
$WinBGP_metrics=@()
# WinBGP peer status
$state_peerDescriptor=New-PrometheusMetricDescriptor -Name winbgp_state_peer -Type gauge -Help 'WinBGP Peers status' -Labels local_asn,local_ip,name,peer_asn,peer_ip,state
$peerStatus=@('connected','connecting','stopped')
# Try/catch to detect if BGP is configured properly
$BgpStatus=$null
try {
$peersCurrentStatus=Get-BgpPeer -ErrorAction SilentlyContinue | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}
} }
catch {
#If BGP Router (Local) is not configured, catch it
$BgpStatus=($_).ToString()
}
if ($BgpStatus -eq 'BGP is not configured.') {
$peersCurrentStatus=$null
}
# Parse all peers and generate metric
foreach ($peerCurrentStatus in $peersCurrentStatus) {
foreach ($status in $peerStatus) {
$WinBGP_metrics+=New-PrometheusMetric -PrometheusMetricDescriptor $state_peerDescriptor -Value $(if ($status -eq $peerCurrentStatus.ConnectivityStatus) { 1 } else { 0 }) -Labels $peerCurrentStatus.LocalASN,$peerCurrentStatus.LocalIPAddress,$peerCurrentStatus.PeerName,$peerCurrentStatus.PeerASN,$peerCurrentStatus.PeerIPAddress,$status
}
}
# WinBGP route status
$state_routeDescriptor=New-PrometheusMetricDescriptor -Name winbgp_state_route -Type gauge -Help 'WinBGP routes status' -Labels family,maintenance_timestamp,name,network,state
$routeStatus=@('down','maintenance','up','warning')
# Silently continue as WinBGP is generating errors when BGP is not configured (TO REVIEW)
$routesCurrentStatus=(WinBGP -ErrorAction SilentlyContinue | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}})
foreach ($routeCurrentStatus in $routesCurrentStatus) {
foreach ($status in $routeStatus) {
$WinBGP_metrics+=New-PrometheusMetric -PrometheusMetricDescriptor $state_routeDescriptor -Value $(if ($status -eq $routeCurrentStatus.Status) { 1 } else { 0 }) -Labels 'ipv4',$routeCurrentStatus.MaintenanceTimestamp,$routeCurrentStatus.Name,$routeCurrentStatus.Network,$status
}
}
# Return output
$commandOutput = Export-PrometheusMetrics -Metrics $WinBGP_metrics
# Add header
$outputHeader.Add('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} }
'POST' { 'metrics' {
if ($FullPath -like 'api/*') { switch ($request.HttpMethod) {
$RouteName = $request.QueryString.Item("RouteName") 'GET' {
$Path=$Path.replace('api/','') # Define WinBGP Prometheus metrics
Write-Log "API received POST request '$Path' from '$RequestUser' - Source IP: '$RequestHost'" -AdditionalFields $RouteName $WinBGP_metrics=@()
switch ($Path) {
'Reload' { # WinBGP peer status
[string]$ActionOutput=WinBGP -Reload $state_peerDescriptor=New-PrometheusMetricDescriptor -Name winbgp_state_peer -Type gauge -Help 'WinBGP Peers status' -Labels local_asn,local_ip,name,peer_asn,peer_ip,state
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput} $peerStatus=@('connected','connecting','stopped')
$outputHeader.Add('Content-Type', 'application/json') # Try/catch to detect if BGP is configured properly
$BgpStatus=$null
try {
$peersCurrentStatus=Get-BgpPeer -ErrorAction SilentlyContinue | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}
} }
'StartMaintenance' { catch {
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StartMaintenance # If BGP Router (Local) is not configured, catch it
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput} $BgpStatus=($_).ToString()
$outputHeader.Add('Content-Type', 'application/json')
} }
'StartRoute' { if ($BgpStatus -eq 'BGP is not configured.') {
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StartRoute $peersCurrentStatus=$null
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput}
$outputHeader.Add('Content-Type', 'application/json')
} }
'StopMaintenance' { # Parse all peers and generate metric
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StopMaintenance foreach ($peerCurrentStatus in $peersCurrentStatus) {
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput} foreach ($status in $peerStatus) {
$outputHeader.Add('Content-Type', 'application/json') $WinBGP_metrics+=New-PrometheusMetric -PrometheusMetricDescriptor $state_peerDescriptor -Value $(if ($status -eq $peerCurrentStatus.ConnectivityStatus) { 1 } else { 0 }) -Labels $peerCurrentStatus.LocalASN,$peerCurrentStatus.LocalIPAddress,$peerCurrentStatus.PeerName,$peerCurrentStatus.PeerASN,$peerCurrentStatus.PeerIPAddress,$status
}
} }
'StopRoute' {
[string]$ActionOutput=WinBGP -RouteName "$RouteName" -StopRoute # WinBGP route status
$commandOutput = ConvertTo-Json -InputObject @{'output'=$ActionOutput} $state_routeDescriptor=New-PrometheusMetricDescriptor -Name winbgp_state_route -Type gauge -Help 'WinBGP routes status' -Labels family,maintenance_timestamp,name,network,state
$outputHeader.Add('Content-Type', 'application/json') $routeStatus=@('down','maintenance','up','warning')
# Silently continue as WinBGP is generating errors when BGP is not configured (TO REVIEW)
$routesCurrentStatus=(WinBGP -ErrorAction SilentlyContinue | Select-Object Name,Network,Status,@{Label='MaintenanceTimestamp';Expression={($_.MaintenanceTimestamp).ToString("yyyy-MM-ddTHH:mm:ss.fffK")}})
foreach ($routeCurrentStatus in $routesCurrentStatus) {
foreach ($status in $routeStatus) {
$WinBGP_metrics+=New-PrometheusMetric -PrometheusMetricDescriptor $state_routeDescriptor -Value $(if ($status -eq $routeCurrentStatus.Status) { 1 } else { 0 }) -Labels 'ipv4',$routeCurrentStatus.MaintenanceTimestamp,$routeCurrentStatus.Name,$routeCurrentStatus.Network,$status
}
} }
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented # Return output
$commandOutput = Export-PrometheusMetrics -Metrics $WinBGP_metrics
# Add header
$outputHeader.Add('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
$statusCode = [System.Net.HttpStatusCode]::OK
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
}
}
}
'mcp' {
# Always send json header for MCP
$outputHeader.Add('Content-Type', 'application/json')
# Body request
$requestBodyReader = New-Object System.IO.StreamReader $request.InputStream
$requestBody=$requestBodyReader.ReadToEnd()
$jsonRequestBody=$requestBody | convertfrom-json
switch ($request.HttpMethod) {
'GET' {
# Send not allowed (405) to avoid switching to SSE
$statusCode = [System.Net.HttpStatusCode]::MethodNotAllowed
}
'POST' {
switch ($jsonRequestBody.method) {
'initialize' {
$commandOutput = '{"jsonrpc":"2.0","id":' + ($jsonRequestBody.id | ConvertTo-Json -Depth 10 -Compress) + ',"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"WinBGP MCP Server","version":"0.1.0"}}}'
$statusCode = [System.Net.HttpStatusCode]::OK
}
'notifications/initialized' {
# Accept the initialization request
$statusCode = [System.Net.HttpStatusCode]::Accepted
}
'notifications/responses' {
# Accept the notification request
$statusCode = [System.Net.HttpStatusCode]::Accepted
}
'tools/list' {
$toolsListJson = '{"name":"Get-WinBGPRoute","description":"Get-WinBGPRoute","inputSchema":{"type":"object","properties":{"ComputerName":{"type":"string","description":"Single or multiple ComputerName (Default: localhost)"},"Name":{"type":"string","description":"Single or multiple Route Name (IntelliSense availalble)"}},"required":[]},"returns":{"type":"string","description":"Get-WinBGPRoute"}}'
$commandOutput = '{"jsonrpc":"2.0","id":' + ($jsonRequestBody.id | ConvertTo-Json -Depth 10 -Compress) + ',"result":{"tools":' + $toolsListJson + '}}'
$statusCode = [System.Net.HttpStatusCode]::OK
}
'tools/call' {
#$toolName = $jsonRequestBody.params.name
#$Arguments = $jsonRequestBody.params.arguments
#$result = & $toolName @targetArgs
$result=[PSCustomObject]@{
Name = 'demo.webalex.lab'
Network = '172.16.10.10/32'
Status = 'up'
MaintenanceTimestamp = $null
ComputerName = 'server1.webalex.lab'
}
$callresponse = [ordered]@{
jsonrpc = "2.0"
id = $jsonRequestBody.id
result = @{
content = @(
[ordered]@{
type = "text"
text = $result | Out-String
}
)
isError = $false
}
}
$commandOutput=$callresponse | ConvertTo-Json -Depth 10 -Compress
$statusCode = [System.Net.HttpStatusCode]::OK
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
}
} }
} }
switch ($commandOutput.output) { Default {
'Success' { $statusCode = [System.Net.HttpStatusCode]::OK } $statusCode = [System.Net.HttpStatusCode]::NotImplemented
'WinBGP not ready' { $statusCode = [System.Net.HttpStatusCode]::InternalServerError } }
}
}
# Add stop method to stop API (TO IMPROVE)
'stop' {
switch ($request.HttpMethod) {
'POST' {
# Only local request are authorized
if ($request.IsLocal) {
$keepListening = $false
$statusCode = [System.Net.HttpStatusCode]::OK
} else {
$statusCode = [System.Net.HttpStatusCode]::Forbidden
}
}
Default {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} else {
$statusCode = [System.Net.HttpStatusCode]::NotImplemented
} }
} }
Default { Default {
@@ -559,7 +661,10 @@ if ($Configuration) {
$output.Write($buffer,0,$buffer.Length) $output.Write($buffer,0,$buffer.Length)
$output.Close() $output.Close()
} }
$listener.Stop() if ($listener.IsListening) {
$listener.Stop()
$listener.Close()
}
} else { } else {
Write-Log -Message "API failed - No Uri listener available" -Level Error Write-Log -Message "API failed - No Uri listener available" -Level Error
} }

View File

@@ -54,7 +54,7 @@ Param(
) )
# Don't forget to increment version when updating engine # Don't forget to increment version when updating engine
$scriptVersion = '1.1.0' $scriptVersion = '1.1.1'
# This script name, with various levels of details # This script name, with various levels of details
@@ -66,6 +66,9 @@ $scriptFullName = $argv0.fullname # Ex: C:\Temp\PSService.ps1
# Global settings # Global settings
$serviceName = "WinBGP" # A one-word name used for net start commands $serviceName = "WinBGP" # A one-word name used for net start commands
$serviceDisplayName = "WinBGP" $serviceDisplayName = "WinBGP"
# To improve (Service name should be rationalized)
$serviceInternalName = "$($serviceName)-Service"
$engineName = "$($serviceName)-Engine"
$ServiceDescription = "The BGP swiss army knife of networking on Windows" $ServiceDescription = "The BGP swiss army knife of networking on Windows"
$pipeName = "Service_$serviceName" # Named pipe name. Used for sending messages to the service task $pipeName = "Service_$serviceName" # Named pipe name. Used for sending messages to the service task
$installDir = "${ENV:ProgramW6432}\$serviceDisplayName" # Where to install the service files $installDir = "${ENV:ProgramW6432}\$serviceDisplayName" # Where to install the service files
@@ -943,7 +946,7 @@ function Add-RoutePolicy() {
# # # #
# Function Start-API # # Function Start-API #
# # # #
# Description Starting API Engine # # Description Starting API Job #
# # # #
# Arguments See the Param() block at the top of this script # # Arguments See the Param() block at the top of this script #
# # # #
@@ -960,6 +963,7 @@ function Start-API() {
) )
# Start API # Start API
Write-Log "Starting API engine" Write-Log "Starting API engine"
# ArgumentList (,$ApiConfiguration) is to handle array as argument # ArgumentList (,$ApiConfiguration) is to handle array as argument
Start-Job -Name 'API' -FilePath "$installDir\$serviceDisplayName-API.ps1" -ArgumentList (,$ApiConfiguration) Start-Job -Name 'API' -FilePath "$installDir\$serviceDisplayName-API.ps1" -ArgumentList (,$ApiConfiguration)
} }
@@ -968,7 +972,7 @@ function Start-API() {
# # # #
# Function Stop-API # # Function Stop-API #
# # # #
# Description Stopping API Engine # # Description Stopping API Job #
# # # #
# Arguments See the Param() block at the top of this script # # Arguments See the Param() block at the top of this script #
# # # #
@@ -980,18 +984,64 @@ function Start-API() {
function Stop-API() { function Stop-API() {
# Stop API # Stop API
Write-Log "Stopping API engine" Write-Log "Stopping API engine"
### IMPROVEMENT - To be check if we can kill API properly ### # Send API stop signal (TO IMPROVE)
$ProcessID=$null try {
$ApiPID=$null if ((Invoke-WebRequest -Uri 'http://127.0.0.1:8888/stop' -Method Post -TimeoutSec 5).StatusCode -eq 200) {
# Get service PID Stop-Job -Name 'API' -ErrorAction SilentlyContinue
$ProcessID=(Get-CimInstance Win32_Process -Filter "name = 'powershell.exe'" -OperationTimeoutSec 1 | Where-Object {$_.CommandLine -like "*'$installDir\$serviceDisplayName.ps1' -Service*"}).ProcessId Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue
if ($ProcessID) { }
# Get API PID } catch {
$ApiPID=(Get-WmiObject win32_process -filter "Name='powershell.exe' AND ParentProcessId=$ProcessID").ProcessId Write-Log "Error stopping API engine: $_" -Level Error
} }
Stop-Process -Id $ApiPID -Force -ErrorAction SilentlyContinue }
Stop-Job -Name 'API' -ErrorAction SilentlyContinue
Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue #-----------------------------------------------------------------------------#
# #
# Function Start-HealthCheck #
# #
# Description Starting HealthCheck Job #
# #
# Arguments See the Param() block at the top of this script #
# #
# Notes #
# #
# History #
# #
#-----------------------------------------------------------------------------#
function Start-HealthCheck() {
Param
(
[Parameter(Mandatory=$true)]
$Route
)
# Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($Route.RouteName)
Start-Job -Name $Route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $Route
}
#-----------------------------------------------------------------------------#
# #
# Function Stop-HealthCheck #
# #
# Description Stopping API HealthCheck #
# #
# Arguments See the Param() block at the top of this script #
# #
# Notes #
# #
# History #
# #
#-----------------------------------------------------------------------------#
function Stop-HealthCheck() {
Param
(
[Parameter(Mandatory=$true)]
$Route
)
# Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($Route.RouteName)
Stop-Job -Name $Route.RouteName
Remove-Job -Name $Route.RouteName -Force
} }
#-----------------------------------------------------------------------------# #-----------------------------------------------------------------------------#
@@ -1115,9 +1165,10 @@ if ($Service) { # Run the service
$configuration = Get-Content -Path $configdir | ConvertFrom-Json $configuration = Get-Content -Path $configdir | ConvertFrom-Json
Write-Log "Loading configuration file '$($configdir)'" Write-Log "Loading configuration file '$($configdir)'"
} else { } else {
Write-Log "Configuration file '$($configdir)' is not valid - Stopping $($serviceDisplayName) process" -Level Error Write-Log "Configuration file '$($configdir)' is not valid" -Level Warning
Write-Log "Stopping $($serviceInternalName) process" -Level Error
# Forcing stop process so service will know that process is not running # Forcing stop process so service will know that process is not running
Stop-Process -Name $serviceDisplayName -Force Stop-Process -Name $serviceInternalName -Force
exit 1 exit 1
} }
@@ -1210,8 +1261,7 @@ if ($Service) { # Run the service
Write-Log "Route '$($route.RouteName)' is in maintenance mode" -AdditionalFields @($route.RouteName) Write-Log "Route '$($route.RouteName)' is in maintenance mode" -AdditionalFields @($route.RouteName)
} else { } else {
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} }
} }
@@ -1301,7 +1351,6 @@ if ($Service) { # Run the service
# Start Api # Start Api
Start-API -ApiConfiguration $configuration.api Start-API -ApiConfiguration $configuration.api
} else { } else {
### TO BE IMPROVED because killing all healthchecks jobs ###
# Stop Api # Stop Api
Stop-API Stop-API
} }
@@ -1334,9 +1383,7 @@ if ($Service) { # Run the service
if ($routeReloaded.SideIndicator -eq '<=') { if ($routeReloaded.SideIndicator -eq '<=') {
Write-Log "Route '$($routeReloaded.RouteName)' removed" -AdditionalFields @($oldRoute.RouteName) Write-Log "Route '$($routeReloaded.RouteName)' removed" -AdditionalFields @($oldRoute.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) Stop-HealthCheck -Route $oldRoute
Stop-Job -Name $oldRoute.RouteName
Remove-Job -Name $oldRoute.RouteName -Force
# Remove routing policy # Remove routing policy
if (get-BgpRoutingPolicy -Name $oldRoute.RouteName -ErrorAction SilentlyContinue) { if (get-BgpRoutingPolicy -Name $oldRoute.RouteName -ErrorAction SilentlyContinue) {
Write-Log "Removing BGP Routing Policy [$($oldRoute.RouteName)]" -AdditionalFields @($oldRoute.RouteName) Write-Log "Removing BGP Routing Policy [$($oldRoute.RouteName)]" -AdditionalFields @($oldRoute.RouteName)
@@ -1363,8 +1410,7 @@ if ($Service) { # Run the service
# Create routing policies # Create routing policies
Add-RoutePolicy -Route $route -Peers $configuration.peers Add-RoutePolicy -Route $route -Peers $configuration.peers
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} elseif ($routeReloaded.SideIndicator -eq '==') { } elseif ($routeReloaded.SideIndicator -eq '==') {
# Comparing old route and new route to check if there are updates to perform # Comparing old route and new route to check if there are updates to perform
if (($route.Network -ne $oldRoute.Network) -or ($route.DynamicIpSetup -ne $oldRoute.DynamicIpSetup) -or ($route.Interface -ne $oldRoute.Interface) -or ($route.Interval -ne $oldRoute.Interval) -or (Compare-Object -ReferenceObject $oldRoute.Community -DifferenceObject $route.Community) -or ($route.Metric -ne $oldRoute.Metric) -or ($route.NextHop -ne $oldRoute.NextHop) -or ($route.WithdrawOnDown -ne $oldRoute.WithdrawOnDown) -or ($route.WithdrawOnDownCheck -ne $oldRoute.WithdrawOnDownCheck)) { if (($route.Network -ne $oldRoute.Network) -or ($route.DynamicIpSetup -ne $oldRoute.DynamicIpSetup) -or ($route.Interface -ne $oldRoute.Interface) -or ($route.Interval -ne $oldRoute.Interval) -or (Compare-Object -ReferenceObject $oldRoute.Community -DifferenceObject $route.Community) -or ($route.Metric -ne $oldRoute.Metric) -or ($route.NextHop -ne $oldRoute.NextHop) -or ($route.WithdrawOnDown -ne $oldRoute.WithdrawOnDown) -or ($route.WithdrawOnDownCheck -ne $oldRoute.WithdrawOnDownCheck)) {
@@ -1376,10 +1422,9 @@ if ($Service) { # Run the service
# If WithdrawOnDown change, restart healthcheck # If WithdrawOnDown change, restart healthcheck
Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Stop-Job -Name $oldRoute.RouteName Stop-HealthCheck -Route $route
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route Start-HealthCheck -Route $route
} }
# Manage WithdrawOnDownCheck change (Only if WithdrawOnDown was enabled and it still enabled) # Manage WithdrawOnDownCheck change (Only if WithdrawOnDown was enabled and it still enabled)
if ($route.WithdrawOnDown -and $oldRoute.WithdrawOnDown) { if ($route.WithdrawOnDown -and $oldRoute.WithdrawOnDown) {
@@ -1387,22 +1432,18 @@ if ($Service) { # Run the service
Write-Log "WithdrawOnDownCheck change - Old Check: '$($oldRoute.WithdrawOnDownCheck)' - New Check: '$($route.WithdrawOnDownCheck)'" -AdditionalFields @($Route.RouteName) Write-Log "WithdrawOnDownCheck change - Old Check: '$($oldRoute.WithdrawOnDownCheck)' - New Check: '$($route.WithdrawOnDownCheck)'" -AdditionalFields @($Route.RouteName)
Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Stop-Job -Name $oldRoute.RouteName Stop-HealthCheck -Route $oldRoute
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route Start-HealthCheck -Route $route
} }
} }
# Manage interval change # Manage interval change
if ($route.Interval -ne $oldRoute.Interval) { if ($route.Interval -ne $oldRoute.Interval) {
Write-Log "Interval change - Old Interval: '$oldRouteInterval' - New Interval: '$period'" -AdditionalFields @($Route.RouteName) Write-Log "Interval change - Old Interval: '$oldRouteInterval' - New Interval: '$period'" -AdditionalFields @($Route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) Stop-HealthCheck -Route $oldRoute
Stop-Job -Name $oldRoute.RouteName
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} }
# Manage network change # Manage network change
if ($route.Network -ne $oldRoute.Network) { if ($route.Network -ne $oldRoute.Network) {
@@ -1533,7 +1574,7 @@ if ($Service) { # Run the service
} }
} }
} else { } else {
Write-Log "Reload aborted - Configuration file '$($configdir)' is not a valid JSON file" -Level Error Write-Log "Reload aborted - Configuration file '$($configdir)' is not a valid JSON file" -Level Warning
} }
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
} }
@@ -1591,9 +1632,7 @@ if ($Service) { # Run the service
# Export maintenance variable on each change (To be moved to function) # Export maintenance variable on each change (To be moved to function)
$maintenance | Export-CliXml -Path $FunctionCliXml -Force $maintenance | Export-CliXml -Path $FunctionCliXml -Force
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) Stop-HealthCheck -Route $route_maintenance
Stop-Job -Name $route_maintenance.RouteName
Remove-Job -Name $route_maintenance.RouteName -Force
# Removing route # Removing route
if ((Get-BgpCustomRoute).Network -contains "$($route_maintenance.Network)") { if ((Get-BgpCustomRoute).Network -contains "$($route_maintenance.Network)") {
remove-Bgp -Route $route_maintenance remove-Bgp -Route $route_maintenance
@@ -1614,8 +1653,7 @@ if ($Service) { # Run the service
# Export maintenance variable on each change (To be moved to function) # Export maintenance variable on each change (To be moved to function)
$maintenance | Export-CliXml -Path $FunctionCliXml -Force $maintenance | Export-CliXml -Path $FunctionCliXml -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) Start-HealthCheck -Route $route_maintenance
Start-Job -Name $route_maintenance.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route_maintenance
} }
else { else {
Write-Log "Route '$($route_maintenance.RouteName)' was not in maintenance mode" -Level Warning Write-Log "Route '$($route_maintenance.RouteName)' was not in maintenance mode" -Level Warning
@@ -1633,6 +1671,29 @@ if ($Service) { # Run the service
# Start another thread waiting for control messages # Start another thread waiting for control messages
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
} }
elseif ($message -like 'healthcheck*') {
$route_to_control=$message.split(' ')[1]
$control_action=$message.split(' ')[2]
# Grabbing route
$route_healthcheck=$configuration.routes | Where-Object {$_.RouteName -eq $route_to_control}
if ($control_action -eq 'start') {
# Start HealthCheck
Start-HealthCheck -Route $route_healthcheck
}
elseif ($control_action -eq 'stop') {
# Stop HealthCheck
Stop-HealthCheck -Route $route_healthcheck
} elseif ($control_action -eq 'restart') {
# Log
Write-Log "Restarting HealthCheck engine"
# Stop HealthCheck
Stop-HealthCheck -Route $route_healthcheck
# Start HealthCheck
Start-HealthCheck -Route $route_healthcheck
}
# Start another thread waiting for control messages
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
}
elseif (($message -ne "stop") -and ($message -ne "suspend")) { # Start another thread waiting for control messages elseif (($message -ne "stop") -and ($message -ne "suspend")) { # Start another thread waiting for control messages
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
} }
@@ -1667,7 +1728,7 @@ if ($Service) { # Run the service
# Cleaning unhealthy HealthCheck # Cleaning unhealthy HealthCheck
Write-Log "Restarting HealthCheck Process (Watchdog)" -AdditionalFields @($route.RouteName) -Level Warning Write-Log "Restarting HealthCheck Process (Watchdog)" -AdditionalFields @($route.RouteName) -Level Warning
Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route Start-HealthCheck -Route $route
} }
} }
} }
@@ -1685,9 +1746,7 @@ if ($Service) { # Run the service
Write-Log -Message "Stopping HealthCheck engine" Write-Log -Message "Stopping HealthCheck engine"
ForEach ($route in $configuration.routes) { ForEach ($route in $configuration.routes) {
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route.RouteName) Stop-HealthCheck -Route $route
Stop-Job -Name $route.RouteName -ErrorAction SilentlyContinue
Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue
} }
# Stopping API # Stopping API
@@ -1728,6 +1787,9 @@ if ($Service) { # Run the service
$msg = $_.Exception.Message $msg = $_.Exception.Message
$line = $_.InvocationInfo.ScriptLineNumber $line = $_.InvocationInfo.ScriptLineNumber
Write-Log -Message "Error at line ${line}: $msg" -Level Error Write-Log -Message "Error at line ${line}: $msg" -Level Error
Write-Log "Stopping $($serviceInternalName) process" -Level Error
# Forcing stop process so service will know that process is not running (to avoid having service running without process)
Stop-Process -Name $serviceInternalName -Force
} finally { # Invoked in all cases: Exception or normally by -Stop } finally { # Invoked in all cases: Exception or normally by -Stop
# Cleanup the periodic timer used in the above example # Cleanup the periodic timer used in the above example
Unregister-Event -SourceIdentifier $timerName Unregister-Event -SourceIdentifier $timerName

View File

@@ -124,6 +124,15 @@ Param(
[Parameter(ParameterSetName='RouteName', Mandatory=$false)] [Parameter(ParameterSetName='RouteName', Mandatory=$false)]
[Switch]$StopRoute, # Control message to send to the service [Switch]$StopRoute, # Control message to send to the service
[Parameter(ParameterSetName='RouteName', Mandatory=$false)]
[Switch]$StartHealthCheck, # Control message to send to the service
[Parameter(ParameterSetName='RouteName', Mandatory=$false)]
[Switch]$StopHealthCheck, # Control message to send to the service
[Parameter(ParameterSetName='RouteName', Mandatory=$false)]
[Switch]$RestartHealthCheck, # Control message to send to the service
[Parameter(ParameterSetName='BGPStatus', Mandatory=$false)] [Parameter(ParameterSetName='BGPStatus', Mandatory=$false)]
[Switch]$BGPStatus = $($PSCmdlet.ParameterSetName -eq 'BGPStatus'), # Get the current service status [Switch]$BGPStatus = $($PSCmdlet.ParameterSetName -eq 'BGPStatus'), # Get the current service status
@@ -144,7 +153,7 @@ Param(
) )
# Don't forget to increment version when updating engine # Don't forget to increment version when updating engine
$scriptVersion = '1.0.1' $scriptVersion = '1.0.2'
# This script name, with various levels of details # This script name, with various levels of details
# Ex: PSService # Ex: PSService
@@ -560,9 +569,12 @@ if ($RestartAPI) {
} }
# Start/stop control or Maintenance control # Start/stop control or Maintenance control
if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) { if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance -or $StartHealthCheck -or $StopHealthCheck -or $RestartHealthCheck) {
# Logging if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance ) {
Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'" # Logging
Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'"
}
# Read configuration # Read configuration
$configuration = Get-Content -Path $configdir | ConvertFrom-Json $configuration = Get-Content -Path $configdir | ConvertFrom-Json
$routeCheck=$null $routeCheck=$null
@@ -589,6 +601,21 @@ if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) {
$control="maintenance $RouteName stop" $control="maintenance $RouteName stop"
} }
} }
# Start/stop control HealthCheck
if ($StartHealthCheck -or $StopHealthCheck -or $RestartHealthCheck) {
# START
if ($StartHealthCheck) {
$control="healthcheck $RouteName start"
}
# STOP
if ($StopHealthCheck) {
$control="healthcheck $RouteName stop"
}
# RESTART
if ($RestartHealthCheck) {
$control="healthcheck $RouteName restart"
}
}
if($routeCheck) { if($routeCheck) {
$PipeStatus=$null $PipeStatus=$null
# Performing Action # Performing Action