<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Linux on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/linux/</link>
    <description>Recent content in Linux on 0x2142 | Networking Nonsense</description>
    <image>
      <title>0x2142 | Networking Nonsense</title>
      <url>https://0x2142.com/logo.jpg</url>
      <link>https://0x2142.com/logo.jpg</link>
    </image>
    <generator>Hugo -- 0.143.1</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 18 Apr 2024 12:00:00 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/linux/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[How To] Set up Cisco Modeling Labs (CML) on Proxmox</title>
      <link>https://0x2142.com/how-to-setup-cml-on-proxmox/</link>
      <pubDate>Thu, 18 Apr 2024 12:00:00 +0000</pubDate>
      <guid>https://0x2142.com/how-to-setup-cml-on-proxmox/</guid>
      <description>In this blog post, we&amp;rsquo;ll walk through how to set up &amp;amp; install Cisco Modeling Labs (CML) on Proxmox</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Ajpi_vVTtLc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>With a lot of the recent nonsense going on after VMware was acquired last year, I&rsquo;m seeing people start to look for ESX alternatives - especially for home labs. In fact, I also just <a href="https://www.youtube.com/watch?v=MS4ERrvG9eM">migrated</a> my home lab from ESX to Proxmox.</p>
<p>One of the things I needed to keep running was Cisco Modeling Labs (CML). I often use CML to build quick virtual topologies for testing things, so it was important for me that it worked on Proxmox.</p>
<p>It&rsquo;s worth noting that Proxmox isn&rsquo;t an officially supported platform. The <a href="https://developer.cisco.com/docs/modeling-labs/#!system-requirements">docs</a> state that only bare metal &amp; VMware platforms are supported.</p>
<p>That being said, I&rsquo;m happy to say that Proxmox has worked just fine for me - it just needed a few tweaks. Though just be warned that you may still run into issues and it is technically an unsupported configuration.</p>
<p>In the post below, I&rsquo;ll share the steps that I used to get CML up &amp; running on Proxmox.</p>
<hr>
<h2 id="prerequisites">Prerequisites</h2>
<p>To start with, we&rsquo;ll need a few things:</p>
<ul>
<li>A server running Proxmox
<ul>
<li>I&rsquo;m currently using version 8.1.3</li>
</ul>
</li>
<li>A CML install ISO, found <a href="https://software.cisco.com/download/home/286193282/type/286326381/release/2.6.1">here</a>
<ul>
<li>We&rsquo;ll be using version 2.6.1 for this guide</li>
</ul>
</li>
<li>CML&rsquo;s reference platform ISO, also found at the link above</li>
</ul>
<p>Our Proxmox machine will still need enough resources to build a VM with the minimum requirements listed <a href="https://developer.cisco.com/docs/modeling-labs/#!system-requirements">here</a>.</p>
<p>The CML &amp; refplat ISOs should be placed into a Proxmox ISO storage location.</p>
<h2 id="creating-the-vm">Creating the VM</h2>
<p>Next we can get to the fun part: Setting up the VM in Proxmox.</p>
<p>We&rsquo;ll first locate a node in Proxmox to place our VM. Then right-click that node &amp; select <strong>Create VM</strong>:</p>
<p><img alt="new-vm" loading="lazy" src="/how-to-setup-cml-on-proxmox/new-vm.png#center"></p>
<p>Then, we&rsquo;ll give our VM a name &amp; ID. I&rsquo;ve named mine <strong>CML</strong>:</p>
<p><img alt="vm-general" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-general.png#center"></p>
<p>On the next page, we can select our CML ISO from the correct storage device. We can leave the Guest OS as <strong>Linux</strong> and <strong>6.x - 2.6 Kernel</strong>:</p>
<p><img alt="vm-os" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-os.png#center"></p>
<p>Next, we&rsquo;ll need to make some minor adjustments on the <strong>System</strong> tab.</p>
<p>The default BIOS will likely be set to <strong>Default (SeaBIOS)</strong>. We&rsquo;ll change that to <strong>OVMF (UEFI)</strong>, which will also give us a few additional options.</p>
<p>We&rsquo;ll keep <strong>Add EFI Disk</strong> checked, and select a storage location for that EFI disk.</p>
<p><img alt="vm-system" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-system.png#center"></p>
<p>On the <strong>Disks</strong> tab, feel free to increase the size of the VM disk. While 32GB is the minimum required, you&rsquo;ll likely want more than that. I&rsquo;ve increased mine to 50G for now. Keep the image format as <strong>QEMU</strong>.</p>
<p>We&rsquo;ll also want to update the <strong>Async IO</strong> setting to <strong>native</strong>. This is hidden under the <strong>Advanced</strong> settings:</p>
<p><img alt="vm-disk" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-disk.png#center"></p>
<p>On the <strong>CPU</strong> tab, we&rsquo;ll update the core count to a minimum of 4 per the requirements. Of course, you&rsquo;re welcome to increase this as needed.</p>
<p>More importantly however, we&rsquo;ll need to set the CPU <strong>type</strong> to <strong>host</strong>. This allows the VM to use the underlying nested virtualization features:</p>
<p><img alt="vm-cpu" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-cpu.png#center"></p>
<p>Next we can assign memory to our VM.</p>
<p>The requirements state a minimum of 8GB, however this will likely only accommodate simpler labs. Some individual CML nodes require more than that to start.</p>
<p>In my case, I&rsquo;ll start with 32GB - and we can always increase this later:</p>
<p><img alt="vm-memory" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-memory.png#center"></p>
<p>Lastly, on the <strong>Network</strong> tab, no changes are necessary. Of course, you can assign a VLAN if needed.</p>
<p><img alt="vm-network" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-network.png#center"></p>
<p>After that, we can head over to the <strong>Confirm</strong> tab &amp; finish up the wizard.</p>
<h2 id="add-refplat-iso">Add Refplat ISO</h2>
<p>Next, we&rsquo;ll need to make sure our reference platform ISO is connected to the VM.</p>
<p>We&rsquo;ll head over to the VM&rsquo;s hardware tab, then click <strong>Add</strong> and select <strong>CD/DVD Drive</strong>:</p>
<p><img alt="vm-add-cdrom" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-add-cdrom.png#center"></p>
<p>Then we&rsquo;ll select the appropriate ISO storage, and pick our refplat ISO file:</p>
<p><img alt="vm-add-refplat" loading="lazy" src="/how-to-setup-cml-on-proxmox/vm-add-refplat.png#center"></p>
<h2 id="install-cml">Install CML</h2>
<p>Once that&rsquo;s done, we can go ahead and power on our VM &amp; pop open the console!</p>
<p>If you&rsquo;ve installed CML previously on another platform, this process is just the same.</p>
<p><img alt="con-install" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-install.png#center"></p>
<p>At boot, we&rsquo;ll select <strong>Install CML</strong>.</p>
<p>After a moment &amp; a few screens, we&rsquo;ll start the system configuration.</p>
<p>First, we&rsquo;ll input a system hostname:</p>
<p><img alt="con-hostname" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-hostname.png#center"></p>
<p>Then, we&rsquo;ll input credentials for the system management user.</p>
<blockquote>
<p>Note: This user isn&rsquo;t used to log into the CML software. Rather, it&rsquo;s used for system administration tasks via the <a href="https://cockpit-project.org/">Cockpit UI</a>, such as: Installing updates, checking logs, joining a domain, or powering off / rebooting the system.</p></blockquote>
<p><img alt="con-sysadmin" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-sysadmin.png#center"></p>
<p>After that, we can configure our CML admin user:</p>
<p><img alt="con-cmladmin" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-cmladmin.png#center"></p>
<p>Next we have our network config. By default, CML will prompt to use DHCP, but we&rsquo;ll likely want to change this to a static IP assignment:</p>
<p><img alt="con-networktype" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-networktype.png#center"></p>
<p><img alt="con-staticip" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-staticip.png#center"></p>
<p>After that, CML will prompt us to confirm our settings - then it will begin the installation. Since a lot of the VM images come from the reference platform ISO, this process may take a while as each of those images are copied over to the system.</p>
<p>When CML is ready, we&rsquo;ll see something similar on the console telling us how to reach the web UI:</p>
<p><img alt="con-alldone" loading="lazy" src="/how-to-setup-cml-on-proxmox/con-alldone.png#center"></p>
<p>Again for reference, the main UI is reachable at <code>https://&lt;CML IP&gt;</code> and the Cockpit management UI is at <code>https://&lt;CML IP&gt;:9090</code>.</p>
<p>At this point, we can log in &amp; start building labs!</p>
<hr>
<p>The process for setting up CML on Proxmox isn&rsquo;t super crazy, but there are just a few tweaks that need to be made during setup. I wrote this hoping that if anyone else out there is trying to set this up, maybe it would help.</p>
<p>As always, Thanks for reading!</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Setup a Satisfactory Dedicated Server on Debian Linux</title>
      <link>https://0x2142.com/how-to-set-up-a-satisfactory-dedicated-game-server/</link>
      <pubDate>Sat, 04 Dec 2021 18:07:40 +0000</pubDate>
      <guid>https://0x2142.com/how-to-set-up-a-satisfactory-dedicated-game-server/</guid>
      <description>In a brief detour from my usual content, we&amp;rsquo;&amp;rsquo;ll take a quick look at how to build a new Satisfactory dedicated server on Debian Linux.</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/3hpeP7JVtDY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Okay, so this post is ever-so-slightly off-topic from what I usually write about here - but I&rsquo;m allowed to have fun sometimes, right?</p>
<p>With the holidays coming up, I&rsquo;m getting ready to take some time off &amp; relax with my favorite video games.</p>
<p>And just in time, Satisfactory just released their new <strong>Update 5</strong> content - which finally adds support for dedicated servers. So I&rsquo;ll be able to more easily play with friends &amp; family who are in different time zones.</p>
<p>This will be a quick guide, mostly derived from the <a href="https://satisfactory.fandom.com/wiki/Dedicated_servers">Satisfactory Wiki</a> with some additional steps/context.</p>
<blockquote>
<p>Disclaimer: The dedicated server software is pretty new &amp; still a work in progress, so I imagine there is a good chance some of these steps may change. I&rsquo;ll do my best to keep it updated!</p></blockquote>
<hr>
<h2 id="a-quick-note">A Quick Note</h2>
<p>Before getting started, just a quick note about what is used for this setup &amp; what assumptions are made.</p>
<p>This guide will walk through building a new dedicated server on Debian 11. There is additional information regarding other Linux distributions &amp; Windows in the Satisfactory Wiki page linked above.</p>
<p>This guide assumes that you have some fairly basic level of Linux knowledge. You should be comfortable with connecting to a Linux server via SSH and editing files. If you need additional guidance, please consider checking out the video above where I walk through the whole process!</p>
<p>This guide will not look at installing Linux, or setting up port forwarding, etc. We will only be focusing on getting the dedicated server software installed &amp; running.</p>
<h2 id="setting-up-static-networking">Setting up Static Networking</h2>
<p>By default my Debian 11 install was configured with a dynamic / DHCP address. While this might work for other use cases, we&rsquo;ll want to set a static IP address for our game server.</p>
<p>So first we&rsquo;ll open up our network config file, using the following command: <code>sudo nano /etc/network/interfaces</code></p>
<p>Then, we&rsquo;ll update our network config to look like the one below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">auto ens192
</span></span><span class="line"><span class="cl">iface ens192 inet static
</span></span><span class="line"><span class="cl"> address 192.168.1.20
</span></span><span class="line"><span class="cl"> netmask 255.255.255.0
</span></span><span class="line"><span class="cl"> gateway 192.168.1.1
</span></span><span class="line"><span class="cl"> dns-nameservers 8.8.8.8
</span></span></code></pre></div><p>Please note, the values listed above are for example only. You&rsquo;ll need to assign the correct addressing from your own local network. If you&rsquo;re unsure about the interface name (mine is <code>ens192</code>), you can use the command <code>ip address</code> to list current network interfaces &amp; find yours.</p>
<p>Once we&rsquo;ve edited our network configuration, we&rsquo;ll need to restart the network process for the changes to take effect: <code>sudo systemctl restart networking</code></p>
<blockquote>
<p>Note: Assuming the static IP you configure is different than the current dynamically-assigned IP, you will lose connection with your server &amp; need to reconnect using the new IP address.</p></blockquote>
<h2 id="install-steamcmd">Install Steamcmd</h2>
<p>Next, we&rsquo;ll need to install the Steam commandline utility so we can download &amp; manage our server.</p>
<p>On Debian, the default repositories don&rsquo;t include this package. So we&rsquo;ll need to add a third-party package repository.</p>
<p>We&rsquo;ll edit our active/configured repositories with the following command: <code>sudo nano /etc/apt/sources.list</code></p>
<p>Then add the following at the end of the file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">deb http://mirrors.linode.com/debian bullseye main non-free
</span></span><span class="line"><span class="cl">deb-src http://mirrors.linode.com/debian bullseye main non-free
</span></span></code></pre></div><p>We&rsquo;ll also need to enable multiarchitecture support: <code>sudo dpkg --add-architecture i386</code></p>
<p>Once that&rsquo;s done, we&rsquo;ll update our local cache to include the newly available list of packages: <code>sudo apt-get update</code></p>
<p>Then we can install steamcmd: <code>sudo apt-get install steamcmd</code></p>
<p>We will likely want to create a dedicated user to run our game server. We&rsquo;ll create a user called <code>steam</code> using the following command: <code>sudo useradd -m steam</code></p>
<p>Then we can switch to our <code>steam</code> user account: <code>su steam</code></p>
<p>Optionally, we can create a shortcut to the steamcmd executable within the steam user&rsquo;s home directory: <code>ln -s /usr/games/steamcmd steamcmd</code></p>
<h2 id="download--install-satisfactory-dedicated-server">Download / Install Satisfactory Dedicated Server</h2>
<p>Now we can pull down a copy of the server software.</p>
<p>Assuming you&rsquo;ve created the steamcmd shortcut as shown above, we can use the following command to pull down the server software:
<code>./steamcmd +force_install_dir ~/satisfactory +login anonymous +app_update 1690800 validate +quit</code></p>
<p>This command does a few things:</p>
<ul>
<li>+force_install_dir - Tells steamcmd which directory to place the game files in, in this case ~/satisfactory</li>
<li>+login anonymous - Skip logging in with a steam user account, since we just need to download an update</li>
<li>+app_update 1690800 - This is the Satisfactory server application ID, so steamcmd knows which software to install</li>
<li>validate - Runs a validation to ensure that the current files match what&rsquo;s hosted on Steam</li>
<li>+quit - Asks steamcmd to log off of Steam when everything is finished.</li>
</ul>
<p>If we wanted to use the experimental branch, we would also add the flag: <code>-beta experimental</code></p>
<p>Once that command finishes running, the Satisfactory software should be downloaded!</p>
<p>We can try to test this out by running the software manually first. Using the following command to move into the game server folder, and execute the software: <code>cd ~/satisfactory &amp;&amp; ./FactoryServer.sh</code></p>
<p>Assuming it runs, we can press <code>Ctrl-C</code> at any point to stop the game server.</p>
<h2 id="registering-a-background-service">Registering a Background Service</h2>
<p>Okay, so we were able to run the server software manually for a quick test - but we&rsquo;ll want to configure it to run as a background service that will start &amp; stop with our Linux system. We&rsquo;ll use the info located on the <a href="https://satisfactory.fandom.com/wiki/Dedicated_servers/Running_as_a_Service">Satisfactory Wiki</a>.</p>
<p>So we&rsquo;ll create a new service definition using the following command: <code>sudo nano /etc/systemd/system/satisfactory.service</code></p>
<p>Then paste the following into that file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>Unit<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">Description</span><span class="o">=</span>Satisfactory dedicated server
</span></span><span class="line"><span class="cl"><span class="nv">Wants</span><span class="o">=</span>network-online.target
</span></span><span class="line"><span class="cl"><span class="nv">After</span><span class="o">=</span>syslog.target network.target nss-lookup.target network-online.target
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Service<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">Environment</span><span class="o">=</span><span class="s2">&#34;LD_LIBRARY_PATH=./linux64&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">ExecStartPre</span><span class="o">=</span>/usr/games/steamcmd +force_install_dir <span class="s2">&#34;/home/steam/SatisfactoryDedicatedServer&#34;</span> +login anonymous +app_update <span class="m">1690800</span> validate +quit
</span></span><span class="line"><span class="cl"><span class="nv">ExecStart</span><span class="o">=</span>/home/steam/SatisfactoryDedicatedServer/FactoryServer.sh
</span></span><span class="line"><span class="cl"><span class="nv">User</span><span class="o">=</span>steam
</span></span><span class="line"><span class="cl"><span class="nv">Group</span><span class="o">=</span>steam
</span></span><span class="line"><span class="cl"><span class="nv">StandardOutput</span><span class="o">=</span>journal
</span></span><span class="line"><span class="cl"><span class="nv">Restart</span><span class="o">=</span>on-failure
</span></span><span class="line"><span class="cl"><span class="nv">WorkingDirectory</span><span class="o">=</span>/home/steam/SatisfactoryDedicatedServer
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Install<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">WantedBy</span><span class="o">=</span>multi-user.target
</span></span></code></pre></div><p>Please note, if you used a different user account name (instead of <code>steam</code>), or downloaded the game in a different directory - you will need to modify those values in the above text.</p>
<p>Once that&rsquo;s done, we can enable our new service to start &amp; stop with the machine: <code>sudo systemctl enable satisfactory.service</code></p>
<p>Then we can start the game server: <code>sudo systemctl start satisfactory.service</code></p>
<p>After a few seconds, we can check the service status here: <code>sudo systemctl status satisfactory.service</code></p>
<p>If everything looks good, you should see output similar to below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">● satisfactory.service - Satisfactory dedicated server
</span></span><span class="line"><span class="cl">     Loaded: loaded <span class="o">(</span>/etc/systemd/system/satisfactory.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
</span></span><span class="line"><span class="cl">     Active: active <span class="o">(</span>running<span class="o">)</span> since Wed 2021-12-01 11:45:31 EST<span class="p">;</span> 18s ago
</span></span><span class="line"><span class="cl">    Process: <span class="m">3679</span> <span class="nv">ExecStartPre</span><span class="o">=</span>/usr/games/steamcmd +force_install_dir /home/steam/SatisfactoryDedicatedServer +login anonymous +app_update <span class="m">1690800</span> validate +quit <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>0/SUCCESS<span class="o">)</span>
</span></span><span class="line"><span class="cl">   Main PID: <span class="m">3749</span> <span class="o">(</span>FactoryServer.s<span class="o">)</span>
</span></span><span class="line"><span class="cl">      Tasks: <span class="m">21</span> <span class="o">(</span>limit: 7087<span class="o">)</span>
</span></span><span class="line"><span class="cl">     Memory: 1.0G
</span></span><span class="line"><span class="cl">        CPU: 18.526s
</span></span><span class="line"><span class="cl">     CGroup: /system.slice/satisfactory.service
</span></span><span class="line"><span class="cl">             ├─3749 /bin/sh /home/steam/SatisfactoryDedicatedServer/FactoryServer.sh
</span></span><span class="line"><span class="cl">             └─3756 /home/steam/SatisfactoryDedicatedServer/Engine/Binaries/Linux/UE4Server-Linux-Shipping FactoryGame
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:178<span class="o">][</span>  0<span class="o">]</span>LogAIModule: Creating AISystem <span class="k">for</span> world DedicatedserverEntry
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:178<span class="o">][</span>  0<span class="o">]</span>LogLoad: Game class is <span class="s1">&#39;BP_GameModeMenu_C&#39;</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:179<span class="o">][</span>  0<span class="o">]</span>LogReplicationGraph: Display: SetActorDiscoveryBudget <span class="nb">set</span> to <span class="m">20</span> kBps <span class="o">(</span><span class="m">5333</span> bits per network tick<span class="o">)</span>.
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>LogNetCore: DDoS detection status: detection enabled: <span class="m">0</span> analytics enabled: <span class="m">0</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>LogInit: BSD IPv4/6: Socket queue. Rx: <span class="m">262144</span> <span class="o">(</span>config 131072<span class="o">)</span> Tx: <span class="m">262144</span> <span class="o">(</span>config 131072<span class="o">)</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>LogNet: Created socket <span class="k">for</span> <span class="nb">bind</span> address: :: on port <span class="m">7777</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>PacketHandlerLog: Loaded PacketHandler component: DTLSHandlerComponent <span class="o">()</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>PacketHandlerLog: Loaded PacketHandler component: Engine.EngineHandlerComponentFactory <span class="o">(</span>StatelessConnectHandlerComponent<span class="o">)</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>LogNet: GameNetDriver EOSNetDriver_2147482561 IpNetDriver listening on port <span class="m">7777</span>
</span></span><span class="line"><span class="cl">Dec <span class="m">01</span> 11:45:33 Satisfactory FactoryServer.sh<span class="o">[</span>3756<span class="o">]</span>: <span class="o">[</span>2021.12.01-16.45.33:205<span class="o">][</span>  0<span class="o">]</span>LogWorld: Bringing World /Game/FactoryGame/Map/DedicatedserverEntry.DedicatedserverEntry up <span class="k">for</span> play <span class="o">(</span>max tick rate 30<span class="o">)</span> at 2021.12.01-11.45.33
</span></span></code></pre></div><h2 id="joining-the-server">Joining the Server</h2>
<p>We&rsquo;re almost there!</p>
<p>After the game server has been set up &amp; running, we&rsquo;ll need to perform the initial setup within the game itself.</p>
<p>So it&rsquo;s time to launch Satisfactory &amp; setup our game server!</p>
<p><img alt="servermanager" loading="lazy" src="/content/images/2021/12/servermanager.png#center"></p>
<p>In the screenshot above, the title screen now has an option for <strong>Server Manager</strong>. We&rsquo;ll click that. You should then see an option to add server, which will display the following dialog:</p>
<p><img alt="add-server" loading="lazy" src="/content/images/2021/12/add-server.png#center"></p>
<p>Here&rsquo;s where we put in the IP or hostname of our local dedicated server.</p>
<blockquote>
<p>As a reminder: If you have someone outside of your local network, they&rsquo;ll need a different address to connect. Again, this post won&rsquo;t dive into port forwarding or allowing access through your home router - there are plenty of good tutorials out there already!</p></blockquote>
<p>After we successfully connect to our server, we&rsquo;ll be prompted to claim it. Since it&rsquo;s newly created, this is where we can set our admin login &amp; the server name.</p>
<p>First we&rsquo;ll be asked to provide a name to claim the server:
<img alt="claimserver" loading="lazy" src="/content/images/2021/12/claimserver.png#center"></p>
<p>Then an administrator password - don&rsquo;t forget to save this somewhere safe!</p>
<p><img alt="admin-login" loading="lazy" src="/content/images/2021/12/admin-login.png#center"></p>
<p>Okay. So now we&rsquo;ll have a few options. First we&rsquo;ll check out the settings page:</p>
<p><img alt="serversettings" loading="lazy" src="/content/images/2021/12/serversettings.png#center"></p>
<p>Here we can modify our server name or update our admin password if we need to.</p>
<p>We can also set a <strong>Player password</strong> - This will keep people from joining your server unless they know the password.</p>
<p>Interestingly enough, we can opt to have the game pause when no players are connected - or uncheck the box to let your factory continue building even when no one is playing.</p>
<p>And lastly an option to autosave whenever someone disconnects from the server.</p>
<p>If we&rsquo;re good with those settings - we can start a new game session!</p>
<p>We&rsquo;ll head over to the <strong>Create Game</strong> tab:</p>
<p><img alt="createserver" loading="lazy" src="/content/images/2021/12/createserver.png#center"></p>
<p>This should look pretty similar to the normal game. Select the starting area &amp; give the session a name.</p>
<p>We&rsquo;ll also have a checkbox to automatically enter the game after it&rsquo;s done being created. If you don&rsquo;t check this box, you can enter the server from the <strong>Server Status</strong> page:</p>
<p><img alt="serverstatus" loading="lazy" src="/content/images/2021/12/serverstatus.png#center"></p>
<p>Here we can get a quick view of the current server status - which tier we&rsquo;re on, the current milestone, and whether or not anyone is currently connected.</p>
<p>Down in the bottom left, we&rsquo;ll have our <strong>Join Game</strong> button.</p>
<p>Time to play!</p>
<hr>
<p>Okay - and that about wraps it up. I hope this post was helpful for anyone else looking to build a dedicated server. The process was surprisingly easy, and I greatly appreciate the effort that the Coffee Stain devs have put into this!!</p>
<p>Happy building!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Started with Cisco NFVIS</title>
      <link>https://0x2142.com/getting-started-with-cisco-nfvis/</link>
      <pubDate>Sat, 13 Apr 2019 12:59:00 +0000</pubDate>
      <guid>https://0x2142.com/getting-started-with-cisco-nfvis/</guid>
      <description>A quick look at how to spin up NFVIS on a Cisco ENCS</description>
      <content:encoded><![CDATA[<p>A few weeks ago I got my hands on a Cisco UCS C220 M4 server - which I&rsquo;ve set up in a lab to install and test Cisco&rsquo;s Network Function Virtualization Infrastructure Software (NFVIS). I really wanted to get this running on an Enterprise Network Compute System (ENCS) box, but you can&rsquo;t always get everything what you want :). The UCS machine is also on the list of supported platforms, so we&rsquo;ll use that - but everything here should apply similarly to the ENCS platform.</p>
<h2 id="what-is-nfvis">What is NFVIS?</h2>
<p>NFVIS is an operating system developed by Cisco which is intended to be deployed at branch office locations - and allow for quick deployment of network services in lightweight VMs. For example, we might want to reduce cost and hardware footprint by deploying a single ENCS machine, then deploy our typical branch services on top of that (DNS, Firewalls, SDWAN, etc). Under the hood, NFVIS is built on top of CentOS and KVM.</p>
<p>In the image below, we have an ENCS unit that is running ISRv, FTDv, and a vEdge Cloud. NFVIS has the ability to build out traffic flows for service chaining. In this particular setup, we could have all branch traffic receive a default route up to our ISRv. The ISRv forwards traffic to a Firepower VM (FTDv) which performs some traffic inspection before passing everything up to the vEdge Cloud.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image.png#center"></p>
<p>We&rsquo;ll be coming back to this diagram later to see how we can build out this flow of services. For now, let&rsquo;s dive into how we can get NFVIS up and running.</p>
<h2 id="installing-nfvis">Installing NFVIS</h2>
<p>Lucky for us - the installation of NFVIS is fairly straightforward!</p>
<ol>
<li>Create a bootable USB - or mount the installation ISO via CIMC</li>
<li>Upon boot, select &ldquo;Install Cisco NFV Infrastructure Software&rdquo;:</li>
</ol>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/000-install.png#center"></p>
<ol start="3">
<li>Wait. A while. Install time can vary depending on your hardware.</li>
<li>Once completed, log into the CLI: Default login = admin/Admin123#</li>
<li>You&rsquo;ll be prompted to change the default admin password immediately:</li>
</ol>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-1.png#center"></p>
<p>Install completed! Now let&rsquo;s look at some of our base configuration..</p>
<h2 id="initial-configuration">Initial Configuration</h2>
<p>By default the NFVIS install will have a LAN and WAN bridge (lan-br and wan-br, respectively). The LAN config will be set up with a static IP of 192.168.1.1/24, and the WAN will be set for DHCP. We can check the current network settings by running the <em>show system settings</em>command:</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-2.png#center"></p>
<p>In this case, my WAN interface is able to get an IP via DHCP. We&rsquo;re likely going to want to change this to a static IP address - which we can do from the CLI or web interface. Let&rsquo;s start by trying this from the CLI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">config t
</span></span><span class="line"><span class="cl">system settings wan ip address &lt;ip addr&gt; &lt;netmask&gt;
</span></span><span class="line"><span class="cl">system settings default-gw &lt;gateway addr&gt;
</span></span><span class="line"><span class="cl">system settings hostname &lt;hostname&gt;
</span></span></code></pre></div><p>Changes can then be applied using the <strong>commit</strong> command</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-3.png#center"></p>
<p>We can verify these settings by repeating the <code>show system settings</code> command we used earlier.</p>
<p>Let&rsquo;s go ahead and log into the web interface to see what the network configuration looks like there:</p>
<ol>
<li>If we know our WAN or LAN IP from earlier, we can just pop that in our web browser.</li>
<li>Go ahead and log in using the new admin credentials we just created:</li>
</ol>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-4.png#center"></p>
<ol start="3">
<li>We&rsquo;ll be taken to the primary NFVIS dashboard, which will currently show no active VMs deployed:</li>
</ol>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-5.png#center"></p>
<ol start="4">
<li>In the left-hand menu, expand <strong>Host</strong>then click on <strong>Settings:</strong></li>
</ol>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-6.png#center"></p>
<p>Here we can see that we already configured our IP Addressing/hostname - but we could use the <strong>Edit</strong> button at the bottom to change any of these values. For example - I&rsquo;m going to go ahead and select <strong>Static</strong> for both the Management (LAN) and WAN IP addresses.</p>
<p>Another quick tip - if we need to modify which physical network adapters are tied to an internal network bridge, we can find that under <strong>VM Life Cycle &gt; Networking:</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/03/image-7.png#center"></p>
<hr>
<p>That&rsquo;s all for this time. In the next post, we&rsquo;ll take a look at how to package VM images and deploy our service chain.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automated F5 Backups with CatTools</title>
      <link>https://0x2142.com/automated-f5-backups-with-cattools/</link>
      <pubDate>Tue, 27 Mar 2018 11:00:22 +0000</pubDate>
      <guid>https://0x2142.com/automated-f5-backups-with-cattools/</guid>
      <description>How to configure SolarWinds Kiwi CatTools to monitor &amp;amp; back up F5 load balancer configurations</description>
      <content:encoded><![CDATA[<p>We have some new F5 load balancers in our environment, which means I need a method of grabbing regular configuration backups. There are a number of methods out there, but I&rsquo;ve opted to use SolarWind&rsquo;s CatTools software since we already own it.</p>
<p>The config I used is based on <a href="https://lessonsintech.wordpress.com/2017/06/15/automate-f5-backups">this blog post</a>. It&rsquo;s a great write-up on how to back up F5 configurations using CatTools. I don&rsquo;t want to replicate what was written over there - but I did hit some issues that were specific to my use-case that I wanted to share.</p>
<p>While I was happy to find the article linked above, the immediate results didn&rsquo;t work so smooth for me. This may be due to some key configuration differences that I face in my network:</p>
<ul>
<li>All the F5&rsquo;s are LDAP integrated - so there isn&rsquo;t an easy way to provide LDAP users with direct bash access</li></li>
<li>All of my F5&rsquo;s are remote appliances, where the backup configuration is being copied across the WAN</li></li>
</ul>
<p>Getting around the first problem was my biggest challenge. CatTools is a very command/response-oriented application. Any remote LDAP authenticated users are immediately dropped into F5&rsquo;s shell: <strong>tmsh</strong>. To get from there to their &lsquo;advanced shell&rsquo; is as simple as typing <strong>bash</strong>. However, When the terminal prompt changes, it often throws CatTools into a state of &ldquo;I didn&rsquo;t receive the response prompt I expected, therefore kill the job - something went wrong&rdquo;. I spent a bit more time on this than I wanted to - but the underlying problem was that the &ldquo;<strong>F5.BigIP</strong>&rdquo; device type was specifically looking for the tmsh shell and couldn&rsquo;t handle the prompt change. The fix? Switch the device type to &ldquo;<strong>Linux.RedHat.Bash</strong>&rdquo;, then add the <strong>bash</strong> command to the first line of your backup script.</p>
<p>The next problem was using TFTP to copy the backup archives over the WAN. Even some of the new F5&rsquo;s with minimal configuration still generate a 10Mb file. Doesn&rsquo;t seem like much, but when you&rsquo;re copying that over a WAN between two datacenters, that turns into a ~5 minute file transfer. CatTools by default will only wait 30 seconds after executing a command before it expects a response. So every time I tried to run the job, CatTools would kill it only 30-seconds into the file transfer. Luckily enough, they support a utility command that can alter the normal timeout:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">%ctUM: Timeout <span class="m">600</span>
</span></span><span class="line"><span class="cl">tftp -m binary 192.168.1.10 -c put <span class="nv">$filename</span>
</span></span><span class="line"><span class="cl">%ctUM: Timeout <span class="m">0</span>
</span></span></code></pre></div><p>The command <code>%ctUM: Timeout 600</code> changes the timeout value to 600 seconds, or 10 minutes. The TFTP file transfer command is next, which is now permitted up to 10 minutes to finish. The last command resets the timeout back to the default (30 seconds).</p>
<p>I also realized that the original script doesn&rsquo;t purge the backup archive afterwards. For my use case, I would much rather automatically clean up the backup files once they&rsquo;ve been transferred to a central location.</p>
<p>So after all that, here is the version of that script that I&rsquo;m using:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">date</span><span class="o">=</span><span class="sb">`</span>date +<span class="s2">&#34;%y%m%d&#34;</span>​<span class="sb">`</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">filename</span><span class="o">=</span><span class="nv">$HOSTNAME</span>.<span class="nv">$date</span>.ucs
</span></span><span class="line"><span class="cl">tmsh save /sys ucs /var/local/ucs/<span class="nv">$filename</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /var/local/ucs
</span></span><span class="line"><span class="cl">%ctUM: Timeout <span class="m">600</span>
</span></span><span class="line"><span class="cl">tftp -m binary 192.168.1.10 -c put <span class="nv">$filename</span>
</span></span><span class="line"><span class="cl">%ctUM: Timeout <span class="m">0</span>
</span></span><span class="line"><span class="cl">rm -f <span class="nv">$filename</span>
</span></span></code></pre></div><hr>
<p>Thanks again to the <a href="https://lessonsintech.wordpress.com/2017/06/15/automate-f5-backups">original blog post</a> for getting me on the right track with this! I hope that my ramblings here are helpful to anyone with a similar deployment scenario.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Review: Amazon LightSail</title>
      <link>https://0x2142.com/review-amazon-lightsail/</link>
      <pubDate>Tue, 17 Oct 2017 08:00:20 +0000</pubDate>
      <guid>https://0x2142.com/review-amazon-lightsail/</guid>
      <description>My experience with hosting this blog on Amazon Lightsail</description>
      <content:encoded><![CDATA[<p>Disclaimer: I&rsquo;m not at all sponsored by anything I review on here. If there ever comes a time where someone is crazy enough to sponsor a review, I&rsquo;ll definitely let you all know</p>
<p>I&rsquo;ve been considering the idea of posting some short reviews of products or services I use. Not at all meant to make this a review site, but just a bit of &ldquo;here is what I use and what I think of it&rdquo;. This is going to be the first shot at that idea - so let me know what you think!</p>
<hr>
<p>When I was first looking to start up my own personal blog again, I did quite a bit of digging around to try and find where to host it. I considered hosting it myself at home, but overall I didn&rsquo;t really want to manage that overhead. I also looked at a number of hosting providers, but I never really found anything that I was quite happy with. So many of them would offer you an instance of a web platform like WordPress or Drupal, but not much control beyond that. I really wanted something where I could have full control over my own VM, like a VPS, but most of these services cost more than I wanted to pay.</p>
<p>Right in the middle of my research for a hosting provider last year, Amazon announced a new AWS service offering: <a href="https://amazonlightsail.com">LightSail</a>. I had already briefly considered picking up a small AWS VM for what I wanted to do, but I honestly didn&rsquo;t want to try and manage usage when every single little thing is an additional cost (storage, bandwidth, etc). However, LightSail really caught my attention because of the stupid-simple pricing structure - the smallest allocation is only $5 a month and includes 512MB of RAM, 1 core CPU, 20GB SSD, and 1TB of free data transfer. This was probably more than enough for me to get started with a site, and the pricing was very tempting. As if they wanted to provoke me to try it even further, Amazon actually offers your first month free (for the $5/mo instance). Additionally, Amazon offers up to three free public IP addresses and free DNS management along with every plan - which I thought made this a fantastic offer.</p>
<p>With all that LightSail seemed to offer, I decided to jump on the $5/mo plan and use the free trial period to give it a shot. The setup of my first VM was extremely simple and only a few clicks: Pick a size, give it a name, and select which applications you want pre-deployed (optional). I went ahead and selected my options, and within <em>seconds</em> my VM was already powered on, pre-configured, and ready to use. Unfortunately, it seemed like my VM deployment didn&rsquo;t quite go as smoothly as I would have liked. I wasn&rsquo;t able to log into the admin console for my pre-deployed web application, and the deployment guide didn&rsquo;t seem to match up with what I was seeing on my VM. I spent about ten minutes or so trying to troubleshoot this before it hit me: <em>I could just delete and re-deploy this VM in seconds</em>. So I did exactly that, and within 2 minutes I already had another VM instance deployed - and this one worked perfectly.</p>
<p>Next I went ahead and applied a static IP to my instance. You don&rsquo;t necessarily have to do this, and if you host your DNS within LightSail then it will automatically update your DNS any time your instance IP changes. However, since they offer up to three static IP addresses for free, I don&rsquo;t really see a reason why you wouldn&rsquo;t use it - unless the VM was temporary or not being published publicly. This process is also extremely straightforward: Click the button to create a new static IP, give it a name, and select which VM to assign it to. Easy enough, right? I also tried out the DNS hosting for only a very brief period of time. I ultimately ended up opting to move my DNS hosting out to CloudFlare, which I would like to cover in another post.</p>
<p>Once the LightSail VM is up and running, it&rsquo;s easy enough to connect to it. They offer a web-based SSH console, and by default allow access to ports 443 and 22 for remote connections. Remote SSH connections are handled by public/private key pairs only - no username/passwords permitted for login. I actually prefer this, and there is an extremely simplistic interface for generating new or additional SSH keys and automatically configuring them within your VM. The management console also offers an easy-to-use firewall system, where you can open ports for common services via a drop-down menu. You&rsquo;re also able to enter custom port numbers, or remove any/all open ports entirely. As a quick note: LightSail offers no traditional console access to your VM - so if you close port 22, then you won&rsquo;t even be able to manage the VM from the web console since it uses SSH. For me, I would rather take the additional security step to only enable that port when I absolutely need to access the VM via SSH.</p>
<p>So it&rsquo;s been almost a year since I started using Lightsail and overall I am extremely pleased. I&rsquo;ve run one primary VM since I opened my account, and I&rsquo;ve spun up a few additional VMs here and there for different testing. It&rsquo;s easy enough to just turn up a new VM, try it out, and then just purge it if/when you don&rsquo;t need it any more. The billing reports are very straightforward too - and I&rsquo;ve so far never come close to using all of my 1TB free data transfer. The VM itself is extremely snappy for only a single core with 512MB of RAM.</p>
<p>Overall I would highly recommend giving LightSail a try, even if you only use it for the 1 month free trial. I&rsquo;ve been very happy with the service so far, and I&rsquo;m looking forward to any new features/functionality that might be added in the future.</p>
<p>As a quick summary:</p>
<p><strong>Benefits:</strong></p>
<ul>
<li>Extremely easy to use (Simplistic interface)</li>
<li>Three free static IPs</li>
<li>Free DNS management</li>
<li>Up to 1TB of free data tranfer</li>
<li>First month of a $5 instance is free</li>
<li>Pre-deployment/configuration of multiple different web applications</li>
</ul>
<p><strong>Drawbacks:</strong></p>
<ul>
<li>You can&rsquo;t move which zone your VM is deployed in</li>
<li>You can&rsquo;t rename your VM instance</li>
<li>In order to upgrade plans, you have to take a snapshot and restore it to a new VM - and this is currently only available via the AWS APIs, so no way to do this in the web portal</li>
<li>Snapshots take forever, and you get charged for how long they are stored for (I&rsquo;ve had a snapshot of a 20G VM take &gt;30 minutes in the past)</li>
</ul>
<p><strong>My wishlist:</strong></p>
<ul>
<li>An integrated load balancer - even one with very basic options available</li>
<li>Native support for IPv6 addresses - Amazon already offers a free IPv4 address, so why not v6 too?</li>
<li>More storage options - I am careful about how many images i upload to my site, because I only have 20GB of space available. I don&rsquo;t want to pay for S3 if I don&rsquo;t have to.</li>
</ul>
<p><strong>UPDATE 12/19/2017</strong>- As of the end of November, Amazon has added both Load Balancing and additional storage to LightSail. Currently the load balancing functionality costs  $18 per month, and additional disk space is $0.10 per GB per month.</p>
<hr>
<p>Have you used LightSail before? What are your thoughts? Comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 4 – Polling the SRX)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/</link>
      <pubDate>Tue, 04 Jul 2017 08:00:31 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/</guid>
      <description>This post focuses on the Juniper SRX side of my VPN dashboard - writing the Python scripting to query VPN status from each device</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior posts first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
</ul>
<hr>
<p>Alright - We&rsquo;re finally at the last step to this project. We already have Django running with a somewhat decent-looking web dashboard. Now the only thing that remains is polling our SRX firewalls for VPN connections, then populating this information into the database.</p>
<p>For this to happen, I built a custom management command for Django - the cool thing here is that we can easily use this within a cron job to automatically schedule updates at regular intervals. So within our application (the vpn folder), we&rsquo;re going to create a <em>management</em> folder then a <em>commands</em> folder within that. In here you can name your script whatever you want, so I went with cronpoller.py.</p>
<p>The script that I wrote is extremely simplistic and could probably use quite a number of improvements - which I will slowly make over time. But for now, it is what it is - and it does exactly what I need it to.</p>
<p>First thing we need to do is import a few things we will need:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.core.management.base</span> <span class="kn">import</span> <span class="n">BaseCommand</span><span class="p">,</span> <span class="n">CommandError</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">vpn.models</span> <span class="kn">import</span> <span class="n">Firewall</span><span class="p">,</span> <span class="n">Datacenter</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">jnpr.junos</span> <span class="kn">import</span> <span class="n">Device</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">lxml</span> <span class="kn">import</span> <span class="n">etree</span>
</span></span></code></pre></div><p>We have some Django modules for treating this as a management command - and we&rsquo;re also importing our existing models into this script. This part is really cool to me, because it means that this cron script can just reference the existing objects that we created to get their attributes. Next, we import the JunOS stuff from their pyEZ packages (don&rsquo;t forget to <a href="/getting-started-with-junos-pyez/">install pyEZ</a>!). The last one is going to be used to parse the responses from our SRX into something we can use.</p>
<p>Alright - so in order to build a management command, we have to create a class called <em>Command,</em> then define our functions within that. Within the <em>Command</em> class, Django will look for a function called <em>handle</em> to execute. In my final script, I have that function plus one called <em>getVPNStatus</em>. Let&rsquo;s start with putting together <em>getVPNStatus:</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># This function will poll the device for status </span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">getVPNStatus</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fw</span><span class="p">,</span> <span class="n">dc</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">connectedlist</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># So we generate a JunOS pyEZ device connection using the information about the </span>
</span></span><span class="line"><span class="cl">    <span class="c1"># firewall that we gather from the database object</span>
</span></span><span class="line"><span class="cl">    <span class="n">device</span> <span class="o">=</span> <span class="n">Device</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_manageip</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">user</span><span class="o">=</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_user</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">password</span><span class="o">=</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_pass</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Try to open a connection out to the target device</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">dev</span><span class="o">.</span><span class="n">open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># If for some reason this doesn&#39;t work, just return UNREACHABLE - which</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we&#39;ll assume means the device is down</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;UNREACHABLE&#34;</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">    <span class="c1"># Here is where we poll the SRX for a list of all IPSec Security Associations.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># The equivalent of the &#39;show security ipsec sa&#39; command</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">etree</span><span class="o">.</span><span class="n">tostring</span><span class="p">(</span><span class="n">dev</span><span class="o">.</span><span class="n">rpc</span><span class="o">.</span><span class="n">get_security_associations_information</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># The SRX returns a response in XML, which we&#39;ll need to dig through</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Credit to the guys over at &lt;a href=&#34;http://packetpushers.net/parsing-junos-xml-python/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Packet Pushers&lt;/a&gt; for a great post explaining how to</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># parse these responses</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> <span class="k">as</span> <span class="n">a</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">xmldoc</span> <span class="o">=</span> <span class="n">etree</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">docroot</span> <span class="o">=</span> <span class="n">xmldoc</span><span class="o">.</span><span class="n">getroot</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">rootchildren</span> <span class="o">=</span> <span class="n">docroot</span><span class="o">.</span><span class="n">iter</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">child</span> <span class="ow">in</span> <span class="n">rootchildren</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># For each IPSec SA returned, we need to find the remote gateway IP, which </span>
</span></span><span class="line"><span class="cl">            <span class="c1"># we use to tie the connection back to the connected datacenter</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">child</span><span class="o">.</span><span class="n">tag</span> <span class="o">==</span> <span class="s2">&#34;sa-remote-gateway&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">connectedlist</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">child</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">    <span class="c1"># Once we&#39;ve built our list, send it back!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">connectedlist</span>
</span></span></code></pre></div><p>That already is major part of our script. It will connect out to each device, grab a list of every connected IPSec tunnel, then return back a list of connected gateways. Now we just need to write our <em>handle</em> function, which will do all of the remaining work.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Let&#39;s quickly create two lists - based on our firewall and datacenter models</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This actually polls the database for anything that&#39;s been created</span>
</span></span><span class="line"><span class="cl">    <span class="n">dclist</span> <span class="o">=</span> <span class="n">Datacenter</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;datacenter_code&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">fwlist</span> <span class="o">=</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">    <span class="c1"># This will loop through each firewall, then call the getVPNStatus function</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">fw</span> <span class="ow">in</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">statuslist</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getVPNStatus</span><span class="p">(</span><span class="n">fw</span><span class="p">,</span> <span class="n">dclist</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Once we have our response, we&#39;re going to check the returned list of connected</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># gateways against our list of datacenters from the database</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">dc</span> <span class="ow">in</span> <span class="n">dclist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">remoteFW</span> <span class="ow">in</span> <span class="n">fwlist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># If we find another datacenter in the connected gateway list, add </span>
</span></span><span class="line"><span class="cl">            <span class="c1"># that datacenter to the vpnstatus list as &#34;VPNUP&#34;, otherwise assume it&#39;s down</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">remoteFW</span><span class="o">.</span><span class="n">firewall_vpnip</span> <span class="ow">in</span> <span class="n">statuslist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">vpnstatus</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;VPNUP&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">vpnstatus</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;VPNDOWN&#34;</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">    <span class="c1"># After all that is done - we just save the new vpnstatus list to the firewall_vpnstatus</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># field in the database</span>
</span></span><span class="line"><span class="cl">    <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span> <span class="o">=</span> <span class="n">vpnstatus</span>
</span></span><span class="line"><span class="cl">    <span class="n">fw</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">update_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;firewall_vpnstatus&#34;</span><span class="p">])</span>
</span></span></code></pre></div><p>I&rsquo;ve said this before, but I think it&rsquo;s worth noting again - This probably isn&rsquo;t the best or most efficient/reliable way of doing this. I think that over time I would like to revisit this and refine it a bit - but this is what I&rsquo;ve put together so far. In fact, I think it&rsquo;s worth stating that this will probably break in a fantastically horrific manner. This isn&rsquo;t a finished product, but pretty much just version 0.1 - the base functionality works, but it&rsquo;s in desperate need of refinement.</p>
<p>Alright - so now we just have to add a cron job in our system to call this script and run it. Depending on how many firewalls you have and how the latency is, you might need a different interval than I am using - but I went ahead and set mine to run once every 10 minutes. I would like to get this down to 5 minutes or less, but I might need to figure out a way to potentially multi-thread the cronpoller.py script. So here is what I did in /etc/cron.d/vpnstatuspoller:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">*/10  * * * * * python /root/junos-dashboard/manage.py cronpoller
</span></span></code></pre></div><p>After letting the script poll my firewalls - I ended up with a dashboard that looks like this:</p>
<p><img alt="dashboard-complete" loading="lazy" src="/content/images/2021/01/dashboard-complete.png#center"></p>
<h2 id="future-improvements">Future improvements</h2>
<p>Of course, I still have a few things I would like to add or improve on. I&rsquo;ve been keeping a list of some ideas that I&rsquo;ve had, which I&rsquo;ll share here:</p>
<ul>
<li>Add a timestamp to each firewall that contains the last poll time (in case something gets missed or hasn&rsquo;t updated yet)</li>
<li>Possibly add ability to click a &lsquo;down&rsquo; VPN cell to force clear SA</li>
<li>Set up email alerts from the cronpoller to automatically notify me of a failure</li>
<li>Ability to click on any VPN cell and get additional info - like VPN uptime, subnets routed across it, or maybe the IKE/IPSec negotiation parameters</li>
<li>Ability to click on any firewall name and get additional info - like uptime, JunOS version, CPU/Memory</li>
</ul>
<hr>
<p>I finally got around to getting myself a <a href="https://github.com/0x2142">GitHub</a> account, so I might put the final code up there once I&rsquo;m done. I also have a number of other JunOS scripts that are probably worth posting up there as well.</p>
<p>Well, I hope you enjoyed reading about this project - because I certainly enjoyed working on it. This was one of my first real projects using the JunOS pyEZ libraries, and I got to pick up and learn how to use Django as well. The experience of building this has given me ideas for other JunOS automation projects - so look out for some of those in the future!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 3 – Creating the Dashboard)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/</link>
      <pubDate>Tue, 27 Jun 2017 08:00:19 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/</guid>
      <description>This post focuses on the web GUI side of the VPN dashboard - storing &amp;amp; displaying the information that gets collected</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior posts first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
</ul>
<hr>
<p>In the last post, we got Django working well enough to start serving up web requests. That&rsquo;s a great start - So now it&rsquo;s on to actually building the project. We&rsquo;re going to cover this in two parts: the front-end dashboard, and the back-end scripts. In this post, I&rsquo;m going to show you how I began putting everything together to assemble the front-end HTML dashboard.</p>
<p>As a quick refresher note, we created a project in the last post called junos-dashboard. Within that project, we created our app named vpn. This folder is primarily where we&rsquo;ll be spending our time today. There are two files in our app folder that will need a few edits:</p>
<p><strong>views.py</strong> - This is where we originally created our basic index page, which currently just spits out a message. So we&rsquo;ll need to add code in here to render our dashboard instead.</p>
<p><strong>models.py</strong> - In here we will define our objects and the characteristics about those objects that we want to store/retrieve in the sqlite database.</p>
<p>Let&rsquo;s go ahead and open up our models.py file. For my dashboard, I have two objects I want to track - Firewalls (the SRX hosting the VPN) and Datacenter (as a location tracking). So in order to create those objects, we just have to add two classes into the models.py file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Datacenter</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">datacenter_code</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Firewall</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">firewall_name</span>
</span></span></code></pre></div><p>Easy enough, right? So here is the really cool thing about Django (or probably any web framework really) - These model objects are now something that we can reference in our code to change or query attributes about them. Even cooler is that Django automatically builds an admin page to our site, where we can create new instances of each model right there (which I&rsquo;ll cover later). So this means that all I have to do is define these two models and their attributes, then anyone on my team can log into the dashboard admin page and add new firewalls or datacenters.</p>
<p>Okay, so in order to actually make these models useful, we need to add our attributes. For the datacenter object, I only care about two things - What is the datacenter code (identifier), and is the datacenter an active location. So here is where we add those options under our datacenter class:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Datacenter</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_code</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;Datacenter Code&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="s1">&#39;Datacenter Active?&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>The firewall object gets a little more complicated because I want to be able to define quite a few things. The obvious attributes are: the firewall name, which datacenter it belongs to, and whether or not it is an active firewall. I&rsquo;ll also need to collect this firewall&rsquo;s VPN peer IP, so that I can compare it against other devices to check the connection. But wait - how am I actually checking that VPN status? Oh, I guess I&rsquo;ll also need to collect a username/password for the SRX API, as well as a valid management IP to reach the API.</p>
<p>Let&rsquo;s take a moment though - because I stopped here and realized that I&rsquo;ll need to collect user/password data in a form, then it&rsquo;s going to be stored in a sqlite database. Oh, and of course it will be unencrypted. I didn&rsquo;t really like that idea - so I did some research on how to encrypt one of the object attributes. Turns out, it&rsquo;s actually pretty easy since someone has already created a module to do exactly that.</p>
<p>So let&rsquo;s grab that module real quick:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos vpn]# pip install django-encrypted-fields
</span></span></code></pre></div><p>Once we have the module, it requires us to generate some keys which will be used for encrypting our data. The following is based on the steps listed on the GitHub page for <a href="https://github.com/defrex/django-encrypted-fields">django-encrypted-fields</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# mkdir fieldkeys
</span></span><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# keyczart create --location=fieldkeys --purpose=crypt
</span></span><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# keyczart addkey --location=fieldkeys --status=primary --size=256
</span></span></code></pre></div><p>Next, we just need to add this keyfile into our settings.py:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ENCRYPTED_FIELDS_KEYDIR = &#39;/root/junos-dashboard/fieldkeys&#39;
</span></span></code></pre></div><p>Alright - back to actually creating the attributes we need for our firewalls. Since we&rsquo;re using this new module, we&rsquo;ll need to add an additional import statement - otherwise, we are just adding the fields I covered earlier:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">encrypted_fields</span> <span class="kn">import</span> <span class="n">EncryptedCharField</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Firewall</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;Firewall Name&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_location</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="s1">&#39;Datacenter&#39;</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="s1">&#39;Firewall Active?&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_manageip</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">GenericIPAddressField</span><span class="p">(</span><span class="s1">&#39;Management IP&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_vpnip</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">GenericIPAddressField</span><span class="p">(</span><span class="s1">&#39;VPN Interface IP&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_user</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;API User&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_pass</span> <span class="o">=</span> <span class="n">EncryptedCharField</span><span class="p">(</span><span class="s1">&#39;API Pass&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_vpnstatus</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>A few things to note here - The firewall_location attribute will actually query the table of datacenter objects - so we&rsquo;re not creating any data twice. You might also notice that I added a firewall_vpnstatus text field, which is going to be hidden in the administrative console. Because I&rsquo;m a terrible programmer, I&rsquo;ll be storing all of the VPN status info in this text field in the database. Yes, there is probably a much more elegant way of handling this - but again, I&rsquo;m a network admin not a professional coder. For what I need, this gets the job done. If you have a better way of accomplishing this - let me know! I would be very interested in another method.</p>
<p>I also found out later that just because I encrypted the field doesn&rsquo;t mean that Django knows the field is a password. I wanted the field to be treated like a password input, so the data wasn&rsquo;t visible to anyone who just logged in. I found out that you can create a forms.py file within the vpn directory, and pretty much override the field type to account for this.</p>
<p>So here is what I threw into the forms.py file to handle password input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.forms</span> <span class="kn">import</span> <span class="n">ModelForm</span><span class="p">,</span> <span class="n">PasswordInput</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Firewall</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FirewallForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_pass</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span><span class="n">widget</span><span class="o">=</span><span class="n">forms</span><span class="o">.</span><span class="n">PasswordInput</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span> <span class="o">=</span> <span class="n">Firewall</span>
</span></span><span class="line"><span class="cl">        <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;firewall_pass&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>That was actually way easier than I had anticipated.</p>
<p>Awesome, now our work on the models is completely done - why don&rsquo;t we log into the administrative  web interface and take a look at how it all turned out?</p>
<p>Here is a view of the datacenter page - where we can add a new location:</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/djangoadmin-datacenters.png#center"></p>
<p>And now for the firewall objects - with all the fields we need to get the job done:</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/djangoadmin-firewalls.png#center"></p>
<p>Looks pretty good, huh? And we didn&rsquo;t even need to do any work to get the free admin functionality! This is honestly my favorite part of Django. Without posting a ton of additional screenshots, the admin page also provides audit logs - so we can see who changed what objects. Very helpful.</p>
<hr>
<p>Alright - now let&rsquo;s start work on our views.py file, so we can actually start putting all this together in our dashboard!</p>
<p>Within our views.py file, we already had our index function, which just returned a string of text. We&rsquo;ll be adding to that in a moment - but first I created a separate function called buildrows, which does exactly what you think. This function will pull each firewall and it&rsquo;s status, and build our little HTML table. I&rsquo;m not going to go into great detail on this - but check out the comments for more information on what&rsquo;s going on here:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># This is our function to build the HTML table - We&#39;re going to be expecting this function</span>
</span></span><span class="line"><span class="cl"><span class="c1"># to be fed a list of firewalls and datacenters</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">buildrows</span><span class="p">(</span><span class="n">firewall_list</span><span class="p">,</span> <span class="n">datacenter_list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewallstatus</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We will iterate through each firewall and compile each row for our table</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">fw</span> <span class="ow">in</span> <span class="n">firewall_list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># The first cell in our row is going to be the firewall name and it&#39;s own VPN peer IP</span>
</span></span><span class="line"><span class="cl">        <span class="n">onerow</span> <span class="o">=</span> <span class="s2">&#34;&lt;td&gt;&lt;b&gt;</span><span class="si">%s</span><span class="s2">&lt;/b&gt;&lt;i&gt;</span><span class="si">%s</span><span class="s2">&lt;/i&gt;&lt;/td&gt;&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_name</span><span class="p">,</span> <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnip</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Now we iterate through every datacenter, in order to see which ones we have a</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># connection to from this firewall</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">dc</span> <span class="ow">in</span> <span class="n">datacenter_list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># First thing is first - If this firewall contains the name of the datacenter</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># then we&#39;ll print N/A, since we wouldn&#39;t expect a VPN to itself </span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="nb">str</span><span class="p">(</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;N/A&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Also, if there is no VPN status in the database, then just print &#39;No Status&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;No Status&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">     
</span></span><span class="line"><span class="cl">            <span class="c1"># This portion is pretty self-explanatory - We parse the vpnstatus field in the</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># database. If the state of the connection is &#39;VPNUP&#39;, then we print a cell for</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># that datacenter with the text &#39;UP&#39;. If the state is &#39;VPNDOWN&#39;, then we print</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># &#39;DOWN&#39;. And if nothing matches, print &#39;No Status&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="n">statuslist</span> <span class="o">=</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">status</span> <span class="o">=</span> <span class="n">statuslist</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="s2">&#34;VPNUP&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td class=</span><span class="se">\&#34;</span><span class="s2">vpnup</span><span class="se">\&#34;</span><span class="s2">&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;UP&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">elif</span> <span class="n">status</span> <span class="o">==</span> <span class="s2">&#34;VPNDOWN&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td class=</span><span class="se">\&#34;</span><span class="s2">vpndown</span><span class="se">\&#34;</span><span class="s2">&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;DOWN&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;No Status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;No Status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Once complete, we append this row to the status list</span>
</span></span><span class="line"><span class="cl">        <span class="n">firewallstatus</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">onerow</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># All done! Return our list of statuses!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">firewallstatus</span>
</span></span></code></pre></div><p>That block might not make a ton of sense at the moment - but it will shortly. Essentially I have a script that is connecting to each firewall via the SRX API and polling the VPN status for each peer. The script is then writing all of this information to a field in the database called vpnstatus in one long string, which kinda looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="err">u&#39;US-1&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;US-2&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;EU-1&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;EU-2&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;CN-1&#39;:</span> <span class="err">&#39;VPNDOWN&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>The buildrows function is then just grabbing each firewall and then parsing this data to figure out what it has connections to - then generates a row in our table.</p>
<p>With that done, our index function within views.py needs a little work to start displaying our table. You&rsquo;ll also notice we&rsquo;re importing the models we created, as well as a template loader - which I&rsquo;ll get to in a moment. So here is what that function will look like when we&rsquo;re done:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Firewall</span><span class="p">,</span> <span class="n">Datacenter</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.template</span> <span class="kn">import</span> <span class="n">loader</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">ast</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_status</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This will grab each datacenter object (if they&#39;re marked active) from the </span>
</span></span><span class="line"><span class="cl">    <span class="c1"># database and add them to a list</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="n">Datacenter</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;datacenter_code&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">each</span><span class="o">.</span><span class="n">datacenter_active</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="n">datacenter_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Same thing here - grab our firewalls and append to a list</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">each</span><span class="o">.</span><span class="n">firewall_active</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="n">firewall_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Now we pass those lists to the buildrows function to assemble our HTML table</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_status</span> <span class="o">=</span> <span class="n">buildrows</span><span class="p">(</span><span class="n">firewall_list</span><span class="p">,</span> <span class="n">datacenter_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re also going to apply a template to make the page look fancy</span>
</span></span><span class="line"><span class="cl">    <span class="n">template</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="s1">&#39;web/index.html&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This is taking all of our lists, and passing them into our HTML template</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># so that it can build the page semi-automatically</span>
</span></span><span class="line"><span class="cl">    <span class="n">context</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;datacenter_list&#39;</span><span class="p">:</span><span class="n">datacenter_list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;firewall_list&#39;</span><span class="p">:</span> <span class="n">firewall_list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;firewall_status&#39;</span><span class="p">:</span><span class="n">firewall_status</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="n">request</span><span class="p">))</span>
</span></span></code></pre></div><p>Alright - our views.py file is now complete. I wanted to keep the index function kind of simple, so we just have it pull data from the database, pass it to another function for processing, then return it to the browser for display.</p>
<p>So in the code above, you might have noticed that I was using an HTML template to make the page look fancier. I did this by going out to Google and finding a free CSS template for HTML tables. I won&rsquo;t post the CSS here, but there are plenty of free templates out there - just find whatever suits your taste. Once you have that CSS file, create a directory (within our app folder) called static and place the CSS file in there.</p>
<p>Then, it&rsquo;s one simple template HTML file to load our CSS and render our table. Within the app directory, I created a templates folder, then a folder called web within that. I added a new file called index.html - which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>SRX VPN Dashboard<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">align</span><span class="o">=</span><span class="s">&#34;center&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% load static %}
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Load the CSS template here --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/css&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{% static &#39;style.css&#39; %}&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Add a header to the table --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;table-title&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>SRX VPN Dashboard<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Create our first row, which will be a list of each data center location --&gt;</span>
</span></span><span class="line"><span class="cl">    {% if firewall_list %}
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">table</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;container&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {% for datacenter in datacenter_list %}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;</span>{{ datacenter.datacenter_code }}<span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {% endfor %}
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- Then we add a row for each firewall, followed by its status 
</span></span></span><span class="line"><span class="cl"><span class="c">             for each datacenter --&gt;</span>
</span></span><span class="line"><span class="cl">        {% for firewall in firewall_status %}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            {{firewall|safe}}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {%endfor%}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- If something goes wrong, we just print an error --&gt;</span>
</span></span><span class="line"><span class="cl">    {% else %}
</span></span><span class="line"><span class="cl">        No firewalls were found.
</span></span><span class="line"><span class="cl">    {% endif %}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Guess what? The dashboard is complete! Now we can run our Django server using &lsquo;./manage.py runserver&rsquo; and take a look at what we have created.</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/dashboard-nodata.png#center"></p>
<p>Pretty simplistic - but we&rsquo;re getting very close to what I wanted to accomplish. The dashboard loads and generates the table, but it doesn&rsquo;t have any data yet.</p>
<p>In the next post - We&rsquo;ll take a look at building the backend script to poll all of our SRX firewalls for VPN connections. I hope you enjoyed reading this - let me know what you think in the comments below!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 2 – Learning Django)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/</link>
      <pubDate>Tue, 13 Jun 2017 08:00:18 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/</guid>
      <description>In order to build my Juniper SRX VPN dashboard, I opted to use Django. Let&amp;rsquo;s take a quick look at how I got started.</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior post first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
</ul>
<hr>
<p>Once I figured out the overall idea behind what I wanted to do - the next big part was figuring out where to start. As I mentioned in part 1, I had never used Django before starting this project. So I figured that learning how to to use a new web framework might be the best place to begin.</p>
<p>The great thing, is that Django already has some great tutorials on how to get the packages installed and begin working on your first project. Their tutorials can be found here:</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/1.11/intro/install/">Installing Django</a></li>
<li><a href="https://docs.djangoproject.com/en/1.11/intro/tutorial01/">Writing your first app</a> - Credit to this page for some parts of the examples below</li>
</ul>
<p>I used both of these tutorials when I wrote my dashboard, and pretty much just modified what they were doing to fit my needs. After I got a baseline down, it was pretty easy to keep going and complete what I wanted to do.</p>
<p><strong>Note:</strong> Just as a pure warning - I&rsquo;m a network admin, not a professional programmer. Python, Django, and the Junos PyEZ libraries make it easy enough for me to make my ideas into a reality. However, that doesn&rsquo;t mean I write perfect or even great code. (I learned Python from <a href="https://learnpythonthehardway.org/book/">Learn Python The Hard Way</a> - I highly recommend this site, as it was a great resource!)</p>
<p>Alright, with that out of the way - Let&rsquo;s get started!</p>
<h2 id="step-one---installing-django">Step One - Installing Django</h2>
<p>First thing is first, I learned Python on the 2.7.x chain, and I&rsquo;ve been terrible about forcing myself to switch to 3.x. So for the purposes of this tutorial, everything I&rsquo;ve written was intended for Python 2.7.5.
Installing the actual Django package is a super simple task (if you use <a href="https://pip.pypa.io/en/latest/installing/#installing-with-get-pip-py">pip</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos ~]# pip install django
</span></span><span class="line"><span class="cl">Collecting django
</span></span><span class="line"><span class="cl"> Downloading Django-1.11.2-py2.py3-none-any.whl (6.9MB)
</span></span><span class="line"><span class="cl"> 100% |████████████████████████████████| 7.0MB 140kB/s
</span></span><span class="line"><span class="cl">Collecting pytz (from django)
</span></span><span class="line"><span class="cl"> Downloading pytz-2017.2-py2.py3-none-any.whl (484kB)
</span></span><span class="line"><span class="cl"> 100% |████████████████████████████████| 491kB 1.9MB/s
</span></span><span class="line"><span class="cl">Installing collected packages: pytz, django
</span></span><span class="line"><span class="cl">Successfully installed django-1.11.2 pytz-2017.2
</span></span></code></pre></div><h2 id="step-two---get-junos-pyez-libraries">Step Two - Get JunOS pyEZ libraries</h2>
<p>I already wrote about this earlier in <a href="/getting-started-with-junos-pyez">Getting Started with JunOS PyEZ</a>. If you don&rsquo;t already have the JunOS packages installed, check out that page for the instructions.</p>
<h2 id="step-three---create-the-project">Step Three - Create the Project</h2>
<p>Django is pretty wonderful from my brief experiences with it. They package a number of tools that make starting a new project very simple.</p>
<p>So to start off with our project, we&rsquo;re going to use the django-admin tool:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos ~]# django-admin startproject junos-dashboard
</span></span></code></pre></div><p>This will create a base directory structure for us to begin our project. We&rsquo;ll dive into what these files and folders are as we touch them - but for now, you&rsquo;ll need to make one minor edit to ~/junos-dashboard/junos-dashboard/settings.py. Open that file up in your favorite text editor, and find the ALLOWED_HOSTS setting. Add in the IP address of your linux VM, like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ALLOWED_HOSTS = [&#39;10.2.32.90&#39;]
</span></span></code></pre></div><p>Once that&rsquo;s done, go to the root of your project (~/junos-dashboard/) and test out the web server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# ./manage.py runserver 10.2.32.90:8000
</span></span></code></pre></div><p>Django includes it&rsquo;s own web server for development and testing, which is extremely helpful. Once you run that command, try to hit your page in a browser and just make sure it loads.</p>
<p>Once that&rsquo;s done, make sure you&rsquo;re in the root of your project - and we will create our dashboard application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# django-admin startapp vpn
</span></span></code></pre></div><p>This command just creates <em>another</em> directory structure within your project. I think this is helpful though, because then I don&rsquo;t have to worry about which files I need to create or how they should be laid out - Django just handles that for us.</p>
<h2 id="step-four---getting-our-web-server-to-return-our-app-page">Step Four - Getting our web server to return our app page</h2>
<p>Okay - So next we need to make a couple of minor edits in a few files. Within our app directory, there are two files (views.py and urls.py) that handle the main parts of our web interface. Note - urls.py doesn&rsquo;t exist by default and you will have to create it!</p>
<p>Within views.py, we&rsquo;re going to create our dashboard homepage with a quick test response:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Import stuff</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.template</span> <span class="kn">import</span> <span class="n">loader</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">ast</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># This is our index page</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Return some plain text - just so we know it worked!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="s2">&#34;This page will eventually be a magical dashboard!&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>This file is where we will eventually be putting in all the code for our dashboard page. When we hit our webserver, this file is executed to generate the view that we&rsquo;ll see.</p>
<p>Next, we need to create a urls.py file within the same folder as views.py. This file is just a way to direct traffic. If we receive traffic to a regex of ^$, which is no path, then we are going to redirect it to the index function in views.py:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Import stuff</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">views</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#Redirect to index function in views.py</span>
</span></span><span class="line"><span class="cl"><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^$&#39;</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">index</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">&#39;index&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Finally, we&rsquo;re going to grab the urls.py file in our <em>project</em> folder - not the one we just edited within the app. So this should be ~/junos-dashboard/junos-dashboard/urls.py. In this file, we just need to tell the web server to include the urls.py file we created when evaluating traffic. This file should already exist and have content in it for routing traffic to the admin page, so just add an entry under urlpatterns - should look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^$&#39;</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">&#39;vpn.urls&#39;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^admin/&#39;</span><span class="p">,</span> <span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>I just want to point out that in this case, we&rsquo;re catching traffic again with the ^$ regex. This is so that any traffic to the main page (in this case <a href="http://10.2.32.90:8000">http://10.2.32.90:8000</a>) is automatically sent to our vpn app. However, if you had multiple apps running, you might want to give each one it&rsquo;s own directory.</p>
<p>Go ahead and execute the runserver tool again, and validate that you&rsquo;re able to successfully hit the page and retrieve the test text we put in.</p>
<hr>
<p>Okay, I think we&rsquo;re going to stop here for this post. At this point we have Django installed and our project/app created. We also have the beginnings of our dashboard page. In the next post, we&rsquo;ll actually start building out the dashboard interface!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 1 - Initial thoughts)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/</link>
      <pubDate>Wed, 07 Jun 2017 08:32:04 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/</guid>
      <description>I decided to build a web GUI to monitor Juniper SRX VPN tunnels. Here&amp;rsquo;s what I was thinking&amp;hellip;</description>
      <content:encoded><![CDATA[<p>So maybe you&rsquo;re like me - you&rsquo;ve done a bit of everything in the past, but now you&rsquo;ve specialized on something (like networking). Know what the best part of that is? Using all that knowledge and experience to make things happen. In this case, I&rsquo;m talking about some of my prior experiences in scripting in automation, paired with some pretty great network APIs.</p>
<p>One thing my current job has never had is a good way to view VPN tunnel status between our firewalls. Our Check Point firewalls don&rsquo;t really provide a good high-level view, which has unfortunately caused some confusion around whether or not a site-to-site VPN tunnel is currently established. Luckily, over the past year I&rsquo;ve had the opportunity to install over 20 new Juniper SRX firewalls - mostly made up of SRX 1500 and SRX 345 models. I&rsquo;ve written a bit before about the JunOS APIs and pyEZ (their Python library), but I&rsquo;m always really excited at a new use case for network automation.</p>
<p>So recently I decided that it would be great to build a web-based dashboard, which would query all of our SRX firewalls for currently connected VPN tunnels. Approximately 90% of the current tunnels are site-to-site between our own data center locations, with the other 10% being external to customers. My idea was that I would have a simple HTML table, which would show each data center along the top and bottom and whether or not each was connected to each other, kinda like this:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><strong>Location 1</strong></th>
          <th><strong>Location 2</strong></th>
          <th><strong>Location 3</strong></th>
          <th><strong>Location 4</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Location 1</strong></td>
          <td>N/A</td>
          <td>UP</td>
          <td>UP</td>
          <td>UP</td>
      </tr>
      <tr>
          <td><strong>Location 2</strong></td>
          <td>UP</td>
          <td>N/A</td>
          <td>UP</td>
          <td>DOWN!</td>
      </tr>
      <tr>
          <td><strong>Location 3</strong></td>
          <td>UP</td>
          <td>UP</td>
          <td>N/A</td>
          <td>UP</td>
      </tr>
      <tr>
          <td><strong>Location 4</strong></td>
          <td>UP</td>
          <td>DOWN!</td>
          <td>UP</td>
          <td>N/A</td>
      </tr>
  </tbody>
</table>
<p>I wasn&rsquo;t really concerned about making it look fancy - just dynamically updatable by whatever backend mechanism I used. Speaking of which, I had also assumed I would just be writing some simple Python script to pull VPN info from each device, then just write it to an HTML file. I haven&rsquo;t really done much web stuff with Python in the past, so I set out to learn a bit and figure out what the best approach might be.</p>
<p>I ended up settling on trying out <a href="https://www.djangoproject.com">Django</a> to write the frontend web stuff. I had never used it before, but I&rsquo;ve been interested in trying - and what&rsquo;s a better time to learn, than when you have something that needs to be accomplished? One thing that really pushed me towards Django for this project was the built-in administration page. This was huge for me, because it meant that I could easily have a way for other people to update the web dashboard. Whenever a new location came online, I wouldn&rsquo;t have to go update the script directly - anyone on my team could log into the admin page and make the changes.</p>
<p>In this post I just wanted to get through what my ideas were behind this project. In the next few weeks, I&rsquo;ll begin explaining how I built the dashboard using Django and use pyEZ to scrape VPN status from each firewall.</p>
<p>Thoughts? Drop a comment below!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to: Synology Backups with CrashPlan</title>
      <link>https://0x2142.com/synology-backups-with-crashplan/</link>
      <pubDate>Tue, 16 May 2017 08:00:28 +0000</pubDate>
      <guid>https://0x2142.com/synology-backups-with-crashplan/</guid>
      <description>In this post, we&amp;rsquo;ll walk through a tutorial on setting up Crashplan to back up a Synology NAS via NFS</description>
      <content:encoded><![CDATA[<p><sup><em>Note: I may receive commissions for purchases made through links in this post. This is to help support my blog and does not have any impact on my recommendations.</em></sup></p>
<hr>
<p>As part of my home lab, I have an older Synology DS411 that I picked up in early 2012. I&rsquo;ve been using the device since then with 4x 3TB drives as an iSCSI backend to my ESX host. Of course, I&rsquo;ve also been using the NAS for general file storage (photos, videos, documents, etc). So in late 2014 I decided that I needed to find a good backup solution for it.</p>
<p>I started using <a href="https://www.crashplan.com/en-us/">CrashPlan</a>, because they offer unlimited cloud storage for only $5.99 a month. This was amazing to me, because I had around 1.2TB of data at the time. I found a great community package <a href="https://pcloadletter.co.uk/2012/01/30/crashplan-syno-package">here</a>, which allowed me to install the backup client directly on the NAS. This ran great until late 2016 when CrashPlan updated to 4.8.0 and ended support for ARM processors (which is what the DS411 uses). For the past 6-7 months, I&rsquo;ve been unable to back up my primary storage device at home. Since my DS411 is reaching the end of it&rsquo;s life anyways, I figured I would just wait until I replaced it with one of the new Synology devices that use an Intel processor.</p>
<p>Well a few days ago, I received an email from CrashPlan threatening to delete my backups since my NAS hadn&rsquo;t connected in over 6 months. It makes sense why they do that - but I didn&rsquo;t want to lose my backups before I could replace the device. Re-seeding my backups whenever I picked up a replacement NAS would take forever (I think I have ~2TB backed up currently). So I finally forced myself to sit down and find an alternate solution.</p>
<p>Alright - so as of today I now have a dedicated CentOS VM running on my ESX host, which is connected to the Synology via NFS. This VM is running the CrashPlan client, and my backups have resumed! Here is how I got this all set up:</p>
<h2 id="synology-configuration">Synology Configuration</h2>
<ol>
<li>
<p>Enable NFS on the Synology</p>
<ul>
<li>Open up the <strong>Control Panel</strong> and go to <strong>File Services</strong></li>
<li>Scroll down to the <strong>NFS</strong> section, and check the box for <strong>Enable NFS</strong></li>
<li>Click <strong>Apply</strong></li>
</ul>
</li>
<li>
<p>Apply NFS permissions to each share</p>
<ul>
<li>Still under <strong>Control Panel</strong>, visit <strong>Shared Folders</strong></li>
<li>For each folder you need to back up via CrashPlan:
<ul>
<li>Select the folder and click <strong>Edit</strong></li>
<li>Click the tab for <strong>NFS Permissions</strong>, then click <strong>Create</strong></li>
<li>Enter the IP address for your CentOS VM (or other linux system)</li>
<li>Set privilege to <strong>Read-Only</strong> (you could probably leave this read-write, but CrashPlan only needs read permissions to back up data)</li>
<li>Click <strong>OK</strong></li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="linux-vm-setup">Linux VM Setup</h2>
<ol>
<li>
<p>Build a CentOS VM - This can be on an ESX host like I used, or just a standalone PC</p>
<ul>
<li>Don&rsquo;t forget to use the same IP address that we entered in the Synology for NFS</li>
</ul>
</li>
<li>
<p>Install packages</p>
<ul>
<li>Make sure you get the latest package updates first: <strong>yum -y update</strong></li>
<li>Install NFS tools: <strong>yum -y installnfs-utils nfs-utils-lib</strong></li>
</ul>
</li>
<li>
<p>Test NFS</p>
<ul>
<li>If you have the packages installed and NFS set up correctly on the Synology, then you should be able to validate the configuration by using showmount. For example, here are the directories that I was using CrashPlan to backup:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@SynologyBackupVM~]# showmount -e 10.12.32.2
</span></span><span class="line"><span class="cl">Export list for 10.12.32.2:
</span></span><span class="line"><span class="cl">/volume1/backups   10.12.32.209
</span></span><span class="line"><span class="cl">/volume1/documents 10.12.32.209
</span></span><span class="line"><span class="cl">/volume1/homes     10.12.32.209
</span></span><span class="line"><span class="cl">/volume1/photos    10.12.32.209
</span></span></code></pre></div><ul>
<li>Make your local Linux directory structure, which shouldmatch what the Synology structure is. So in my case, I made new directories on my local Linux VM for /volume1/backups, /volume1/documents, etc</li>
<li>Edit <strong>/etc/fstab</strong> to auto-mount your NFS shares on boot. In my case, I added the following lines:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># NFS Share location          Local Folder
</span></span><span class="line"><span class="cl">10.12.32.2:/volume1/backups   /volume1/backups   nfs defaults 0 0
</span></span><span class="line"><span class="cl">10.12.32.2:/volume1/documents /volume1/documents nfs defaults 0 0
</span></span><span class="line"><span class="cl">10.12.32.2:/volume1/homes     /volume1/homes     nfs defaults 0 0
</span></span><span class="line"><span class="cl">10.12.32.2:/volume1/photo     /volume1/photo     nfs defaults 0 0
</span></span></code></pre></div><ul>
<li>Reboot, and check to make sure each NFS share was actually mounted. If you do a <strong>ls /volume1/backups</strong>, do yousee all the files on the NAS in that folder?</li>
<li>If everything works, then grab the CrashPlan Client installer (4.8.2 was the latest at the time)
<ul>
<li><strong>wget <a href="https://download.code42.com/installs/linux/install/CrashPlan/CrashPlan_4.8.2_Linux.tgz">https://download.code42.com/installs/linux/install/CrashPlan/CrashPlan_4.8.2_Linux.tgz</a></strong></li>
</ul>
</li>
<li>Extract the files: <strong>tar -xzvf CrashPlan_4.8.2_Linux.tgz</strong></li>
<li>Run the installer: <strong>cdcrashplan-install/ &amp;&amp; ./install.sh</strong>
<ul>
<li>I just let CrashPlan use all the defaults, so I didn&rsquo;t change any options during install</li>
</ul>
</li>
<li>Set CrashPlan to start automatically: <strong>chkconfig crashplan on</strong></li>
<li>Make sure the service is already running: <strong>/etc/init.d/crashplan status</strong></li>
</ul>
</li>
</ol>
<p>At this point we should be able to run CrashPlan&rsquo;s backup client on our VM, which will pull the data across the network from the NAS. The last step is to set up our local PC for remote administration of the Linux VM. Unfortunately, this process has become more and more painful as CrashPlan keeps updating their clients. They provide their own <a href="https://support.code42.com/CrashPlan/4/Configuring/Using_CrashPlan_On_A_Headless_Computer">documentation</a> on how to do this piece, but I&rsquo;ll summarize what I did here:</p>
<h2 id="connecting-the-crashplan-ui-to-the-linux-vm">Connecting the CrashPlan UI to the Linux VM</h2>
<ol>
<li>
<p>Download and install the CrashPlan Client on your PC (In this case, I&rsquo;m using a Windows 10 laptop)</p>
</li>
<li>
<p>On your linux VM, run the following command to findyour authentication token: <strong>cat /var/lib/crashplan/.ui_info</strong></p>
</li>
<li>
<p>Back on the Windows side, place that token in the following file: <strong>C:\ProgramData\CrashPlan.ui_info</strong></p>
<ul>
<li>You will replace the existing token in the file</li>
<li>Also change the port from 4243 to 4200</li>
<li>Should look something like this when you&rsquo;re done: <code>4200,3da4v903-7q38-4r52-e67e-79aecxf760c4,127.0.0.1</code></li>
</ul>
</li>
<li>
<p>Download <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a>, which will be used to create a SSH tunnel to our linux box</p>
</li>
<li>
<p>Open PuTTY and set the following configurations:</p>
<ul>
<li>On the left side, go to <strong>Connection</strong> &gt; <strong>SSH</strong> &gt; <strong>Tunnels</strong></li>
<li>Enter thesource port: <strong>4200</strong></li>
<li>Enter the destination: <strong>localhost:4243</strong></li>
<li>Click <strong>Add</strong></li>
<li>On the left side, go back up to <strong>Session</strong></li>
<li>Enter the IP address of your Linux VM</li>
<li>(optional) Under <strong>Saved Sessions</strong>, put in a name and click<strong>Save</strong></li>
</ul>
</li>
<li>
<p>Once that&rsquo;s done, go ahead and click<strong>Open</strong> to connect to your Linux VM</p>
</li>
<li>
<p>Log into the VM and just leave the window open.</p>
<ul>
<li><strong>Note:</strong> This SSH session will need to remain open any time you need to connect to your Linux VM and administer CrashPlan</li>
</ul>
</li>
<li>
<p>Open the CrashPlan client locally on your Windows machine</p>
</li>
<li>
<p>Log into CrashPlan using your account (Should be the same one you were previously using to back up your Synology with)</p>
</li>
<li>
<p>CrashPlan may give you a warning about migrating to a new PC, and ask if you want to adopt the backups - You want to acceptthis prompt, and let CrashPlan know that your new Linux VM is a replacement PC for your Synology.</p>
</li>
</ol>
<p>As long as everything went according to plan, the CrashPlan client should start scanning the NFS shares on your Linux VM and comparing them to what&rsquo;s already backed up. Once it completes its synchronization, it will initiate the backup processes again.</p>
<p>I was extremely happy that this worked, because I was able to start backing up my data again (which had apparently almost doubled since the last time CrashPlan was connected). In my case, moving to a Linux VM provided me with much better backup performance as well, since the Synology DS411 only has a 1.6Ghz single-core processor and 512MB of RAM.</p>
<p>I hope this helps out anyone else out there who may have been trapped in a similar scenario. If you have any questions, please feel free to leave me a comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tracking Latency and Packet Loss with SmokePing</title>
      <link>https://0x2142.com/tracking-latency-and-packet-loss-with-smokeping/</link>
      <pubDate>Tue, 25 Apr 2017 08:53:04 +0000</pubDate>
      <guid>https://0x2142.com/tracking-latency-and-packet-loss-with-smokeping/</guid>
      <description>Why should you use SmokePing - and a quick look at how to interpret the graphs it generates</description>
      <content:encoded><![CDATA[<p>&ldquo;The network is slow&rdquo; - Sound like something you&rsquo;ve heard before? What does &lsquo;slow&rsquo; mean anyway? And is it different from yesterday? Sometimes tracking down network &lsquo;slowness&rsquo; can be pretty difficult, especially when you don&rsquo;t have a good baseline of what is normal. This kind of goes back to one of the tips I shared earlier in &lsquo;<a href="/a-little-bit-of-magic/">A Little Bit of Magic</a>&rsquo; - having a baseline and understanding of what is normal on your network will help you find issues much more quickly.</p>
<p>When I started working for a cloud service provider a few years ago, the first thing to start coming up extremely often is network latency and performance issues. These are things I never had to worry too much about previously, as most of my jobs had been with enterprise environments where everyone is on the same LAN (or at least within one state). However, when you get into hosting a Software-as-a-Service cloud on a global scale, then slight performance issues begin to mean big slowdowns for your customers.</p>
<p>I was amazed at the current network infrastructure monitoring that was in place when I began working for the SaaS provider:  A few bare-bones Cacti instances, completely unmanaged by anyone, and not configured to monitor any relevant ports or data. Today that situation is vastly different - I have installed a few different applications that allow us to get alerted on network variances and quickly determine exactly where the issue is. One of the tools that has helped us get to this point is called <a href="http://oss.oetiker.ch/smokeping/">SmokePing</a>, which I would like to talk about today.</p>
<h2 id="setup-and-installation">Setup and Installation</h2>
<p>I won&rsquo;t get into the details of installing SmokePing, as there are already a number of good tutorials out there (like <a href="http://pingpros.com/linux-network-tools/centos/6-x/installing-smokeping-on-centos/">this one</a> or <a href="http://www.wedebugyou.com/2012/11/how-to-install-and-configure-smokeping-on-centos-6">this one</a>). If you have a decent familiarity with Linux, then the process should be fairly straightforward. Keep in mind that your SmokePing graphs will show latency and packet loss between the machine you have SmokePing installed on and the targets you define. So make sure that you plan out where you deploy your SmokePing machine(s) to provide beneficial information.</p>
<p>Once you have SmokePing installed and setup, it&rsquo;s time to start defining targets to monitor. We have over a dozen points of presence globally, so I&rsquo;ve installed SmokePing on a single machine in each location. Each instance has ping targets defined for every network segment within it&rsquo;s own datacenter, network segments in every other datacenter, and some public IP space of every datacenter. So we accomplish latency and packet loss monitoring within the datacenter, across the site-to-site VPNs between each datacenter, and the general internet connections between each datacenter. For certain customers, particularly those who have dedicated MPLS circuits to us, we are also monitoring latency/packet loss to customer endpoints.</p>
<p>SmokePing also supports deployment in a controller/worker configuration, where you have a single primary configuration/management point and several workers to perform testing. I really want to test this out for our environment, but I haven&rsquo;t quite had the time to dedicate to it. If you&rsquo;re interested though, you can find the details on that <a href="http://oss.oetiker.ch/smokeping/doc/smokeping_master_slave.en.html">here</a>.</p>
<h2 id="interpreting-the-graphs">Interpreting the graphs</h2>
<p>The graphs created by Smokeping might not seem clear the first time you see them. For example, take a look at this:</p>
<p><img alt="image" loading="lazy" src="/content/images/2017/04/smokeping-ex1.png#center"></p>
<p>This graph is the result of a standard latency test - 20 pings every 5 minutes. So for every step on the graph, SmokePing draws out the range of responses in those 20 pings - shown by the gray &lsquo;smoke&rsquo;. The darker the gray area, the more pings came back with that response time - and similarly the lighter areas mean that fewer pings had that response time. The solid colored part of the line marks the average response across all 20 pings, and also gives an indication of percentage of packets lost.</p>
<p>So the first thing I would notice about this graph is that the average response time is varying quite significantly between about 15ms and 200ms. In a normal healthy network, you should not expect to see such a drastic change in response times like that - some variation is normal, but not to this extreme. Two other things to note from this graph: The time of each latency jump seems to line up almost every 30 minutes, and towards the end we begin seeing some slight packet loss.</p>
<p>After being informed that there was a performance issue between a few different systems, I opened up SmokePing immediately to start looking for anything that jumped out - like the graph above. In this case, this was a 200Mb dedicated MPLS circuit used only for replication traffic between data centers. Every 30 minutes, a replication job was kicking off and saturating the line for a few minutes - which in turn was causing excessive jumps in latency and some minor packet loss.</p>
<p>As another example:</p>
<p><img alt="image" loading="lazy" src="/content/images/2017/04/smokeping-ex2.png#center"></p>
<p>The first thing you probably notice about the graph above is the sudden stabilization of latency. This graph monitors traffic between two data centers over an IPsec VPN tunnel - and we happened to be suspecting that one of the two peer firewalls was having performance issues. We swapped out to new hardware on one side of the connection, and the latency immediately started flat-lining. A consistent 85ms is way better than averaging anywhere from 90-180ms. (And if you happened to notice the slight packet loss after the new device was implemented - that was actually due to an unrelated upstream provider issue). My point with this graph is really just to show how helpful it is to have the historical data available. It would have been extremely difficult to prove that  the one firewall was the root cause of our problems if I didn&rsquo;t have a way to track the issue.</p>
<hr>
<p>So that&rsquo;s a bit about SmokePing and how I&rsquo;ve deployed it within a cloud provider&rsquo;s environment. It&rsquo;s only been up and running for a few months, but I&rsquo;ve already found it to be extremely helpful in troubleshooting performance and latency issues. SmokePing is also extensible via scripting, which can help to collect additional data at the time of an issue. I&rsquo;ve written a few quick scripts to run extended traceroutes during packet loss events, which I might post up here in the future.</p>
<p>Have you installed SmokePing in your environment? How do you use it? Has it helped you with performance issues?</p>
<p>Comment below!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
