From c39a65030c41230b57440be210202c178dcdae29 Mon Sep 17 00:00:00 2001 From: eosfor Date: Tue, 4 Jan 2022 22:02:08 -0800 Subject: [PATCH 1/7] Initial commit --- .gitignore | 350 +++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 1 + 3 files changed, 372 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfcfd56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ac12aff --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 eosfor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..24506f2 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# scripting-notes \ No newline at end of file From 936df280a590cd553a9abb6ee97c1cc22a306325 Mon Sep 17 00:00:00 2001 From: eosfor Date: Tue, 4 Jan 2022 22:06:04 -0800 Subject: [PATCH 2/7] init - first notebook --- NOTEBOOKS/ru/ipmgmt.ipynb | 369 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 NOTEBOOKS/ru/ipmgmt.ipynb diff --git a/NOTEBOOKS/ru/ipmgmt.ipynb b/NOTEBOOKS/ru/ipmgmt.ipynb new file mode 100644 index 0000000..b51926c --- /dev/null +++ b/NOTEBOOKS/ru/ipmgmt.ipynb @@ -0,0 +1,369 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Как рассчитать IP сети на PowerShell" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Сценарий**\n", + "\n", + "Предположим, что для всего, что мы планируем запускать в Azure, network team выделила нам диапазон адресов `10.172.0.0/26`. При этом мы пытаемся сделать автоматизированный механизм, который, получив на вход желаемый размер или список размеров сетей, сможет найти свободные блоки из указанного диапазона и рассчитать подсети и маски. Затем эти подсети и маски мы сможем использовать для создания сетей в Azure.\n", + "\n", + "Эти примеры можно затем использовать при автоматизации создания того, что Microsoft называет [Landing Zone](https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/), в части автоматического рассчета диапазонов сетей" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Начнем c загрузки модуля [ipmgmt](https://github.com/eosfor/ipmgmt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Import-Module ipmgmt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если он не установлен, его всегда можно установить из Powershell gallery" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Install-Module ipmgmt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В этом модуле всего две команды. Одна из них предназначена для разбиения сетей на подсети. Вторая - может найти свободные диапазоны подходящего размера, исходя из \"корневой\" сети и списка занятых сетей в этой корневой сети" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\u001b[32;1mCommandType Name Version Source\u001b[0m\r\n", + "\u001b[32;1m----------- ---- ------- ------\u001b[0m\r\n", + "Function Get-IPRanges 0.1.5 ipmgmt\r\n", + "Function Get-VLSMBreakdown 0.1.5 ipmgmt\r\n", + "\r\n" + ] + } + ], + "source": [ + "Get-Command -Module ipmgmt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь разобьем нашу корневую сеть на несколько подсетей. Для нас эти подсети станут виртуальными сетями в Azure. Для этого нам нужно указать эти сети и их размеры. В переменной `$subnets` содержится массив этих будущих сетей. В поле `type` мы указываем имя будущей сети, а в поле `size` - ее размер. Размер - это количество возможных адресов в сети. В нашем примере мы создаем две /24 сети и их размер соответственно 256 адресов максимально возможных минус 2 – нулевой и последний которые мы хотим исключить. Не забываем, что Azure отхватывает еще по 5 адресов в каждой подсети, которую мы создаем для своих нужд" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$subnets = @{type = \"VNET-HUB\"; size = (256-2)},\n", + " @{type = \"VNET-A\"; size = (256-2)}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Сети готовы, можно разбивать" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\u001b[32;1mtype Network Netmask FirstUsable LastUsable Usable Cidr\u001b[0m\r\n", + "\u001b[32;1m---- ------- ------- ----------- ---------- ------ ----\u001b[0m\r\n", + "VNET-A 10.172.1.0 255.255.255.0 10.172.1.1 10.172.1.254 254 24\r\n", + "VNET-HUB 10.172.0.0 255.255.255.0 10.172.0.1 10.172.0.254 254 24\r\n", + "reserved 10.172.128.0 255.255.128.0 10.172.128.1 10.172.255.254 32766 17\r\n", + "reserved 10.172.64.0 255.255.192.0 10.172.64.1 10.172.127.254 16382 18\r\n", + "reserved 10.172.32.0 255.255.224.0 10.172.32.1 10.172.63.254 8190 19\r\n", + "reserved 10.172.16.0 255.255.240.0 10.172.16.1 10.172.31.254 4094 20\r\n", + "reserved 10.172.8.0 255.255.248.0 10.172.8.1 10.172.15.254 2046 21\r\n", + "reserved 10.172.4.0 255.255.252.0 10.172.4.1 10.172.7.254 1022 22\r\n", + "reserved 10.172.2.0 255.255.254.0 10.172.2.1 10.172.3.254 510 23\r\n", + "\r\n" + ] + } + ], + "source": [ + "Get-VLSMBreakdown -Network 10.172.0.0/16 -SubnetSize $subnets | ft type, network, netmask, *usable, cidr -AutoSize" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Как видно из результата, мы получили две сети с указанными именами `VNET-A` и `VNET-HUB`. При этом, в корневом диапазоне образовались незанятые слоты, которые получились от того, что сети, которые мы выделяли, маленькие. Однако команда старается максимизировать размеры этих неиспользованных кусков, с тем чтобы терять меньше адресов при таких разбиениях. Так, например, есть кусок с размером `/17` в поле `Cidr`. Это говорит о том, что команда разбила наш `/16` диапазон на два `/17`, оставила один из них неиспользованным, затем разбила другой, уже на два `/18` и так далее. Незадействованные куски считаются \"зарезервированными\" для будущего использования" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "При условии, что вы уже авторизовались в Azure. В данном примере я использую ресурсную группу с именем `vnet-test` в регионе `eastus2`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Login-AzAccount" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Сеточки эти теперь можно создать примерно вот так. Тут мы отфильтровываем \"reserved\" диапазоны - не будем создавать сети из них. Затем, для каждого из этих диапазонов создаем сеточку в некой ресурсной группе. Это, конечно, упрощенный пример, но дальнейшие усовершенствования, связанные с другими подписками или ресурсными группами, пирингом виртуальных сетей и их дальнейшим разбиением на подсети уже никак не повлияет на то, что бы делаем сейчас." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$vnets = Get-VLSMBreakdown -Network 10.172.0.0/16 -SubnetSize $subnets | ? type -ne 'reserved'\n", + "\n", + "$vnets | % {\n", + " New-AzVirtualNetwork -Name $_.type -ResourceGroupName 'vnet-test' `\n", + " -Location 'eastus2' -AddressPrefix \"$($_.Network)/$($_.cidr)\" | select name, AddressSpace, ResourceGroupName, Location\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь мы готовы добавлять новые диапазоны к существующим сетям. При этом мы хотим максимально использовать пустые места, чтобы не терять адресов. Для этого в модуле есть команда `Get-IPRanges`. Она принимает на вход список занятых диапазонов, \"корневой\" диапазон и размер сети в VLSM нотации и возвращает сети, которые нашла. При этом она пытается использовать сначала незанятые диапазоны и предлагает слот в конце диапазона, если он есть." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\u001b[32;1mIsFree Network AddressFamily Netmask Broadcast FirstUsable LastUsable Usable Total Cid\u001b[0m\r\n", + "\u001b[32;1m r\u001b[0m\r\n", + "\u001b[32;1m------ ------- ------------- ------- --------- ----------- ---------- ------ ----- ---\u001b[0m\r\n", + " True 10.10.0.0 InterNetwork 255.255.252.0 10.10.3.255 10.10.0.1 10.10.3.254 1022 1024 22\r\n", + " False 10.10.5.0 InterNetwork 255.255.255.0 10.10.5.255 10.10.5.1 10.10.5.254 254 256 24\r\n", + " False 10.10.7.0 InterNetwork 255.255.255.0 10.10.7.255 10.10.7.1 10.10.7.254 254 256 24\r\n", + " True 10.10.8.0 InterNetwork 255.255.252.0 10.10.11.255 10.10.8.1 10.10.11.254 1022 1024 22\r\n", + "\r\n" + ] + } + ], + "source": [ + "Get-IPRanges -Networks \"10.10.5.0/24\", \"10.10.7.0/24\" -CIDR 22 -BaseNet \"10.10.0.0/16\" | ft -AutoSize" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В этом примере \"базовая\" сеть `10.10.0.0/16` два занятых диапазона - `10.10.5.0/24`, `10.10.7.0/24` и длина маски `/22`. Команда нашла два свободных слота длиной `/22` - один до занятых сеток, и один после." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь провернем более хитрый трюк. Что если необходимо найти не один, а сразу много диапазонов с заранее известными размерами. Пи этом, как источник уже существующих сетей - занятых, будем использовать сам Azure, ведь он уже хранит всю необходимую нам информацию.\n", + "\n", + "Сделаем сдедующее: \n", + "\n", + "- Создадим список желаемых размеров сеток - `$cidrRange`. Сортируем по-возрастанию, чтобы сначала использовать бОльшие диапазоны, если они есть.\n", + "- Из Azure подтянем список сеток, которые там есть, тех, которые мы считаем занятыми - `$existingRanges`. \n", + "- Для корректного сравнения нам надо привести эти ranges к типу `System.Net.IPNetwork`, который используется внутри модуля, для рассчетов сетей. \n", + "- Теперь нам надо просто бежать по списку желаемых рамеров сеток, просить `Get-IPRanges` найти слоты и аккумулировать их в сиске используемых - строки 10-13\n", + "\n", + "После всего нам остается лишь пометить найденные диапазоны, как свободные, сравних их со списком уже занятых, которые мы подтянули из Azure - строки 2-5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\u001b[32;1mIsFree Network AddressFamily Netmask Broadcast FirstUsable LastUsable Usable Tot\u001b[0m\r\n", + "\u001b[32;1m al\u001b[0m\r\n", + "\u001b[32;1m------ ------- ------------- ------- --------- ----------- ---------- ------ ---\u001b[0m\r\n", + " False 10.172.0.0 InterNetwork 255.255.255.0 10.172.0.255 10.172.0.1 10.172.0.254 254 256\r\n", + " False 10.172.1.0 InterNetwork 255.255.255.0 10.172.1.255 10.172.1.1 10.172.1.254 254 256\r\n", + " True 10.172.2.0 InterNetwork 255.255.254.0 10.172.3.255 10.172.2.1 10.172.3.254 510 512\r\n", + " True 10.172.4.0 InterNetwork 255.255.255.0 10.172.4.255 10.172.4.1 10.172.4.254 254 256\r\n", + " True 10.172.5.0 InterNetwork 255.255.255.0 10.172.5.255 10.172.5.1 10.172.5.254 254 256\r\n", + " True 10.172.6.0 InterNetwork 255.255.255.0 10.172.6.255 10.172.6.1 10.172.6.254 254 256\r\n", + " True 10.172.7.0 InterNetwork 255.255.255.0 10.172.7.255 10.172.7.1 10.172.7.254 254 256\r\n", + " True 10.172.8.0 InterNetwork 255.255.255.128 10.172.8.127 10.172.8.1 10.172.8.126 126 128\r\n", + " True 10.172.8.128 InterNetwork 255.255.255.128 10.172.8.255 10.172.8.129 10.172.8.254 126 128\r\n", + " True 10.172.9.0 InterNetwork 255.255.255.128 10.172.9.127 10.172.9.1 10.172.9.126 126 128\r\n", + " True 10.172.9.128 InterNetwork 255.255.255.192 10.172.9.191 10.172.9.129 10.172.9.190 62 64\r\n", + " True 10.172.9.192 InterNetwork 255.255.255.192 10.172.9.255 10.172.9.193 10.172.9.254 62 64\r\n", + "\r\n" + ] + } + ], + "source": [ + "$cidrRange = 25,25,24,24,24,24,23,25,26,26 | sort\n", + "$existingRanges = (Get-AzVirtualNetwork -ResourceGroupName vnet-test | \n", + " select name, @{l = \"AddressSpace\"; e = { $_.AddressSpace.AddressPrefixes }}, ResourceGroupName, Location |\n", + " select -expand AddressSpace)\n", + "$existingNetworks = $existingRanges | % {[System.Net.IPNetwork]$_}\n", + "$nets = $existingRanges\n", + "\n", + "$ret = @()\n", + "\n", + "$cidrRange | % {\n", + " $ret = Get-IPRanges -Networks $nets -CIDR $_ -BaseNet \"10.172.0.0/16\"\n", + " $nets = ($ret | select @{l=\"range\"; e = {\"$($_.network)/$($_.cidr)\"}}).range\n", + "}\n", + "\n", + "$ret | % {\n", + " if ( -not ($_ -in $existingNetworks)) {$_.IsFree = $true}\n", + "}\n", + "\n", + "$ret | ft -AutoSize" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
typeNetworkAddressFamilyNetmaskBroadcastFirstUsableLastUsableUsableTotalCidr
reserved10.172.128.0InterNetwork255.255.128.010.172.255.25510.172.128.110.172.255.254327663276817
reserved10.172.64.0InterNetwork255.255.192.010.172.127.25510.172.64.110.172.127.254163821638418
reserved10.172.32.0InterNetwork255.255.224.010.172.63.25510.172.32.110.172.63.2548190819219
reserved10.172.24.0InterNetwork255.255.248.010.172.31.25510.172.24.110.172.31.2542046204821
VNET-79510.172.0.0InterNetwork255.255.254.010.172.1.25510.172.0.110.172.1.25451051223
reserved10.172.23.0InterNetwork255.255.255.010.172.23.25510.172.23.110.172.23.25425425624
reserved10.172.21.0InterNetwork255.255.255.010.172.21.25510.172.21.110.172.21.25425425624
reserved10.172.19.0InterNetwork255.255.255.010.172.19.25510.172.19.110.172.19.25425425624
reserved10.172.17.0InterNetwork255.255.255.010.172.17.25510.172.17.110.172.17.25425425624
reserved10.172.15.0InterNetwork255.255.255.010.172.15.25510.172.15.110.172.15.25425425624
reserved10.172.13.0InterNetwork255.255.255.010.172.13.25510.172.13.110.172.13.25425425624
reserved10.172.9.0InterNetwork255.255.255.010.172.9.25510.172.9.110.172.9.25425425624
reserved10.172.7.0InterNetwork255.255.255.010.172.7.25510.172.7.110.172.7.25425425624
reserved10.172.11.0InterNetwork255.255.255.010.172.11.25510.172.11.110.172.11.25425425624
reserved10.172.3.0InterNetwork255.255.255.010.172.3.25510.172.3.110.172.3.25425425624
reserved10.172.5.0InterNetwork255.255.255.010.172.5.25510.172.5.110.172.5.25425425624
VNET-69110.172.10.0InterNetwork255.255.255.010.172.10.25510.172.10.110.172.10.25425425624
VNET-37310.172.8.0InterNetwork255.255.255.010.172.8.25510.172.8.110.172.8.25425425624
VNET-74910.172.12.0InterNetwork255.255.255.010.172.12.25510.172.12.110.172.12.25425425624
VNET-80310.172.6.0InterNetwork255.255.255.010.172.6.25510.172.6.110.172.6.25425425624
VNET-6610.172.4.0InterNetwork255.255.255.010.172.4.25510.172.4.110.172.4.25425425624
VNET-29010.172.2.0InterNetwork255.255.255.010.172.2.25510.172.2.110.172.2.25425425624
reserved10.172.16.128InterNetwork255.255.255.12810.172.16.25510.172.16.12910.172.16.25412612825
reserved10.172.18.128InterNetwork255.255.255.12810.172.18.25510.172.18.12910.172.18.25412612825
reserved10.172.20.128InterNetwork255.255.255.12810.172.20.25510.172.20.12910.172.20.25412612825
VNET-85610.172.14.0InterNetwork255.255.255.12810.172.14.12710.172.14.110.172.14.12612612825
reserved10.172.22.128InterNetwork255.255.255.12810.172.22.25510.172.22.12910.172.22.25412612825
VNET-51710.172.16.0InterNetwork255.255.255.12810.172.16.12710.172.16.110.172.16.12612612825
VNET-53510.172.18.0InterNetwork255.255.255.12810.172.18.12710.172.18.110.172.18.12612612825
reserved10.172.14.128InterNetwork255.255.255.12810.172.14.25510.172.14.12910.172.14.25412612825
reserved10.172.20.64InterNetwork255.255.255.19210.172.20.12710.172.20.6510.172.20.126626426
reserved10.172.22.64InterNetwork255.255.255.19210.172.22.12710.172.22.6510.172.22.126626426
VNET-14910.172.20.0InterNetwork255.255.255.19210.172.20.6310.172.20.110.172.20.62626426
VNET-10810.172.22.0InterNetwork255.255.255.19210.172.22.6310.172.22.110.172.22.62626426
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "$s = @()\n", + "$ret | % {\n", + " $s += @{type = \"VNET-$(Get-Random -Maximum 1000 -Minimum 10)\"; size = ($_.total - 2)}\n", + "}\n", + "\n", + "\n", + "$view = Get-VLSMBreakdown -Network 10.172.0.0/16 -SubnetSize $s | sort cidr | ConvertTo-Html -Fragment\n", + "[Microsoft.DotNet.Interactive.Kernel]::HTML($view) | Out-Display" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "file_extension": ".cs", + "mimetype": "text/x-csharp", + "name": "C#", + "pygments_lexer": "csharp", + "version": "9.0" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 870c30d0adcbde28e9a5e4270d8de266c0c1b9fe Mon Sep 17 00:00:00 2001 From: eosfor Date: Tue, 4 Jan 2022 22:16:24 -0800 Subject: [PATCH 3/7] + readme --- NOTEBOOKS/ru/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 NOTEBOOKS/ru/README.md diff --git a/NOTEBOOKS/ru/README.md b/NOTEBOOKS/ru/README.md new file mode 100644 index 0000000..5a68952 --- /dev/null +++ b/NOTEBOOKS/ru/README.md @@ -0,0 +1,15 @@ +# Как пощупать ноутбуки + +Для того чтобы попробовать ноутбуки вам понадобится: + +- [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) +- [Visual Studio Code](https://code.visualstudio.com/) +- [.NET Interactive Notebooks for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) + +После того как се установлено, вам нужно: + +- Склонировать репозиторий +- Открыть соответсвующую папку в VSCode +- Начать эксперименты! + +Удачных экспериментов! \ No newline at end of file From fba90c7d17567ef4d688a67f08d7dc0caef2eed9 Mon Sep 17 00:00:00 2001 From: eosfor Date: Tue, 4 Jan 2022 22:20:34 -0800 Subject: [PATCH 4/7] + comment --- NOTEBOOKS/ru/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NOTEBOOKS/ru/README.md b/NOTEBOOKS/ru/README.md index 5a68952..2ec78af 100644 --- a/NOTEBOOKS/ru/README.md +++ b/NOTEBOOKS/ru/README.md @@ -1,5 +1,7 @@ # Как пощупать ноутбуки +Содержимое ноутбуков можно посмотреть прямо на github, он умеет их показывать. + Для того чтобы попробовать ноутбуки вам понадобится: - [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) @@ -12,4 +14,4 @@ - Открыть соответсвующую папку в VSCode - Начать эксперименты! -Удачных экспериментов! \ No newline at end of file +Удачных экспериментов! From ad75c091e5064636b399882c11ae57c1576f84c0 Mon Sep 17 00:00:00 2001 From: eosfor Date: Tue, 4 Jan 2022 22:44:29 -0800 Subject: [PATCH 5/7] spelling --- NOTEBOOKS/ru/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTEBOOKS/ru/README.md b/NOTEBOOKS/ru/README.md index 2ec78af..d360cf5 100644 --- a/NOTEBOOKS/ru/README.md +++ b/NOTEBOOKS/ru/README.md @@ -8,7 +8,7 @@ - [Visual Studio Code](https://code.visualstudio.com/) - [.NET Interactive Notebooks for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) -После того как се установлено, вам нужно: +После того как все установлено, вам нужно: - Склонировать репозиторий - Открыть соответсвующую папку в VSCode From 14f5bd62e3aa4fa386f456517136d844444e0b06 Mon Sep 17 00:00:00 2001 From: Vadzim Choparau <42169334+tolstyiii@users.noreply.github.com> Date: Wed, 5 Jan 2022 10:48:32 +0100 Subject: [PATCH 6/7] Update ipmgmt.ipynb --- NOTEBOOKS/ru/ipmgmt.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTEBOOKS/ru/ipmgmt.ipynb b/NOTEBOOKS/ru/ipmgmt.ipynb index b51926c..4ec1eb2 100644 --- a/NOTEBOOKS/ru/ipmgmt.ipynb +++ b/NOTEBOOKS/ru/ipmgmt.ipynb @@ -13,7 +13,7 @@ "source": [ "**Сценарий**\n", "\n", - "Предположим, что для всего, что мы планируем запускать в Azure, network team выделила нам диапазон адресов `10.172.0.0/26`. При этом мы пытаемся сделать автоматизированный механизм, который, получив на вход желаемый размер или список размеров сетей, сможет найти свободные блоки из указанного диапазона и рассчитать подсети и маски. Затем эти подсети и маски мы сможем использовать для создания сетей в Azure.\n", + "Предположим, что для всего, что мы планируем запускать в Azure, network team выделила нам диапазон адресов `10.172.0.0/16`. При этом мы пытаемся сделать автоматизированный механизм, который, получив на вход желаемый размер или список размеров сетей, сможет найти свободные блоки из указанного диапазона и рассчитать подсети и маски. Затем эти подсети и маски мы сможем использовать для создания сетей в Azure.\n", "\n", "Эти примеры можно затем использовать при автоматизации создания того, что Microsoft называет [Landing Zone](https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/), в части автоматического рассчета диапазонов сетей" ] From b0be7a8295baa6a18ba86a6baf78a7a0572faa07 Mon Sep 17 00:00:00 2001 From: eosfor Date: Thu, 6 Jan 2022 23:06:38 -0800 Subject: [PATCH 7/7] sysmon-events-notebook --- NOTEBOOKS/ru/analyze-sysmon-events.ipynb | 413 +++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 NOTEBOOKS/ru/analyze-sysmon-events.ipynb diff --git a/NOTEBOOKS/ru/analyze-sysmon-events.ipynb b/NOTEBOOKS/ru/analyze-sysmon-events.ipynb new file mode 100644 index 0000000..576dbd8 --- /dev/null +++ b/NOTEBOOKS/ru/analyze-sysmon-events.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Анализ связей между приложениями с использованием SysMon" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Сценарий\n", + "\n", + "У нас есть один или несколько серверов, и мы хотим знать, как запущенные на них приложения связаны между собой. Как они взаимодействуют по сети. Хотим увидеть граф связей между приложениями." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Эксперимент" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В рамках эксперимента мы будем использовать следующие инструменты:\n", + "\n", + "- [Sysmon](https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon) - иструмент для мониторинга за активностью всего. Кстати есть и [Sysmon for Linux](https://github.com/Sysinternals/SysmonForLinux)\n", + "- Конфигурационные файлы для Sysmon [отсюда](https://github.com/SwiftOnSecurity/sysmon-config). Это конфигурация по-умолчанию, для ваших целей можно сделать свою\n", + "\n", + "Попросту говоря, [Sysmon](https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon) это системный сервис + дравер, устанавливаемый в систему. Он умеет перехватывать различные операции, происходящие в системе и логировать их в EventLog. Параметр `-i` принимает путь к конфигурационному файлу, в котором можно задавать фильтрацию того, чего вы хотите или не хотите видеть в логе. Кроме всего прочего Sysmon видит сетевые подключения, которые тот или иной процесс пытается установит с удаленными системами. Таким образом мы можем узнать какой процесс, когда и куда ходил, по какому протоколу и на какие порты пытался отправлять свои пакеты. Имея эти данные с одной машины мы можем увидеть процессы и их сетевые взаимодействия. Собрав данные с нескольких машин можно увидеть связи между ними.\n", + "\n", + "В этом эксперименте мы попробуем провернуть это все на локальной машине." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Подготовка\n", + "\n", + "Прежде всего установим Sysmon вмест с его конфигом. Ну, то есть, скачаем, запустим процесс cmd с повышенными привилегиями, чтобы система дала установить драймер, и пеенаправим вывод в файл, чтобы увидеть, что же вернет sysmon. Поскольку он запускается с повышенными привилегиями это, наверное, единственный способ получить его вывод." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$resFileName = \"sysmonInstallResult.txt\"\n", + "\n", + "# качнем в TEMP по-умолчанию\n", + "Invoke-WebRequest -Uri \"https://live.sysinternals.com/Sysmon.exe\" -OutFile \"$($env:TEMP)\\sysmon.exe\"\n", + "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml\" -OutFile \"$($env:TEMP)\\sysmonconfig-export.xml\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ели непонятно, поясняю что делает эта команда. Она запускает `cmd.exe`, который расположен по пути из `$env:comspec`, и передает ему примерно вот такое `/c %TEMP%\\sysmon.exe -i %TEMP%\\sysmonconfig-export.xml -accepteula > %TEMP%\\sysmonInstallResult.txt`. Только вместо `%TEMP%` полные пути из `$env:TEMP`. Таким образом при запуске стартанет процесс `cmd.exe`, среагирует UAC, покажет вам окно и спросит разрешения, и дальше запустится сам sysmon. Его вывод будет перенаправлен туда, куда мы указали, за счет оператора `>`. Затем мы этот вывод читаем и выводим, чтобы понять, сработало или нет." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "# запустим процесс с повышенными привилегиями и оператором перенаправления\n", + "Start-Process -FilePath $env:comspec -ArgumentList (\"/c\", \"$($env:TEMP)\\sysmon.exe\", \"-i\", \"$($env:TEMP)\\sysmonconfig-export.xml\", \"-accepteula\", \">\", \"$($env:TEMP)\\$resFileName\") `\n", + " -Verb runas\n", + "\n", + "gc \"$($env:TEMP)\\$resFileName\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь нужно добавить себя в группу \"Event Log Readers\" если вы еще не там, на всякий случай. .NET Interactive notebooks в VSCode отчего-то не работают, когда VSCode запущен с повышеными привилегиями. После этого все равно придется перелогиниться, поэтому это действие можно проделать руками." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Игра\n", + "\n", + "Если все прошло хорошо - мы готовы. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Установим модуль, который умеет работать с графами. На данный момент доступна только alpha версия, но она, вроде, работает. Сам модуль расположен [тут](https://github.com/eosfor/PSGraph/tree/master). Ветка `dev`, из которой периодически собирается alpha [тут](https://github.com/eosfor/PSGraph/tree/dev). Версия в ветке `master` тоже рабочая, но она не умеет многое из того что нам нужно." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Install-Module -Name PSQuickGraph -AllowPrerelease -RequiredVersion \"2.0.1-alpha\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Импортнем его" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Import-Module PSQuickGraph -RequiredVersion \"2.0.1\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь, собственно, самое интересное. Нам нужно прочитать события из журнала событий, построить на них граф и нарисовать его, чем и займемся. Прежде всего, прочитаем журнал. Sysmon пишет события в `Microsoft-Windows-Sysmon/Operational`. Нас интересуют события с `id=3`. Это - попытки установления сетевых соединений процессами. Выгрузив их из журнала событий мы преобразум их в массив объектов, разбирая на именованные свойства, чтобы с ними было проще работать позже. Каждый объект, кроме всего прочего содержит source и destination маркеры. Source маркер это `<имя процесса>:`, а destination - `:<порт назначения>`. Адреса используются потому, что DNS имена не всегда известны. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$ids = Get-WinEvent -LogName Microsoft-Windows-Sysmon/Operational | ? {$_.id -eq 3}\n", + "$commObjects = $ids | % {\n", + " New-Object psobject -Property @{ \n", + " RuleName = $_.Properties[0].value\n", + " UtcTime = $_.Properties[1].value\n", + " ProcessGuid = $_.Properties[2].value\n", + " ProcessId = $_.Properties[3].value\n", + " Image = $_.Properties[4].value\n", + " User = $_.Properties[5].value\n", + " Protocol = $_.Properties[6].value\n", + " Initiated = $_.Properties[7].value\n", + " SourceIsIpv6 = $_.Properties[8].value\n", + " SourceIp = $_.Properties[9].value\n", + " SourceHostname = $_.Properties[10].value\n", + " SourcePort = $_.Properties[11].value\n", + " SourcePortName = $_.Properties[12].value\n", + " DestinationIsIpv6 = $_.Properties[13].value\n", + " DestinationIp = $_.Properties[14].value\n", + " DestinationHostname = $_.Properties[15].value\n", + " DestinationPort = $_.Properties[16].value\n", + " DestinationPortName = $_.Properties[17].value\n", + " SourceString = \"$($_.Properties[4].value)`:$($_.Properties[3].value)\" # <имя процесса>:\n", + " DestinationString = \"$($_.Properties[14].value)`:$($_.Properties[16].value)\" # :<порт назначения>\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь можно построить граф взаимодействий. Для этого надо сначала создать пустой граф командой `New-Graph`, а затем наполнить его. Чтобы это сделать мы просто бежим по всем объектам и добавляем в граф ребра от `source` маркера в `destination` маркер. При этом, команда умеет понять, существуют в графе соответствующие вершины или нет. Если вершин нет - они добавляются и между ними создается направленное ребро. Если вершины есть, то новые не создаются, просто добавляется еще одно ребро. Таким образом при создании графа не нужно думать добавляли мы такую вершину или нет, команда сделает все сама, просто подавайте данные." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$g = New-Graph\n", + "$commObjects | % {\n", + " Add-Edge -From $_.SourceString -To $_.DestinationString -Graph $g\n", + "}\n", + "\n", + "$g" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Граф собран. Покрасим некоторые вершины. Мы хотим выделить одним цветом те вершины, которые используются несколькими процессами. Другими словами, если, к одному и тому же `destination` обращаются с течением времени несколько разных `source`, то в эту вершину будет входить больше чем одно ребро. Другим цветом мы хотим покрасить вершины, у которых нет входящих ребер. Но чтобы не слишком перегружать картинку, выберем только те, у которых 0 входящих и больше двух исходящих ребер.\n", + "\n", + "Для этого нам нужно просто пробежать по всем вершинам графа, для каждой из них выяснить количество входящих и исходящих ребер и задать соответствующие цвета этой вершине" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "$g.Vertices | % { if ($g.InDegree($_) -gt 1) { $_.GVertexParameters.Fillcolor = [QuikGraph.Graphviz.Dot.GraphvizColor]::AntiqueWhite } }\n", + "$g.Vertices | % { if ( ($g.InDegree($_) -eq 0) -and ($g.OutDegree($_) -gt 2) ) { $_.GVertexParameters.Fillcolor = [QuikGraph.Graphviz.Dot.GraphvizColor]::BlueViolet } }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ну и наконец мы можем экспортировать граф. Это можно сделать в нескольких различных форматах. Первый из них, `Graphviz`. Это, так называемый, [dot](https://www.graphviz.org/doc/info/lang.html) формат, текстовый формат используемый утилитой [graphviz](https://www.graphviz.org/) и некоторыми другими, для хранения графов. Другой формат, который нас интересует, `MSAGL_MDS`. В этом случае создается SVG визуализация средствами библиотеки [MSAGL](https://github.com/microsoft/automatic-graph-layout). Команда поддерживат еще несколько форматов, но о них в другой раз." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "Export-Graph -Graph $g -Path \"$($env:TEMP)\\comms.svg\" -Format MSAGL_MDS\n", + "Export-Graph -Graph $g -Path \"$($env:TEMP)\\comms.gv\" -Format Graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь нам придется написать пару строк на C#. На данный момент .NET Interactive ноутбуки не умеют взять, и просто показать картинку. Нужно использовать extension methods, а использовать их из PowerShell не очень удобно. Собственно здесь нам всего-то и надо прочитать два файли и дернуть extension method предоставляемый .NET Interactive kernel для того, чтобы отобразить SVG картинку. Однако граф большой, картинка в MSAGL получается большая, по понятным причинам, и смотреть ее в выводе самого ноутбука неудобно" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "using System.IO;\n", + "\n", + "var path = Path.GetTempPath();\n", + "var svg = File.ReadAllText($\"{path}\\\\comms.svg\");\n", + "var gv = File.ReadAllText($\"{path}\\\\comms.gv\");\n", + "svg.DisplayAs(\"text/html\"); // эта строка покажет большую картинку, которая не влезает в секцию вывода" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Поэтому ее можно просто открыть в соседнем окне браузера. Там можно приближать, удалять, двигать." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + } + }, + "outputs": [], + "source": [ + "& \"$($env:TEMP)\\comms.svg\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Но у нас есть еще один, более интерактивный способ отобразить граф - javascript и библиотека [vis.js](https://visjs.org/) например. Мне удалось быстро найти примеры ипользования и адаптировать их. Но есть и другие варианты" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Первым делом надо создать место, в которое будет выведен граф. По-умолчанию создается слишком маленький контейнер, и такой большой граф в нем смотреть очень неудобно. Поэтому я просто, руками, задаю размер в 800px." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "html" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#!html\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Теперь короткий кусок жабаскрипта. Но в этой ячейке есть еще кое-что интересное. .NET Interactive позволяет делиться переменными между разными языками. В данном случае мы прочитали файл в переменную `gv` в куске C# кода выше, и передаем ее значение прямо в javascript код. Для этого в начале ячейки просто говорим `#!share --from csharp gv`. Этот кусок кода показывает нам наш граф, отрисованный с помошью `forceAtlas2Based` алгоритма. Рассчет занимает какое-то время, поэтому надо подождать, может, секунд 30. Кроме того в vis.js есть багофича. После отрисовки, при включенной физике, граф никак не может прийти к стабильному состоянию. Поэтому в этом режиме \"картинка\" дергается, некоторые вершины перемещаются с места на место, смотреть неприятно. Единственное, что они рекомендуют, это задать вручную количество итераций и подождать - `network.stabilize(600)`. Можно попытаться побаловаться этим числом, авось на самом деле нужно меньше итераций. Я просто жду :)\n", + "\n", + "В итоге мы видим граф с вершинами трех цветов, два из них мы задали сами. Напомню, что белым цветом мы красили те вершины, которые используются сразу несколькими процессами, а фиолетовым - те, которые являются, как бы, корневыми, из них есть только исходящие ребра, но нет входящих. Это позволяет нам визуально наблюдать общие елементы двух \"подсистем\" - они белые, и \"корни\" этих \"подсистем\" - они фиолетовые. Граф можно приближать и удалять, таскать мышкой и так далее, что очень удобно." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "javascript" + } + }, + "outputs": [], + "source": [ + "#!js\n", + "#!share --from csharp gv\n", + "\n", + "visRequire = interactive.configureRequire({\n", + " paths: {\n", + " visjs: \"https://visjs.github.io/vis-network/standalone/umd/vis-network.min\"\n", + " }\n", + "});\n", + " \n", + "visRequire([\"visjs\"], visjs => {\n", + " \n", + " var container = document.getElementById(\"mynetwork\");\n", + " var dot = gv;\n", + " var parsedData = visjs.parseDOTNetwork(dot);\n", + "\n", + " var data = {\n", + " nodes: parsedData.nodes,\n", + " edges: parsedData.edges\n", + " };\n", + " var options = parsedData.options;\n", + " options = {\n", + " physics: {\n", + " solver: 'forceAtlas2Based',\n", + " enabled: false\n", + " },\n", + " interaction: { hover: true, zoomView: true },\n", + " layout: { randomSeed: 'Mickey' }\n", + " }\n", + "\n", + " var network = new visjs.Network(container, data, options); \n", + " network.stabilize(600)\n", + "});" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "file_extension": ".cs", + "mimetype": "text/x-csharp", + "name": "C#", + "pygments_lexer": "csharp", + "version": "9.0" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +}