Public/ContainerNetworkTools.psm1
########################################################################### # # # Copyright (c) Microsoft Corporation. All rights reserved. # # # # This code is licensed under the MIT License (MIT). # # # ########################################################################### using module "..\Private\CommonToolUtilities.psm1" $ModuleParentPath = Split-Path -Parent $PSScriptRoot Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force function Get-WinCNILatestVersion { param ( [String]$repo = "microsoft/windows-container-networking" ) $tool = switch ($repo.ToLower()) { $WINCNI_PLUGIN_REPO { "wincniplugin" } $CLOUDNATIVE_CNI_REPO { "cloudnativecni" } Default { Throw "Invalid repository. Supported repositories are $WINCNI_PLUGIN_REPO and $CLOUDNATIVE_CNI_REPO" } } $latestVersion = Get-LatestToolVersion -Tool $tool return $latestVersion } function Install-WinCNIPlugin { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] param( [parameter(HelpMessage = "Windows CNI plugin version to use. Defaults to 'latest'")] [string]$WinCNIVersion = "latest", [parameter(HelpMessage = "Path to cni folder ~\cni (not ~\cni\bin). Defaults to `$env:ProgramFiles\containerd\cni)")] [String]$WinCNIPath = "$env:ProgramFiles\containerd\cni", [parameter(HelpMessage = "Source of the Windows CNI plugins. Defaults to 'microsoft/windows-container-networking'")] [ValidateSet("microsoft/windows-container-networking", "containernetworking/plugins")] [string]$SourceRepo = "microsoft/windows-container-networking", [Parameter(HelpMessage = 'OS architecture to download files for. Default is $env:PROCESSOR_ARCHITECTURE')] [ValidateSet('amd64', '386', "arm", "arm64")] [string]$OSArchitecture = $env:PROCESSOR_ARCHITECTURE, [Switch] [parameter(HelpMessage = "Installs Windows CNI plugins even if the tool already exists at the specified path")] $Force ) begin { $ToolName = 'WinCNIPlugin' if (!$WinCNIPath) { $containerdPath = Get-DefaultInstallPath -Tool "containerd" $WinCNIPath = "$containerdPath\cni" } $WinCNIPath = $WinCNIPath -replace '(\\bin)$', '' # Check if WinCNI plugins are installed $isInstalled = -not (Test-EmptyDirectory -Path $WinCNIPath) $plugin = "Windows CNI plugins" $WhatIfMessage = "$plugin will be installed at $WINCNIPath" if ($isInstalled) { $WhatIfMessage = "$plugin will be uninstalled from and reinstalled at $WINCNIPath" } } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { # Check if tool already exists at specified location if ($isInstalled) { $errMsg = "Windows CNI plugins already exists at $WinCNIPath or the directory is not empty" Write-Warning $errMsg # Uninstall if tool exists at specified location. Requires user consent try { Uninstall-WinCNIPlugin -Path "$WinCNIPath" -Confirm:$false -Force:$Force | Out-Null } catch { Throw "Windows CNI plugin installation failed. $_" } } # Get Windows CNI plugins version to install if (!$WinCNIVersion) { # Get default version $WinCNIVersion = Get-WinCNILatestVersion -Repo $SourceRepo } $WinCNIVersion = $WinCNIVersion.TrimStart('v') Write-Output "Downloading CNI plugin version $WinCNIVersion at $WinCNIPath" New-Item -Path $WinCNIPath -ItemType Directory -Force -ErrorAction Ignore | Out-Null Write-Debug ("Downloading Windows CNI plugins from {0}" -f $SourceRepo) # File filter for Windows CNI plugins $fileFilterRegEx = $null if ($SourceRepo -eq "containernetworking/plugins") { # File filter for containernetworking/plugins # Contains files with .tgz extension and the checksum files with .SHA512 and .SHA256 extensions # We use .SHA512 files to verify the integrity of the downloaded files $fileFilterRegEx = ".*tgz(.SHA512)?$" } # Download file from repo $downloadParams = @{ ToolName = "$ToolName" Repository = $SourceRepo Version = $WinCNIVersion OSArchitecture = $OSArchitecture DownloadPath = "$HOME\Downloads\" ChecksumSchemaFile = $null FileFilterRegEx = $fileFilterRegEx } $downloadParamsProperties = [FileDownloadParameters]::new( $downloadParams.ToolName, $downloadParams.Repository, $downloadParams.Version, $downloadParams.OSArchitecture, $downloadParams.DownloadPath, $downloadParams.ChecksumSchemaFile, $downloadParams.FileFilterRegEx ) $sourceFile = Get-InstallationFile -FileParameters $downloadParamsProperties # Untar and install tool $params = @{ Feature = "$ToolName" InstallPath = "$WinCNIPath\bin" SourceFile = $sourceFile cleanup = $true UpdateEnvPath = $false } Install-RequiredFeature @params Write-Output "CNI plugin version $WinCNIVersion ($sourceRepo) successfully installed at $WinCNIPath" } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Initialize-NatNetwork { [CmdletBinding( SupportsShouldProcess = $true )] param( [parameter(HelpMessage = "Name of the new network. Defaults to 'nat''")] [String]$NetworkName = "nat", [parameter(HelpMessage = "Gateway IP address. Defaults to default gateway address'")] [String]$Gateway, [parameter(HelpMessage = "Size of the subnet mask. Defaults to 16")] [ValidateRange(0, 32)] [Int]$CIDR = 16, [parameter(HelpMessage = "Windows CNI plugins version to use. Defaults to latest version.")] [String]$WinCNIVersion, [parameter(HelpMessage = "Absolute path to cni folder ~\cni (not ~\cni\bin). Defaults to `$env:ProgramFiles\containerd\cni)")] [String]$WinCNIPath = "$env:ProgramFiles\containerd\cni", [parameter(HelpMessage = "Bypass confirmation to install any missing dependencies (Windows CNI plugins and HNS module)")] [Switch] $Force ) begin { if (!$WinCNIPath) { $ContainerdPath = Get-DefaultInstallPath -Tool "containerd" $WinCNIPath = "$ContainerdPath\cni" } $WinCNIPath = $WinCNIPath -replace '(\\bin)$', '' $cniConfDir = "$WinCNIPath\conf" # Check if WinCNI plugins is already installed $isInstalled = -not (Test-EmptyDirectory -Path "$WinCNIPath\bin") $WhatIfMessage = "Initialises a NAT network using Windows CNI plugins installed" if (!$isInstalled) { $WhatIfMessage = "`n`t1. Import `"HostNetworkingService`" or `"HNS`" module,`n`t2. Install Windows CNI plugins, and 3. Initialize a NAT network using Windows CNI plugins installed`n" } } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { if (!$force) { if (!$ENV:PESTER) { if (-not $PSCmdlet.ShouldContinue('', "Are you sure you want to initialises a NAT network?`n`t`tHNS module will be imported and missing dependencies (Windows CNI Plugins) will be installed if missing.")) { Write-Error "NAT network initialisation cancelled." return } } } # Import HNS module try { Import-HNSModule -Force:$Force } catch { Throw "Could not import HNS module. $_" } Write-Information -MessageData "Creating NAT network" -InformationAction Continue # Install missing WinCNI plugins if (!$isInstalled) { if ($force) { Write-Warning "Windows CNI plugins have not been installed. Installing Windows CNI plugins at '$WinCNIPath'" Install-WinCNIPlugin -WinCNIPath $WinCNIPath -WinCNIVersion $WinCNIVersion -Force:$force } else { Write-Warning "Couldn't initialize NAT network. CNI plugins have not been installed. To install, run the command `"Install-WinCNIPlugin`"." return } } # Check of NAT exists $natInfo = Get-HnsNetwork -ErrorAction Ignore | Where-Object { $_.Name -eq $networkName } if ($null -ne $natInfo) { Write-Warning "$networkName already exists. To view existing networks, use `"Get-HnsNetwork`". To remove the existing network use the `"Remove-HNSNetwork`" command." return } New-Item -ItemType 'Directory' -Path $cniConfDir -Force | Out-Null # Check if `New-HNSNetwork` command exists if (-not (Get-Command -Name 'New-HNSNetwork' -ErrorAction SilentlyContinue)) { Throw "`"New-HNSNetwork`" command does not exist. Ensure the HNS module is installed. To resolve this issue, see`n`thttps://github.com/microsoft/containers-toolkit/blob/main/docs/FAQs.md#2-new-hnsnetwork-command-does-not-exist" } # Set default gateway if gateway us null and generate subnet mash=k from Gateway if (!$gateway) { $gateway = (Get-NetRoute -DestinationPrefix "0.0.0.0/0").NextHop } $networkIdentifier = $gateway -replace "\.\d*$", ".0" $subnet = "$networkIdentifier/$CIDR" Write-Debug "Creating NAT network with Gateway $gateway and Subnet mask $subnet" # Set default WinCNI version of null if (!$WinCNIVersion) { # Get default version $WinCNIVersion = Get-WinCNILatestVersion } $WinCNIVersion = $WinCNIVersion.TrimStart('v') try { # Restart HNS service Get-Service "hns" -ErrorAction SilentlyContinue | Restart-Service -Force -ErrorAction SilentlyContinue # Create NAT network $hnsNetwork = New-HNSNetwork -Name $networkName -Type NAT -AddressPrefix $subnet -Gateway $gateway # Set default CNI config $params = @{ WinCNIVersion = $WinCNIVersion NetworkName = $networkName Gateway = $gateway Subnet = $subnet CNIConfDir = $cniConfDir } Set-DefaultCNIConfig @params Write-Output "Successfully created new NAT network called '$($hnsNetwork.Name)' with Gateway $($hnsNetwork.Subnets.GatewayAddress), and Subnet Mask $($hnsNetwork.Subnets.AddressPrefix)" } catch { Throw "Could not create a new NAT network $networkName with Gateway $gateway and Subnet mask $subnet. $_" } } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function/script return } } } function Uninstall-WinCNIPlugin { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] param( [parameter(HelpMessage = "Windows CNI plugin path")] [String]$Path, [parameter(HelpMessage = "Bypass confirmation to uninstall Windows CNI plugins")] [Switch] $Force ) begin { $tool = 'WinCNIPlugin' if (!$Path) { $ContainerdPath = Get-DefaultInstallPath -Tool "containerd" $Path = "$ContainerdPath\cni" } $Path = $Path -replace '(\\bin\\?)$', '' $WhatIfMessage = "Windows CNI plugins will be uninstalled from $path" } process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { if (Test-EmptyDirectory -Path $path) { Write-Output "Windows CNI plugins does not exist at $Path or the directory is empty" return } # Check user consents to uninstall WinCNIPlugin $consent = $force if (!$ENV:PESTER) { $consent = $force -or $PSCmdlet.ShouldContinue($Path, 'Are you sure you want to uninstall Windows CNI plugins?') } if (!$consent) { Throw "Windows CNI plugins uninstallation cancelled." } Write-Warning "Uninstalling preinstalled Windows CNI plugin at the path $path" try { Uninstall-WinCNIPluginHelper -Path $path } catch { Throw "Could not uninstall $tool. $_" } } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } function Uninstall-WinCNIPluginHelper { param( [ValidateNotNullOrEmpty()] [parameter(HelpMessage = "Windows CNI plugin path")] [String]$Path ) Write-Output "Uninstalling Windows CNI plugin" if (Test-EmptyDirectory -Path $Path) { Write-Error "Windows CNI plugin does not exist at $Path or the directory is empty." return } # Remove the folder where WinCNI plugins are installed Remove-Item $Path -Recurse -Force -ErrorAction Ignore Write-Output "Successfully uninstalled Windows CNI plugin." } function Import-HNSModule { param( [Switch] $Force ) $ModuleName = 'HostNetworkingService' # https://learn.microsoft.com/en-us/powershell/module/hostnetworkingservice/?view=windowsserver2025-ps if ((Get-Module -Name $ModuleName)) { return } $ModuleName = 'HNS' # https://www.powershellgallery.com/packages/HNS/0.2.4 if (Get-Module -ListAvailable -Name $ModuleName) { Import-Module -Name $ModuleName -DisableNameChecking -Force:$Force return } # Throw an error if the module is not installed Throw "`"HostNetworkingService`" or `"HNS`" module is not installed. To resolve this issue, see`n`thttps://github.com/microsoft/containers-toolkit/blob/main/docs/FAQs.md#2-new-hnsnetwork-command-does-not-exist" } # FIXME: nerdctl- Warning when user tries to run container with this network config function Set-DefaultCNIConfig { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Low' )] param( [String]$WinCNIVersion, [String]$networkName, [String]$gateway, [String]$subnet, [String]$cniConfDir ) process { if ($PSCmdlet.ShouldProcess('', "Sets Default CNI config")) { # TODO: Default CNI config for containernetworking/plugins # https://www.cni.dev/plugins/current/main/win-bridge/ # https://www.cni.dev/plugins/current/main/win-overlay/ $CNIConfig = @" { "cniVersion": "$WinCNIVersion", "name": "$networkName", "type": "nat", "master": "Ethernet", "ipam": { "subnet": "$subnet", "routes": [ { "gateway": "$gateway" } ] }, "capabilities": { "portMappings": true, "dns": true } } "@ $CNIConfig | Set-Content "$cniConfDir\0-containerd-nat.conf" -Force } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script return } } } Export-ModuleMember -Function Get-WinCNILatestVersion Export-ModuleMember -Function Install-WinCNIPlugin Export-ModuleMember -Function Uninstall-WinCNIPlugin, Uninstall-WinCNIPluginHelper Export-ModuleMember -Function Initialize-NatNetwork # SIG # Begin signature block # MIIoXQYJKoZIhvcNAQcCoIIoTjCCKEoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCrz2Q+H9PcrRPn # QajuhQk9n5MNAIe077+VpVpoJuzh06CCDYswggYJMIID8aADAgECAhMzAAAD9LjE # XeFOcLZ+AAAAAAP0MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwNzE3MjEwMjM1WhcNMjUwOTE1MjEwMjM1WjCBiDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWlj # cm9zb2Z0IDNyZCBQYXJ0eSBBcHBsaWNhdGlvbiBDb21wb25lbnQwggEiMA0GCSqG # SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv3P8bL08GKolFW7QNDVOF0aM4iqMxVvAW # VM124/82xbjAraJkKxieMrQa1Fc95LVGgxmJIi5R6QKMz2MO9bnwC7kSkPqoZJil # 26bRLY6jinjbwPpK3TzbW7z9bXfWw5bPFlt72NVIdXJ3xtHoYa+AOi++CF2Ry7+7 # o1AzvotJwG6lQSiCMKeMt8apqEF1f+QkDFEUv5tezw9748DeHW9orvo4IPzWa7vW # QgljB08LKSnzTN9/Jot2coWpFv4YuEoJZmR2ofPJMnDUUruDORTXnxwhfvd/wUmI # SoEysSqobkNV+qFuUmSShYrx8R1zHm7P6G/iRMIKYmSrIYBKUvndAgMBAAGjggFz # MIIBbzAfBgNVHSUEGDAWBgorBgEEAYI3TBEBBggrBgEFBQcDAzAdBgNVHQ4EFgQU # Dz4uMjS8YCSZaU0449GJYQ1ufyowRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEWMBQGA1UEBRMNMjMxNTIyKzUwMjUxODAfBgNV # HSMEGDAWgBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0Ey # MDExXzIwMTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZF # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQ # Q0EyMDExXzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL # BQADggIBABUCAiEn4g8i5T3VCP8160IY4ERdvZi5QZ2pSnBPW1dswVhLxkNTiCTV # XKDjTQ4EwDBNSZZGJePz4+t86pKhlBON3S7wswf5fCovJLlIiKbw+E4TZeY6xAxd # +5zV7Q2lsQhPHxiOY0PIGUE0KJfv/DQUulD8DrE0rru7yOO+DJI0muoK0BbHhRfd # mAJhp2gbYRkarEIkhML9m3gR12mCBb69Vocm4IyOBivUPMjjvQMkERF7cR07k2uP # 6dmpR8wtof9la0/K0wgiP5XuQUsAqgzhXrljH7dK7nqGrBDjJtrRdYfvVL+Rcz9i # YZO280g2uNtac5em3HOEsactAL7XKqZ4o7s9sRyp/bTNLLRmhFMB729IL+Hi0YM7 # C8th3HZ5nP+77L46KUGip6QgRIJs+EO0YNW+AwgMxPfKpTx/Ggh8Z85kP7HLDZJk # ZdPO/3cgVOTO4ax21vO2yMPCdfoGGr2ZLZw4SjEbGuOZJ22iGMV7tBvHk8nWAt3q # +j/icAq99GA1nIPnw3jK3K9OwGqwA9eiWsO8/bHMm6s50UKIFupMKm6qObosaVBy # R58rf8Cxumka7hPy1eSJSzQyA4UqYNTWuChsTfqgRLmLomS6yAu7t4r/bM4mGl+2 # Ki+avhQ4COm3jWWd0V6UGIP3T4zaKNs2GWFBIYsb/6XVvvi7pz/JMIIHejCCBWKg # AwIBAgIKYQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3Qg # Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYw # NzA4MjEwOTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjAN # BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGf # Qhsqa+laUKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRg # JGyvnkmc6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NE # t13YxC4Ddato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnn # Db6gE3e+lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+E # GvKhL1nkkDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+t # GSOEy/S6A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxh # H2rhKEmdX4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AV # s70b1FVL5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f # 7Fufr/zdsGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3D # KI8sj0A3T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9Jaw # vEagbJjS4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1Ud # DgQWBBRIbmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi # AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRy # LToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3Js # Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDEx # XzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDEx # XzIwMTFfMDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/ # BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2Nz # L3ByaW1hcnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAA # bwBsAGkAYwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUA # A4ICAQBn8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+ # vj/oCso7v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4H # Limb5j0bpdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6 # aC6VoCo/KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiX # mE0OPQvyCInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn # +N4sOiBpmLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAq # ZaPDXVJihsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5h # YbXw3MYbBL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/ # RXceNcbSoqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXm # r/r8i+sLgOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMyk # XcGhiJtXcVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGigwghokAgEB # MIGVMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNV # BAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAP0uMRd4U5w # tn4AAAAAA/QwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQB # gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE # MSIEIAlf9KwVTG6mlXPpTwtNj4NZzWkRo3BH9aRZa/iqspdvMEQGCisGAQQBgjcC # AQwxNjA0oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNy # b3NvZnQuY29tIDANBgkqhkiG9w0BAQEFAASCAQClrflgU6qjBaf1lrT38IvKnDdZ # a/Y1ke7bHBERD72zpazUuLRvAFeg1Ol+1cRygCWL3LXilKGXUM6h7yGOMNNgDVgP # GCqW2WnzxsE+mkLOjh8EGvtZoq4yO0U+ruqVpmZFO9NS8Fto0mBGRk0OCEUzztT8 # fJ0PlNJpfN22K+ZhLOH4Y/ldEf5hGpt03VdvStcQ3QxG9q9GnYVLLWpIHyFkB3fi # C+KT2z3qsaJevfhbFUyhJU0zSWU0NlWumXQRqUCCl1lP3o47uzPVHYPl6SGNkaY+ # Mh/qNDQ952UsmJQLOv+WzSMRO2QRqcdPfaov7/JJyvaea20CLvUyeGVFO7IboYIX # sDCCF6wGCisGAQQBgjcDAwExghecMIIXmAYJKoZIhvcNAQcCoIIXiTCCF4UCAQMx # DzANBglghkgBZQMEAgEFADCCAVoGCyqGSIb3DQEJEAEEoIIBSQSCAUUwggFBAgEB # BgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEINidRnHaNjiIB3Bz3cWsaZRW # KjDYC8nspwelHFOqi/VOAgZoEsLQqJwYEzIwMjUwNTE1MTYxOTU4LjI3OVowBIAC # AfSggdmkgdYwgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjJEMUEtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIR/jCCBygwggUQoAMCAQIC # EzMAAAH9c/loWs0MYe0AAQAAAf0wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwHhcNMjQwNzI1MTgzMTE2WhcNMjUxMDIyMTgzMTE2 # WjCB0zELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UE # CxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMScwJQYDVQQL # Ex5uU2hpZWxkIFRTUyBFU046MkQxQS0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jv # c29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQChZaz4P467gmNidEdF527QxMVjM0kRU+cgvNzTZHepue6O+FmCSGn6 # n+XKZgvORDIbbOnFhx5OMgXseJBZu3oVbcBGQGu2ElTPTlmcqlwXfWWlQRvyBReI # PbEimjxgz5IPRL6FM/VMID/B7fzJncES2Zm1xWdotGn8C+yqD7kojQrDpMMmkrBM # uXRVbT/bewqKR5YNKcdB5Oms7TMib9u1qBJibdX/zNeV/HLuz8RUV1KCUcaxSrwR # m6lQ7xdsfPPu1RHKIPeQ7E2fDmjHV5lf9z9eZbgfpvjI2ZkXTBNm7DfvIDU8ko7J # JKtetYSH4fr75Zvr7WW0wI+gwkdS08/cKfQI1w2+s/Im0NpyqOchOsvOuwd04uqO # wfbb1mS+d2TQirEENmAyhj4R/t98VE/ak+SsXUX0hwGRjPyEv5CNf67jLhSqrhS1 # PtVGeyq9H/H/5AsTSlxISH9cTXDV9ynomarxGccReKTJwws39r8pjGlI/cV8Vstm # 5/6oivIUvSAQPK1qkafU42NWSIqlU/a6pUhiPhWIKPLmktRx4x6qIqBiqGmZQcIT # ZaywsuF1AEd2mXbz6T5ljqbh08WcSgZwke4xwhmfDhw7CLGiNE6v42rvVwmPtDgv # RfA++5MdC3SgftEoxCCazLsJUPu/nl06F0dd1izI7r10B0r6daXJhwIDAQABo4IB # STCCAUUwHQYDVR0OBBYEFOkMxcDhlbz7Ivb7e8DpGZTugQqkMB8GA1UdIwQYMBaA # FJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3Rh # bXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUH # MAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9z # b2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQC # MAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqG # SIb3DQEBCwUAA4ICAQBj2Fhf5PkCYKtgZof3pN1HlPnb8804bvJJh6im/h+WcNZA # uEGWtq8CD6mOU2/ldJdmsoa/x7izl0nlZ2F8L3LAVCrhOZedR689e2W5tmT7TYFc # rr/beEzRNIqzYqWFiKrNtF7xBsx8pcQO28ygdJlPuv7AjYiCNhDCRr7c/1VeARHC # 7jr9zPPwhH9mr687nnbcmV3qyxW7Oz27AismF9xgGPnSZdZEFwyHNqMuNYOByKHQ # O7KQ9wGmhMuU4vwuleiiqev5AtgTgGlR6ncnJIxh8/PaF84veDTZYR+w7GnwA1tx # 2KozfV2be9KF4SSaMcDbO4z5OCfiPmf4CfLsg4NhCQis1WEt0wvT167V0g+GnbiU # W2dZNg1oVM58yoVrcBvwoMqJyanQC2FE1lWDQE8Avnz4HRRygEYrNL2OxzA5O7Um # Y2WKw4qRVRWRInkWj9y18NI90JNVohdcXuXjSTVwz9fY7Ql0BL3tPvyViO3D8/Ju # 7NfmyHEGH9GpM+8LICEjEFUp83+F+zgIigVqpYnSv/xIHUIazLIhw98SAyjxx6rX # DlmjQl+fIWLoa6j7Pcs8WX97FBpG5sSuwBRN/IFjn/mWLK+MCDINicQHy8c7tzsW # Da0Z3mEaBiz4A6hbHbj5dzLGlSQBqMOGTL0OX7wllOO2zoFxP2xhOY6h2T9KAjCC # B3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAw # gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMT # KU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIx # MDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57Ry # IQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VT # cVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhx # XFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQ # HJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1 # KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s # 4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUg # fX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3 # Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je # 1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUY # hEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUY # P3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGC # NxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4w # HQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYB # BAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcD # CDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0T # AQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNV # HR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w # cm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEE # TjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl # cnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOC # AgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/a # ZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp # 4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq # 95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qB # woEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG # +jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3B # FARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77 # IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJ # fn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K # 6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDx # yKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNZMIICQQIBATCC # AQGhgdmkgdYwgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjJEMUEtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCi # PRa1VVBQ1Iqiq2uOKdECwFR2g6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA69BeITAiGA8yMDI1MDUxNTEyMzU0 # NVoYDzIwMjUwNTE2MTIzNTQ1WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDr0F4h # AgEAMAoCAQACAhfOAgH/MAcCAQACAhOKMAoCBQDr0a+hAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAJR3JJeGI5QxaoQOYGTDMTcA+MnuvxP7mq902xY2eLwO # cRPqnBeY3K85bzTLAiZRLJcREYOZIGlUTr6BCAjwBm0O5V05vcUL9pp/9GZ5HcJv # kB+fU1micI7lnRwPaP3KoGUbvYnRZBiw3wn4fQC11Y0lxlqX9T0d/m+6PCz3SUf6 # R9qyUSj61Zu2oS3io0RlP+zaZXkO6WQ1/w9RKU4IesbrCnm1EiigoTy45MNvgvgc # wvv6zUAz9XoCClXqlapLp5HPHD79lsoMvWWB2DYnyT40HvWCGO/+T4YAXU8tuNn/ # X4GQ7HJmWADc5Y8DfccqUFeGs1eTBF/DUHiETWQU86ExggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAf1z+WhazQxh7QABAAAB # /TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCA4vBiC3YTQ2lzDX+NFtPc3XWPZZWDWbSsWKDPI3tH6 # SzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIIAoSA3JSjC8h/H94N9hK6ke # R4XWnoYXCMoGzyVEHg1HMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAH9c/loWs0MYe0AAQAAAf0wIgQgGf/DtHulcoON6sk40jLMLWh7 # fw5JSCh5IriC2zp3jJwwDQYJKoZIhvcNAQELBQAEggIAZVwnOtckY7ruR2YRmTfz # abQjwNJHyWky05sxfcd8Hop90cl6FJNwmHQhph8cDRYu108a5wG2RpVLBAdrIGkj # uOhbBmOmPn+ka8PRre/7SLOelGhQvntTZGxVWJKLNi8gHU8kIQRL1F3F56qbnigz # ewOwHvwvoz5b7mJ22mnQSOsrQbV+Say3Lp0cNW99bTbMyYr9IXOvBxhYDVdSpyVR # dW5XF4ByAQ39Ifa0VHTi0O/62UwWJ/8cgS9pEHs6pZ4+vFmWfdYwgQIZA0ORJppq # 5WmgC/WmSR6fF9fi2+ockDjumHQByXygsYEofF26IUNdjA2prObqYNXgpSvM5MMD # FOp53IIGxJ8C4LLdZq5rjv3ODmnPACV1NPGO03fnsyYhMk4y15GUZJC9yNFifftb # ssM9i5SkXUlDBLaEvZjeOQp941349ioMIDqDUZ7xFnZlnDkFS3/8EuQ0jUurUiED # Lyf+PM+KUO82renkGcUxat7p257h8pVtxs07ne+obikKQizGExdJw9k6iwqbCPsy # pyuRF5ceTMUOlVY3J+oemB+HoaIx8ewVxvA1mJzm440RB4Pm8T3A7A0tKvG6DHzV # gNGwONXBwjSnPkCkp5Lp0HSWmPnPxdZvQzmtU1InsyD2MklDsdN+Wxc9Q3D4jLum # eYsx4Aq0kjCSFrxSWwrbQtU= # SIG # End signature block |